diff --git a/.gitignore b/.gitignore index 06e70689..ed088133 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,6 @@ # Ignora directorios de compilación **/target -# Ignora directorios de archivos estáticos -**/static - # Archivos de log **/log/*.log* diff --git a/Cargo.lock b/Cargo.lock index 1ad66c7c..433ce60d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,15 +20,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "aho-corasick" -version = "0.7.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" -dependencies = [ - "memchr", -] - [[package]] name = "aho-corasick" version = "1.1.4" @@ -942,7 +933,7 @@ version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" dependencies = [ - "aho-corasick 1.1.4", + "aho-corasick", "bstr", "log", "regex-automata", @@ -974,16 +965,6 @@ dependencies = [ "rand", ] -[[package]] -name = "hashbrown" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" -dependencies = [ - "ahash", - "bumpalo", -] - [[package]] name = "hashbrown" version = "0.14.5" @@ -1514,17 +1495,6 @@ dependencies = [ "unicase", ] -[[package]] -name = "minify-js" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1fa5546ee8bd66024113e506cabe4230e76635a094c06ea2051b66021dda92e" -dependencies = [ - "aho-corasick 0.7.20", - "lazy_static", - "parse-js", -] - [[package]] name = "miniz_oxide" version = "0.8.9" @@ -1751,6 +1721,7 @@ version = "0.1.0" dependencies = [ "pagetop", "pagetop-build", + "tokio", ] [[package]] @@ -1760,6 +1731,7 @@ dependencies = [ "pagetop", "pagetop-build", "serde", + "tokio", ] [[package]] @@ -1767,18 +1739,9 @@ name = "pagetop-build" version = "0.3.2" dependencies = [ "grass", - "minify-js", "pagetop-statics", ] -[[package]] -name = "pagetop-htmx" -version = "0.1.0" -dependencies = [ - "pagetop", - "pagetop-build", -] - [[package]] name = "pagetop-macros" version = "0.3.0" @@ -1849,19 +1812,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "parse-js" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2742b5e32dcb5930447ed9f9e401a7dfd883867fc079c4fac44ae8ba3593710e" -dependencies = [ - "aho-corasick 0.7.20", - "bumpalo", - "hashbrown 0.13.2", - "lazy_static", - "memchr", -] - [[package]] name = "pastey" version = "0.2.2" @@ -2144,7 +2094,7 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ - "aho-corasick 1.1.4", + "aho-corasick", "memchr", "regex-syntax", ] diff --git a/Cargo.toml b/Cargo.toml index 03a5944a..269f752f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,6 @@ members = [ # Extensions "extensions/pagetop-aliner", "extensions/pagetop-bootsier", - "extensions/pagetop-htmx", "extensions/pagetop-seaorm", ] @@ -36,7 +35,6 @@ indexmap = "2.14" indoc = "2.0" itoa = "1.0" mime_guess = "2.0" -minify-js = "0.6" parking_lot = "0.12" pastey = "0.2" path-slash = "0.2" @@ -64,7 +62,6 @@ pagetop-statics = { version = "0.1", path = "helpers/pagetop-statics" } # Extensions pagetop-aliner = { version = "0.1", path = "extensions/pagetop-aliner" } pagetop-bootsier = { version = "0.1", path = "extensions/pagetop-bootsier" } -pagetop-htmx = { version = "0.1", path = "extensions/pagetop-htmx" } pagetop-seaorm = { version = "0.0", path = "extensions/pagetop-seaorm" } # PageTop pagetop = { version = "0.5", path = "." } diff --git a/README.md b/README.md index 35023682..89fade06 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@
- +

PageTop

diff --git a/assets/css/basic.css b/assets/css/basic.css deleted file mode 100644 index b35b29ef..00000000 --- a/assets/css/basic.css +++ /dev/null @@ -1,147 +0,0 @@ -:root { - /* Font families */ - --val-font-sans: system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif; - --val-font-serif: Georgia,"Times New Roman",serif; - --val-font-monospace: SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; - --val-font-family: var(--val-font-sans); - /* Font size */ - --val-fs--base: 1rem; - /* Font weight */ - --val-fw--base: 400; - /* Line height */ - --val-lh--base: 1.5; - /* Colors */ - --val-color--bg: #fafafa; - --val-color--text: #212529; - --val-color--text--muted: color-mix(in srgb, var(--val-color--text) 50%, var(--val-color--bg)); - --val-color--border: #212529; - --val-color--primary: #0d6efd; - --val-color--danger: #dc3545; - --val-color--switch-off: #adb5bd; -} - -*, *::before, *::after { - box-sizing: border-box; -} - -html { - scroll-behavior: smooth; -} - -body { - font-family: var(--val-font-family); - font-size: var(--val-fs--base); - font-weight: var(--val-fw--base); - line-height: var(--val-lh--base); - color: var(--val-color--text); - background-color: var(--val-color--bg); - -webkit-tap-highlight-color: transparent; -} - -/* - * Form components - */ - -.form-required { - color: var(--val-color--danger); - margin-left: 0.25rem; -} - -.form-label { - display: block; - margin-top: 1em; -} - -.form-text { - font-size: 0.9375rem; - color: var(--val-color--text--muted); -} - -.form-field > select.form-select, -.form-field > input.form-control, -.form-field > input.form-range, -.form-field > textarea.form-control { - display: block; - padding: 0.25rem 0.5rem; - width: 100%; -} - -fieldset { - margin: 2rem 0 1rem; - background: var(--val-color--bg); -} -fieldset > legend { - background-color: var(--val-color--bg); - border: 2px groove threedface; - padding: 0 0.5em; -} - -.form-check { - display: flex; - align-items: center; - gap: 0.5rem; - min-height: 1.5rem; - margin-bottom: 0.25rem; -} -.form-check-inline { - display: inline-flex; - margin-right: 1rem; -} -.form-check-reverse { - flex-direction: row-reverse; - justify-content: flex-start; - text-align: right; -} -.form-check-input { - flex-shrink: 0; - cursor: pointer; -} -.form-check-label { - cursor: pointer; -} -.form-switch .form-check-input { - appearance: none; - -webkit-appearance: none; - width: 2em; - height: 1em; - background-color: var(--val-color--switch-off); - border-radius: 1em; - position: relative; - cursor: pointer; - transition: background-color .15s ease-in-out; -} -.form-switch .form-check-input::after { - content: ""; - position: absolute; - width: 1em; - height: 1em; - background-color: white; - border-radius: 50%; - top: 0; - left: 0; - transition: transform .15s ease-in-out; - box-shadow: 0 0 0 1px rgba(0, 0, 0, .25); -} -.form-switch .form-check-input:checked { - background-color: var(--val-color--primary); -} -.form-switch .form-check-input:checked::after { - transform: translateX(1em); -} - -input:disabled, -input:disabled + label { - cursor: default !important; -} -input:disabled + label { - color: var(--val-color--text--muted); -} - -/* - * Region Footer - */ - -.region-footer { - padding: .75rem 0 3rem; - text-align: center; -} diff --git a/build.rs b/build.rs index a83be1d4..85e02e02 100644 --- a/build.rs +++ b/build.rs @@ -1,12 +1,6 @@ -use pagetop_build::{StaticFilesBundle, copy_dir}; +use pagetop_build::StaticFilesBundle; fn main() -> std::io::Result<()> { - // Regenera `static/` desde cero sólo si hay cambios en `assets/`. - println!("cargo:rerun-if-changed=assets"); - let _ = std::fs::remove_dir_all("static"); - - copy_dir("assets", "static")?; - StaticFilesBundle::from_dir("./static", None) .with_name("assets") .build() diff --git a/examples/form-controls.rs b/examples/form-controls.rs index 172de633..1c7f066e 100644 --- a/examples/form-controls.rs +++ b/examples/form-controls.rs @@ -1,6 +1,5 @@ use pagetop::prelude::*; -use pagetop_bootsier::theme::form; use pagetop_bootsier::theme::*; include_locales!(LOC from "examples/locale"); @@ -144,20 +143,17 @@ async fn form_controls(request: HttpRequest) -> Result { .with_value("form-selections"), ) // Botones de acción. - .with_child(Button::submit(L10n::t("btn_submit", &LOC)).with_prop( - PropsOp::add_classes(classes::ButtonColor::solid( - Color::Primary, - )), - )) - .with_child(Button::reset(L10n::t("btn_reset", &LOC)).with_prop( - PropsOp::add_classes(classes::ButtonColor::outline( - Color::Secondary, - )), - )) .with_child( - Button::plain(L10n::t("btn_cancel", &LOC)).with_prop( - PropsOp::add_classes(classes::ButtonColor::link()), - ), + Button::submit(L10n::t("btn_submit", &LOC)) + .with_color(ButtonColor::Background(Color::Primary)), + ) + .with_child( + Button::reset(L10n::t("btn_reset", &LOC)) + .with_color(ButtonColor::Outline(Color::Secondary)), + ) + .with_child( + Button::plain(L10n::t("btn_cancel", &LOC)) + .with_color(ButtonColor::Link), ), ), ) @@ -179,7 +175,6 @@ async fn form_controls(request: HttpRequest) -> Result { .with_name("name") .with_label(L10n::t("label_name", &LOC)) .with_placeholder(L10n::t("placeholder_name", &LOC)) - .with_help_text(L10n::t("help_name", &LOC)) .with_required(true), ) .with_child( @@ -190,7 +185,6 @@ async fn form_controls(request: HttpRequest) -> Result { "placeholder_email", &LOC, )) - .with_help_text(L10n::t("help_email", &LOC)) .with_autocomplete( Some(form::Autocomplete::email()), ) @@ -268,175 +262,194 @@ async fn form_controls(request: HttpRequest) -> Result { .with_value("form-text"), ) // Botones de acción. - .with_child(Button::submit(L10n::t("btn_submit", &LOC)).with_prop( - PropsOp::add_classes(classes::ButtonColor::solid( - Color::Primary, - )), - )) - .with_child(Button::reset(L10n::t("btn_reset", &LOC)).with_prop( - PropsOp::add_classes(classes::ButtonColor::outline( - Color::Secondary, - )), - )) .with_child( - Button::plain(L10n::t("btn_cancel", &LOC)).with_prop( - PropsOp::add_classes(classes::ButtonColor::link()), - ), + Button::submit(L10n::t("btn_submit", &LOC)) + .with_color(ButtonColor::Background(Color::Primary)), + ) + .with_child( + Button::reset(L10n::t("btn_reset", &LOC)) + .with_color(ButtonColor::Outline(Color::Secondary)), + ) + .with_child( + Button::plain(L10n::t("btn_cancel", &LOC)) + .with_color(ButtonColor::Link), ), ), ) // Bloque 3: listas de selección y etiquetas flotantes. .with_child( Block::new() - .with_title( - if global::SETTINGS.app.theme.eq_ignore_ascii_case("bootsier") { - L10n::t("block_lists_floating", &LOC) - } else { - L10n::t("block_lists", &LOC) - }, - ) - .with_child(form_lists()), + .with_title(L10n::t("block_lists", &LOC)) + .with_child( + Form::new() + .with_id("form-lists") + .with_action("/") + .with_method(form::Method::Post) + // Listas de selección (form::select::Field). + .with_child( + form::Fieldset::new() + .with_legend(L10n::t("fieldset_select", &LOC)) + .with_child( + form::select::Field::new() + .with_name("language") + .with_label(L10n::t("label_language", &LOC)) + .with_item( + form::select::Item::new( + "", + L10n::t("select_choose", &LOC), + ) + .with_selected(true), + ) + .with_group( + form::select::Group::new(L10n::t( + "select_group_europe", + &LOC, + )) + .with_item(form::select::Item::new( + "es", + L10n::t("select_spanish", &LOC), + )) + .with_item(form::select::Item::new( + "fr", + L10n::t("select_french", &LOC), + )), + ) + .with_group( + form::select::Group::new(L10n::t( + "select_group_americas", + &LOC, + )) + .with_item(form::select::Item::new( + "en", + L10n::t("select_english", &LOC), + )) + .with_item(form::select::Item::new( + "pt", + L10n::t("select_portuguese", &LOC), + )), + ) + .with_item( + form::select::Item::new( + "xx", + L10n::t("select_disabled", &LOC), + ) + .with_disabled(true), + ) + .with_required(true), + ) + .with_child( + form::select::Field::new() + .with_name("technologies") + .with_label(L10n::t("label_technologies", &LOC)) + .with_item( + form::select::Item::new( + "rust", + L10n::n("Rust"), + ) + .with_selected(true), + ) + .with_item( + form::select::Item::new( + "python", + L10n::n("Python"), + ) + .with_selected(true), + ) + .with_item(form::select::Item::new( + "javascript", + L10n::n("JavaScript"), + )) + .with_item(form::select::Item::new( + "go", + L10n::n("Go"), + )) + .with_item(form::select::Item::new( + "typescript", + L10n::n("TypeScript"), + )) + .with_multiple(true) + .with_rows(Some(4)) + .with_help_text(L10n::t("help_technologies", &LOC)), + ), + ) + // Etiquetas flotantes. + .with_child( + form::Fieldset::new() + .with_legend(L10n::t("fieldset_floating", &LOC)) + .with_child( + form::input::Field::text() + .with_name("fl_name") + .with_label(L10n::t("label_name", &LOC)) + .with_placeholder(L10n::t("placeholder_name", &LOC)) + .with_floating_label(true) + .with_required(true), + ) + .with_child( + form::Textarea::new() + .with_name("fl_comment") + .with_label(L10n::t("label_comment", &LOC)) + .with_placeholder(L10n::t( + "placeholder_comment", + &LOC, + )) + .with_floating_label(true), + ) + .with_child( + form::select::Field::new() + .with_name("fl_country") + .with_label(L10n::t("label_country", &LOC)) + .with_item( + form::select::Item::new( + "", + L10n::t("select_choose", &LOC), + ) + .with_selected(true), + ) + .with_item(form::select::Item::new( + "de", + L10n::t("select_germany", &LOC), + )) + .with_item(form::select::Item::new( + "es", + L10n::t("select_spain", &LOC), + )) + .with_item(form::select::Item::new( + "fr", + L10n::t("select_france", &LOC), + )) + .with_item(form::select::Item::new( + "pt", + L10n::t("select_portugal", &LOC), + )) + .with_floating_label(true) + .with_required(true), + ), + ) + // Campo oculto (form::Hidden). + .with_child( + form::Hidden::new() + .with_name("origin") + .with_value("form-lists"), + ) + // Botones de acción. + .with_child( + Button::submit(L10n::t("btn_submit", &LOC)) + .with_color(ButtonColor::Background(Color::Primary)), + ) + .with_child( + Button::reset(L10n::t("btn_reset", &LOC)) + .with_color(ButtonColor::Outline(Color::Secondary)), + ) + .with_child( + Button::plain(L10n::t("btn_cancel", &LOC)) + .with_color(ButtonColor::Link), + ), + ), ), ) .render() } -fn form_lists() -> Form { - let mut form = Form::new() - .with_id("form-lists") - .with_action("/") - .with_method(form::Method::Post) - // Listas de selección (form::select::Field). - .with_child( - form::Fieldset::new() - .with_legend(L10n::t("fieldset_select", &LOC)) - .with_child( - form::select::Field::new() - .with_name("language") - .with_label(L10n::t("label_language", &LOC)) - .with_item( - form::select::Item::new("", L10n::t("select_choose", &LOC)) - .with_selected(true), - ) - .with_group( - form::select::Group::new(L10n::t("select_group_europe", &LOC)) - .with_item(form::select::Item::new( - "es", - L10n::t("select_spanish", &LOC), - )) - .with_item(form::select::Item::new( - "fr", - L10n::t("select_french", &LOC), - )), - ) - .with_group( - form::select::Group::new(L10n::t("select_group_americas", &LOC)) - .with_item(form::select::Item::new( - "en", - L10n::t("select_english", &LOC), - )) - .with_item(form::select::Item::new( - "pt", - L10n::t("select_portuguese", &LOC), - )), - ) - .with_item( - form::select::Item::new("xx", L10n::t("select_disabled", &LOC)) - .with_disabled(true), - ) - .with_required(true), - ) - .with_child( - form::select::Field::new() - .with_name("technologies") - .with_label(L10n::t("label_technologies", &LOC)) - .with_item( - form::select::Item::new("rust", L10n::n("Rust")).with_selected(true), - ) - .with_item( - form::select::Item::new("python", L10n::n("Python")) - .with_selected(true), - ) - .with_item(form::select::Item::new("javascript", L10n::n("JavaScript"))) - .with_item(form::select::Item::new("go", L10n::n("Go"))) - .with_item(form::select::Item::new("typescript", L10n::n("TypeScript"))) - .with_multiple(true) - .with_rows(Some(4)) - .with_help_text(L10n::t("help_technologies", &LOC)), - ), - ); - - // Etiquetas flotantes: solo disponibles con el tema Bootsier. - if global::SETTINGS.app.theme.eq_ignore_ascii_case("bootsier") { - form = form.with_child( - form::Fieldset::new() - .with_legend(L10n::t("fieldset_floating", &LOC)) - .with_child( - form::input::Field::text() - .with_name("fl_name") - .with_label(L10n::t("label_name", &LOC)) - .with_placeholder(L10n::t("placeholder_name", &LOC)) - .with_floating_label(true) - .with_required(true), - ) - .with_child( - form::Textarea::new() - .with_name("fl_comment") - .with_label(L10n::t("label_comment", &LOC)) - .with_placeholder(L10n::t("placeholder_comment", &LOC)) - .with_floating_label(true), - ) - .with_child( - form::select::Field::new() - .with_name("fl_country") - .with_label(L10n::t("label_country", &LOC)) - .with_item( - form::select::Item::new("", L10n::t("select_choose", &LOC)) - .with_selected(true), - ) - .with_item(form::select::Item::new( - "de", - L10n::t("select_germany", &LOC), - )) - .with_item(form::select::Item::new("es", L10n::t("select_spain", &LOC))) - .with_item(form::select::Item::new( - "fr", - L10n::t("select_france", &LOC), - )) - .with_item(form::select::Item::new( - "pt", - L10n::t("select_portugal", &LOC), - )) - .with_floating_label(true) - .with_required(true), - ), - ); - } - - form - // Campo oculto (form::Hidden). - .with_child( - form::Hidden::new() - .with_name("origin") - .with_value("form-lists"), - ) - // Botones de acción. - .with_child( - Button::submit(L10n::t("btn_submit", &LOC)).with_prop(PropsOp::add_classes( - classes::ButtonColor::solid(Color::Primary), - )), - ) - .with_child( - Button::reset(L10n::t("btn_reset", &LOC)).with_prop(PropsOp::add_classes( - classes::ButtonColor::outline(Color::Secondary), - )), - ) - .with_child( - Button::plain(L10n::t("btn_cancel", &LOC)) - .with_prop(PropsOp::add_classes(classes::ButtonColor::link())), - ) -} - #[pagetop::main] async fn main() -> std::io::Result<()> { Application::prepare(&FormControls).run()?.await diff --git a/examples/locale/en-US/form-controls.ftl b/examples/locale/en-US/form-controls.ftl index ce6c76ca..7c2b3c96 100644 --- a/examples/locale/en-US/form-controls.ftl +++ b/examples/locale/en-US/form-controls.ftl @@ -2,8 +2,7 @@ title = Form controls slogan = Bootsier form components showcase block_selections = Checkboxes, switches and radio buttons block_text = Text fields, multiline and range -block_lists = Selection lists -block_lists_floating = Select lists and floating labels +block_lists = Select lists and floating labels fieldset_text = Text fields label_name = Full name @@ -17,8 +16,6 @@ label_url = Website placeholder_url = https://example.com label_search = Search placeholder_search = Search term... -help_name = Enter your full name as it appears on your ID. -help_email = We will only use your email to send important notifications. fieldset_textarea = Multiline text label_comment = Comment diff --git a/examples/locale/es-ES/form-controls.ftl b/examples/locale/es-ES/form-controls.ftl index 40781042..67a0fd2c 100644 --- a/examples/locale/es-ES/form-controls.ftl +++ b/examples/locale/es-ES/form-controls.ftl @@ -2,8 +2,7 @@ title = Controles de formulario slogan = Componentes Bootsier para formularios block_selections = Casillas, interruptores y botones de opción block_text = Campos de texto, multilínea y rango -block_lists = Listas de selección -block_lists_floating = Listas de selección y etiquetas flotantes +block_lists = Listas de selección y etiquetas flotantes fieldset_text = Campos de texto label_name = Nombre completo @@ -17,8 +16,6 @@ label_url = Sitio web placeholder_url = https://ejemplo.com label_search = Búsqueda placeholder_search = Término de búsqueda... -help_name = Introduce tu nombre completo tal como aparece en tu documento de identidad. -help_email = Solo usaremos tu correo para enviarte notificaciones importantes. fieldset_textarea = Texto multilínea label_comment = Comentario diff --git a/examples/navbar-menus.rs b/examples/navbar-menus.rs index 06c4ed36..7f8ccdda 100644 --- a/examples/navbar-menus.rs +++ b/examples/navbar-menus.rs @@ -80,9 +80,10 @@ impl Extension for SuperMenu { )) .with_item(navbar::Item::nav( Nav::new() - .with_prop(PropsOp::add_classes( + .with_classes( + ClassesOp::Add, classes::Margin::with(Side::Start, ScaleSize::Auto).to_class(), - )) + ) .with_item(nav::Item::link(L10n::t("menus_item_sign_up", &LOC), |cx| { cx.route("/auth/sign-up") })) diff --git a/extensions/pagetop-aliner/Cargo.toml b/extensions/pagetop-aliner/Cargo.toml index 072324e3..d2828b3f 100644 --- a/extensions/pagetop-aliner/Cargo.toml +++ b/extensions/pagetop-aliner/Cargo.toml @@ -17,5 +17,8 @@ authors.workspace = true [dependencies] pagetop.workspace = true +[dev-dependencies] +tokio.workspace = true + [build-dependencies] pagetop-build.workspace = true diff --git a/extensions/pagetop-aliner/README.md b/extensions/pagetop-aliner/README.md index ded3aa6f..bf515d66 100644 --- a/extensions/pagetop-aliner/README.md +++ b/extensions/pagetop-aliner/README.md @@ -11,13 +11,14 @@
-## Sobre PageTop +## 🧭 Sobre PageTop [PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y configurables, basadas en HTML, CSS y JavaScript. -## Guía rápida + +## ⚡️ Guía rápida Igual que con otras extensiones, **añade la dependencia** a tu `Cargo.toml`: @@ -43,6 +44,11 @@ impl Extension for MyApp { ] } } + +#[pagetop::main] +async fn main() -> std::io::Result<()> { + Application::prepare(&MyApp).run()?.await +} ``` Y **selecciona el tema en la configuración** de la aplicación: @@ -61,10 +67,10 @@ use pagetop_aliner::Aliner; async fn homepage(request: HttpRequest) -> Result { Page::new(request) .with_theme(&Aliner) - .with_child( + .add_child( Block::new() .with_title(L10n::l("sample_title")) - .with_child(Html::with(|cx| html! { + .add_child(Html::with(|cx| html! { p { (L10n::l("sample_content").using(cx)) } })), ) @@ -72,13 +78,15 @@ async fn homepage(request: HttpRequest) -> Result { } ``` -## Advertencia + +## 🚧 Advertencia **PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos hasta que se libere la versión **1.0.0**. -## Licencia + +## 📜 Licencia El código está disponible bajo una doble licencia: diff --git a/extensions/pagetop-aliner/src/lib.rs b/extensions/pagetop-aliner/src/lib.rs index 33f32677..dedf4e19 100644 --- a/extensions/pagetop-aliner/src/lib.rs +++ b/extensions/pagetop-aliner/src/lib.rs @@ -3,13 +3,14 @@

PageTop Aliner

-

Tema de PageTop que muestra esquemáticamente la composición de las páginas HTML.

+

Tema para PageTop que muestra esquemáticamente la composición de las páginas HTML.

[![Doc API](https://img.shields.io/docsrs/pagetop-aliner?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-aliner) [![Crates.io](https://img.shields.io/crates/v/pagetop-aliner.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-aliner) [![Descargas](https://img.shields.io/crates/d/pagetop-aliner.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-aliner) [![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/extensions/pagetop-aliner#licencia) +
## Sobre PageTop @@ -18,7 +19,8 @@ clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y configurables, basadas en HTML, CSS y JavaScript. -## Guía rápida + +# ⚡️ Guía rápida Igual que con otras extensiones, **añade la dependencia** a tu `Cargo.toml`: @@ -44,6 +46,11 @@ impl Extension for MyApp { ] } } + +#[pagetop::main] +async fn main() -> std::io::Result<()> { + Application::prepare(&MyApp).run()?.await +} ``` Y **selecciona el tema en la configuración** de la aplicación: diff --git a/extensions/pagetop-bootsier/Cargo.toml b/extensions/pagetop-bootsier/Cargo.toml index 47766c52..44b6d248 100644 --- a/extensions/pagetop-bootsier/Cargo.toml +++ b/extensions/pagetop-bootsier/Cargo.toml @@ -18,5 +18,8 @@ authors.workspace = true pagetop.workspace = true serde.workspace = true +[dev-dependencies] +tokio.workspace = true + [build-dependencies] pagetop-build.workspace = true diff --git a/extensions/pagetop-bootsier/README.md b/extensions/pagetop-bootsier/README.md index 69440ec6..f71f221e 100644 --- a/extensions/pagetop-bootsier/README.md +++ b/extensions/pagetop-bootsier/README.md @@ -11,13 +11,14 @@ -## Sobre PageTop +## 🧭 Sobre PageTop [PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y configurables, basadas en HTML, CSS y JavaScript. -## Guía rápida + +## ⚡️ Guía rápida Igual que con otras extensiones, **añade la dependencia** a tu `Cargo.toml`: @@ -43,6 +44,11 @@ impl Extension for MyApp { ] } } + +#[pagetop::main] +async fn main() -> std::io::Result<()> { + Application::prepare(&MyApp).run()?.await +} ``` Y **selecciona el tema en la configuración** de la aplicación: @@ -61,10 +67,10 @@ use pagetop_bootsier::Bootsier; async fn homepage(request: HttpRequest) -> Result { Page::new(request) .with_theme(&Bootsier) - .with_child( + .add_child( Block::new() .with_title(L10n::l("sample_title")) - .with_child(Html::with(|cx| html! { + .add_child(Html::with(|cx| html! { p { (L10n::l("sample_content").using(cx)) } })), ) @@ -72,19 +78,22 @@ async fn homepage(request: HttpRequest) -> Result { } ``` -## Créditos + +## 📚 Créditos Este *crate* integra la biblioteca de estilos [Bootstrap 5.3.8](https://getbootstrap.com/) para definir el comportamiento, la apariencia y los componentes de la interfaz. Bootstrap se distribuye bajo licencia [MIT](https://github.com/twbs/bootstrap/blob/main/LICENSE). -## Advertencia + +## 🚧 Advertencia **PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos hasta que se libere la versión **1.0.0**. -## Licencia + +## 📜 Licencia El código está disponible bajo una doble licencia: diff --git a/extensions/pagetop-bootsier/assets/_bootsier-custom.scss b/extensions/pagetop-bootsier/assets/_bootsier-custom.scss deleted file mode 100644 index 51f08dd8..00000000 --- a/extensions/pagetop-bootsier/assets/_bootsier-custom.scss +++ /dev/null @@ -1,84 +0,0 @@ -// Bootsier CSS rules: self-hosted fonts, form components, and regions. - -// Self-hosted Source Sans 3 (SIL OFL 1.1), served from /bootsier/fonts. -// Required by AdminLTE 4, which declares it as the primary font family in $font-family-sans-serif. -@font-face { - font-family: "Source Sans 3"; - src: url("/bootsier/fonts/bootsier.font.woff2") format("woff2"); - font-weight: 200 900; - font-style: normal; - font-display: swap; -} - -@font-face { - font-family: "Source Sans 3"; - src: url("/bootsier/fonts/bootsier.font.italic.woff2") format("woff2"); - font-weight: 200 900; - font-style: italic; - font-display: swap; -} - -// Font-relative top offset to keep the skip-link hidden at any font size. -.skip-link { - top: -3em; -} - -// Required field indicator in forms. -.form-required { - color: var(--bs-danger); - margin: 0 0.25rem; -} - -// Form field with consistent bottom margin. -.form-field { - margin-bottom: 1rem; - - &:last-child { - margin-bottom: 0; - } -} - -// Fieldset with border and floating legend. -fieldset { - position: relative; - background-color: var(--bs-body-bg); - border: var(--bs-border-width) solid var(--bs-border-color); - border-radius: var(--bs-border-radius); - padding: 2rem 1rem 1rem; - margin: 2rem 0 1rem; -} - -fieldset > legend { - position: absolute; - top: 0; - left: 1rem; - transform: translateY(-50%); - color: var(--bs-secondary-color); - background-color: var(--bs-body-bg); - border: var(--bs-border-width) solid var(--bs-border-color); - border-radius: var(--bs-border-radius); - padding: 0.125rem 0.75rem; - font-size: ($font-size-sm + $font-size-base) / 2; - font-weight: var(--bs-font-weight-normal); - line-height: 1.25; - width: fit-content; - max-width: 75%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.fieldset-description { - margin-bottom: 1rem; -} - -// Gap between group label and first inline check. -.form-label + .form-check-inline { - margin-left: 1rem; -} - -// Footer region. -.region-footer { - padding: 0.75rem 0 3rem; - text-align: center; -} diff --git a/extensions/pagetop-bootsier/assets/_bootsier-icons.scss b/extensions/pagetop-bootsier/assets/_bootsier-icons.scss deleted file mode 100644 index fb05eb1e..00000000 --- a/extensions/pagetop-bootsier/assets/_bootsier-icons.scss +++ /dev/null @@ -1,5 +0,0 @@ -// Bootstrap Icons v1.13.1, fonts served from /bootsier/fonts. - -$bootstrap-icons-font: "bootsier.icons"; -$bootstrap-icons-font-dir: "/bootsier/fonts"; -@import "bootstrap-icons-1.13.1/bootstrap-icons"; diff --git a/extensions/pagetop-bootsier/assets/_bootsier-variables.scss b/extensions/pagetop-bootsier/assets/_bootsier-variables.scss deleted file mode 100644 index 888e7450..00000000 --- a/extensions/pagetop-bootsier/assets/_bootsier-variables.scss +++ /dev/null @@ -1,4 +0,0 @@ -// Bootsier overrides for Bootstrap variables. Imported before bootstrap/scss/variables -// so that Bootstrap's !default declarations do not override these values. - -$font-size-base: 1.125rem; diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/fonts/SourceSans3VF-Italic.otf.woff2 b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/fonts/SourceSans3VF-Italic.otf.woff2 deleted file mode 100644 index 4a160ff1..00000000 Binary files a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/fonts/SourceSans3VF-Italic.otf.woff2 and /dev/null differ diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/fonts/SourceSans3VF-Upright.otf.woff2 b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/fonts/SourceSans3VF-Upright.otf.woff2 deleted file mode 100644 index d1537d81..00000000 Binary files a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/fonts/SourceSans3VF-Upright.otf.woff2 and /dev/null differ diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/js/adminlte.js b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/js/adminlte.js deleted file mode 100644 index 7ce13dff..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/js/adminlte.js +++ /dev/null @@ -1,1081 +0,0 @@ -/*! - * AdminLTE v4.0.0 (https://adminlte.io) - * Copyright 2014-2026 Colorlib - * Licensed under MIT (https://github.com/ColorlibHQ/AdminLTE/blob/master/LICENSE) - */ -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : - typeof define === 'function' && define.amd ? define(['exports'], factory) : - (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.adminlte = {})); -})(this, (function (exports) { 'use strict'; - - const domContentLoadedCallbacks = []; - const onDOMContentLoaded = (callback) => { - if (document.readyState === 'loading') { - if (!domContentLoadedCallbacks.length) { - document.addEventListener('DOMContentLoaded', () => { - for (const callback of domContentLoadedCallbacks) { - callback(); - } - }); - } - domContentLoadedCallbacks.push(callback); - } - else { - callback(); - } - }; - const slideUp = (target, duration = 500) => { - if (duration <= 1) { - target.style.display = 'none'; - return; - } - target.style.transitionProperty = 'height, margin, padding'; - target.style.transitionDuration = `${duration}ms`; - target.style.boxSizing = 'border-box'; - target.style.height = `${target.offsetHeight}px`; - target.style.overflow = 'hidden'; - globalThis.setTimeout(() => { - target.style.height = '0'; - target.style.paddingTop = '0'; - target.style.paddingBottom = '0'; - target.style.marginTop = '0'; - target.style.marginBottom = '0'; - }, 1); - globalThis.setTimeout(() => { - target.style.display = 'none'; - target.style.removeProperty('height'); - target.style.removeProperty('padding-top'); - target.style.removeProperty('padding-bottom'); - target.style.removeProperty('margin-top'); - target.style.removeProperty('margin-bottom'); - target.style.removeProperty('overflow'); - target.style.removeProperty('transition-duration'); - target.style.removeProperty('transition-property'); - }, duration); - }; - const slideDown = (target, duration = 500) => { - target.style.removeProperty('display'); - let { display } = globalThis.getComputedStyle(target); - if (display === 'none') { - display = 'block'; - } - target.style.display = display; - if (duration <= 1) { - return; - } - const height = target.offsetHeight; - target.style.overflow = 'hidden'; - target.style.height = '0'; - target.style.paddingTop = '0'; - target.style.paddingBottom = '0'; - target.style.marginTop = '0'; - target.style.marginBottom = '0'; - globalThis.setTimeout(() => { - target.style.boxSizing = 'border-box'; - target.style.transitionProperty = 'height, margin, padding'; - target.style.transitionDuration = `${duration}ms`; - target.style.height = `${height}px`; - target.style.removeProperty('padding-top'); - target.style.removeProperty('padding-bottom'); - target.style.removeProperty('margin-top'); - target.style.removeProperty('margin-bottom'); - }, 1); - globalThis.setTimeout(() => { - target.style.removeProperty('height'); - target.style.removeProperty('overflow'); - target.style.removeProperty('transition-duration'); - target.style.removeProperty('transition-property'); - }, duration); - }; - - const CLASS_NAME_HOLD_TRANSITIONS = 'hold-transition'; - const CLASS_NAME_APP_LOADED = 'app-loaded'; - class Layout { - _element; - _holdTransitionTimer; - constructor(element) { - this._element = element; - this._holdTransitionTimer = undefined; - } - holdTransition(time = 100) { - if (this._holdTransitionTimer) { - clearTimeout(this._holdTransitionTimer); - } - document.body.classList.add(CLASS_NAME_HOLD_TRANSITIONS); - this._holdTransitionTimer = setTimeout(() => { - document.body.classList.remove(CLASS_NAME_HOLD_TRANSITIONS); - }, time); - } - } - onDOMContentLoaded(() => { - const layout = new Layout(document.body); - window.addEventListener('resize', () => layout.holdTransition(200)); - setTimeout(() => { - document.body.classList.add(CLASS_NAME_APP_LOADED); - }, 400); - }); - - const DATA_KEY$4 = 'lte.card-widget'; - const EVENT_KEY$4 = `.${DATA_KEY$4}`; - const EVENT_COLLAPSED$2 = `collapsed${EVENT_KEY$4}`; - const EVENT_EXPANDED$2 = `expanded${EVENT_KEY$4}`; - const EVENT_REMOVE = `remove${EVENT_KEY$4}`; - const EVENT_MAXIMIZED$1 = `maximized${EVENT_KEY$4}`; - const EVENT_MINIMIZED$1 = `minimized${EVENT_KEY$4}`; - const CLASS_NAME_CARD = 'card'; - const CLASS_NAME_COLLAPSED = 'collapsed-card'; - const CLASS_NAME_COLLAPSING = 'collapsing-card'; - const CLASS_NAME_EXPANDING = 'expanding-card'; - const CLASS_NAME_WAS_COLLAPSED = 'was-collapsed'; - const CLASS_NAME_MAXIMIZED = 'maximized-card'; - const SELECTOR_DATA_REMOVE = '[data-lte-toggle="card-remove"]'; - const SELECTOR_DATA_COLLAPSE = '[data-lte-toggle="card-collapse"]'; - const SELECTOR_DATA_MAXIMIZE = '[data-lte-toggle="card-maximize"]'; - const SELECTOR_CARD = `.${CLASS_NAME_CARD}`; - const SELECTOR_CARD_BODY = '.card-body'; - const SELECTOR_CARD_FOOTER = '.card-footer'; - const Default$1 = { - animationSpeed: 500, - collapseTrigger: SELECTOR_DATA_COLLAPSE, - removeTrigger: SELECTOR_DATA_REMOVE, - maximizeTrigger: SELECTOR_DATA_MAXIMIZE - }; - class CardWidget { - _element; - _parent; - _clone; - _config; - constructor(element, config) { - this._element = element; - this._parent = element.closest(SELECTOR_CARD); - if (element.classList.contains(CLASS_NAME_CARD)) { - this._parent = element; - } - this._config = { ...Default$1, ...config }; - } - collapse() { - const event = new Event(EVENT_COLLAPSED$2); - if (this._parent) { - this._parent.classList.add(CLASS_NAME_COLLAPSING); - const elm = this._parent?.querySelectorAll(`:scope > ${SELECTOR_CARD_BODY}, :scope > ${SELECTOR_CARD_FOOTER}`); - elm.forEach(el => { - if (el instanceof HTMLElement) { - slideUp(el, this._config.animationSpeed); - } - }); - setTimeout(() => { - if (this._parent) { - this._parent.classList.add(CLASS_NAME_COLLAPSED); - this._parent.classList.remove(CLASS_NAME_COLLAPSING); - } - }, this._config.animationSpeed); - } - this._element?.dispatchEvent(event); - } - expand() { - const event = new Event(EVENT_EXPANDED$2); - if (this._parent) { - this._parent.classList.add(CLASS_NAME_EXPANDING); - const elm = this._parent?.querySelectorAll(`:scope > ${SELECTOR_CARD_BODY}, :scope > ${SELECTOR_CARD_FOOTER}`); - elm.forEach(el => { - if (el instanceof HTMLElement) { - slideDown(el, this._config.animationSpeed); - } - }); - setTimeout(() => { - if (this._parent) { - this._parent.classList.remove(CLASS_NAME_COLLAPSED, CLASS_NAME_EXPANDING); - } - }, this._config.animationSpeed); - } - this._element?.dispatchEvent(event); - } - remove() { - const event = new Event(EVENT_REMOVE); - if (this._parent) { - slideUp(this._parent, this._config.animationSpeed); - } - this._element?.dispatchEvent(event); - } - toggle() { - if (this._parent?.classList.contains(CLASS_NAME_COLLAPSED)) { - this.expand(); - return; - } - this.collapse(); - } - maximize() { - const event = new Event(EVENT_MAXIMIZED$1); - if (this._parent) { - this._parent.style.height = `${this._parent.offsetHeight}px`; - this._parent.style.width = `${this._parent.offsetWidth}px`; - this._parent.style.transition = 'all .15s'; - setTimeout(() => { - const htmlTag = document.querySelector('html'); - if (htmlTag) { - htmlTag.classList.add(CLASS_NAME_MAXIMIZED); - } - if (this._parent) { - this._parent.classList.add(CLASS_NAME_MAXIMIZED); - if (this._parent.classList.contains(CLASS_NAME_COLLAPSED)) { - this._parent.classList.add(CLASS_NAME_WAS_COLLAPSED); - } - } - }, 150); - } - this._element?.dispatchEvent(event); - } - minimize() { - const event = new Event(EVENT_MINIMIZED$1); - if (this._parent) { - this._parent.style.height = 'auto'; - this._parent.style.width = 'auto'; - this._parent.style.transition = 'all .15s'; - setTimeout(() => { - const htmlTag = document.querySelector('html'); - if (htmlTag) { - htmlTag.classList.remove(CLASS_NAME_MAXIMIZED); - } - if (this._parent) { - this._parent.classList.remove(CLASS_NAME_MAXIMIZED); - if (this._parent?.classList.contains(CLASS_NAME_WAS_COLLAPSED)) { - this._parent.classList.remove(CLASS_NAME_WAS_COLLAPSED); - } - } - }, 10); - } - this._element?.dispatchEvent(event); - } - toggleMaximize() { - if (this._parent?.classList.contains(CLASS_NAME_MAXIMIZED)) { - this.minimize(); - return; - } - this.maximize(); - } - } - onDOMContentLoaded(() => { - const collapseBtn = document.querySelectorAll(SELECTOR_DATA_COLLAPSE); - collapseBtn.forEach(btn => { - btn.addEventListener('click', event => { - event.preventDefault(); - const target = event.target; - const data = new CardWidget(target, Default$1); - data.toggle(); - }); - }); - const removeBtn = document.querySelectorAll(SELECTOR_DATA_REMOVE); - removeBtn.forEach(btn => { - btn.addEventListener('click', event => { - event.preventDefault(); - const target = event.target; - const data = new CardWidget(target, Default$1); - data.remove(); - }); - }); - const maxBtn = document.querySelectorAll(SELECTOR_DATA_MAXIMIZE); - maxBtn.forEach(btn => { - btn.addEventListener('click', event => { - event.preventDefault(); - const target = event.target; - const data = new CardWidget(target, Default$1); - data.toggleMaximize(); - }); - }); - }); - - const DATA_KEY$3 = 'lte.treeview'; - const EVENT_KEY$3 = `.${DATA_KEY$3}`; - const EVENT_EXPANDED$1 = `expanded${EVENT_KEY$3}`; - const EVENT_COLLAPSED$1 = `collapsed${EVENT_KEY$3}`; - const EVENT_LOAD_DATA_API = `load${EVENT_KEY$3}`; - const CLASS_NAME_MENU_OPEN = 'menu-open'; - const SELECTOR_NAV_ITEM = '.nav-item'; - const SELECTOR_NAV_LINK = '.nav-link'; - const SELECTOR_TREEVIEW_MENU = '.nav-treeview'; - const SELECTOR_DATA_TOGGLE$1 = '[data-lte-toggle="treeview"]'; - const Default = { - animationSpeed: 300, - accordion: true - }; - class Treeview { - _element; - _config; - constructor(element, config) { - this._element = element; - this._config = { ...Default, ...config }; - } - open() { - const event = new Event(EVENT_EXPANDED$1); - if (this._config.accordion) { - const openMenuList = this._element.parentElement?.querySelectorAll(`${SELECTOR_NAV_ITEM}.${CLASS_NAME_MENU_OPEN}`); - openMenuList?.forEach(openMenu => { - if (openMenu !== this._element.parentElement) { - openMenu.classList.remove(CLASS_NAME_MENU_OPEN); - const childElement = openMenu?.querySelector(SELECTOR_TREEVIEW_MENU); - if (childElement) { - slideUp(childElement, this._config.animationSpeed); - } - } - }); - } - this._element.classList.add(CLASS_NAME_MENU_OPEN); - const childElement = this._element?.querySelector(SELECTOR_TREEVIEW_MENU); - if (childElement) { - slideDown(childElement, this._config.animationSpeed); - } - this._element.dispatchEvent(event); - } - close() { - const event = new Event(EVENT_COLLAPSED$1); - this._element.classList.remove(CLASS_NAME_MENU_OPEN); - const childElement = this._element?.querySelector(SELECTOR_TREEVIEW_MENU); - if (childElement) { - slideUp(childElement, this._config.animationSpeed); - } - this._element.dispatchEvent(event); - } - toggle() { - if (this._element.classList.contains(CLASS_NAME_MENU_OPEN)) { - this.close(); - } - else { - this.open(); - } - } - } - onDOMContentLoaded(() => { - const openMenuItems = document.querySelectorAll(`${SELECTOR_NAV_ITEM}.${CLASS_NAME_MENU_OPEN}`); - openMenuItems.forEach(menuItem => { - const childElement = menuItem.querySelector(SELECTOR_TREEVIEW_MENU); - if (childElement) { - slideDown(childElement, 0); - const event = new Event(EVENT_LOAD_DATA_API); - menuItem.dispatchEvent(event); - } - }); - const button = document.querySelectorAll(SELECTOR_DATA_TOGGLE$1); - button.forEach(btn => { - btn.addEventListener('click', event => { - const target = event.target; - const targetItem = target.closest(SELECTOR_NAV_ITEM); - const targetLink = target.closest(SELECTOR_NAV_LINK); - const targetTreeviewMenu = targetItem?.querySelector(SELECTOR_TREEVIEW_MENU); - const lteToggleElement = event.currentTarget; - if (!targetTreeviewMenu) { - return; - } - if (target?.getAttribute('href') === '#' || targetLink?.getAttribute('href') === '#') { - event.preventDefault(); - } - if (targetItem) { - const accordionAttr = lteToggleElement.dataset.accordion; - const animationSpeedAttr = lteToggleElement.dataset.animationSpeed; - const config = { - accordion: accordionAttr === undefined ? Default.accordion : accordionAttr === 'true', - animationSpeed: animationSpeedAttr === undefined ? Default.animationSpeed : Number(animationSpeedAttr) - }; - const data = new Treeview(targetItem, config); - data.toggle(); - } - }); - }); - }); - - const DATA_KEY$2 = 'lte.direct-chat'; - const EVENT_KEY$2 = `.${DATA_KEY$2}`; - const EVENT_EXPANDED = `expanded${EVENT_KEY$2}`; - const EVENT_COLLAPSED = `collapsed${EVENT_KEY$2}`; - const SELECTOR_DATA_TOGGLE = '[data-lte-toggle="chat-pane"]'; - const SELECTOR_DIRECT_CHAT = '.direct-chat'; - const CLASS_NAME_DIRECT_CHAT_OPEN = 'direct-chat-contacts-open'; - class DirectChat { - _element; - constructor(element) { - this._element = element; - } - toggle() { - if (this._element.classList.contains(CLASS_NAME_DIRECT_CHAT_OPEN)) { - const event = new Event(EVENT_COLLAPSED); - this._element.classList.remove(CLASS_NAME_DIRECT_CHAT_OPEN); - this._element.dispatchEvent(event); - } - else { - const event = new Event(EVENT_EXPANDED); - this._element.classList.add(CLASS_NAME_DIRECT_CHAT_OPEN); - this._element.dispatchEvent(event); - } - } - } - onDOMContentLoaded(() => { - const button = document.querySelectorAll(SELECTOR_DATA_TOGGLE); - button.forEach(btn => { - btn.addEventListener('click', event => { - event.preventDefault(); - const target = event.target; - const chatPane = target.closest(SELECTOR_DIRECT_CHAT); - if (chatPane) { - const data = new DirectChat(chatPane); - data.toggle(); - } - }); - }); - }); - - const DATA_KEY$1 = 'lte.fullscreen'; - const EVENT_KEY$1 = `.${DATA_KEY$1}`; - const EVENT_MAXIMIZED = `maximized${EVENT_KEY$1}`; - const EVENT_MINIMIZED = `minimized${EVENT_KEY$1}`; - const SELECTOR_FULLSCREEN_TOGGLE = '[data-lte-toggle="fullscreen"]'; - const SELECTOR_MAXIMIZE_ICON = '[data-lte-icon="maximize"]'; - const SELECTOR_MINIMIZE_ICON = '[data-lte-icon="minimize"]'; - class FullScreen { - _element; - _config; - constructor(element, config) { - this._element = element; - this._config = config; - } - inFullScreen() { - const event = new Event(EVENT_MAXIMIZED); - const iconMaximize = document.querySelector(SELECTOR_MAXIMIZE_ICON); - const iconMinimize = document.querySelector(SELECTOR_MINIMIZE_ICON); - void document.documentElement.requestFullscreen(); - if (iconMaximize) { - iconMaximize.classList.add('d-none'); - } - if (iconMinimize) { - iconMinimize.classList.remove('d-none'); - } - this._element.dispatchEvent(event); - } - outFullscreen() { - const event = new Event(EVENT_MINIMIZED); - const iconMaximize = document.querySelector(SELECTOR_MAXIMIZE_ICON); - const iconMinimize = document.querySelector(SELECTOR_MINIMIZE_ICON); - void document.exitFullscreen(); - if (iconMaximize) { - iconMaximize.classList.remove('d-none'); - } - if (iconMinimize) { - iconMinimize.classList.add('d-none'); - } - this._element.dispatchEvent(event); - } - toggleFullScreen() { - if (document.fullscreenEnabled) { - if (document.fullscreenElement) { - this.outFullscreen(); - } - else { - this.inFullScreen(); - } - } - } - } - onDOMContentLoaded(() => { - const buttons = document.querySelectorAll(SELECTOR_FULLSCREEN_TOGGLE); - buttons.forEach(btn => { - btn.addEventListener('click', event => { - event.preventDefault(); - const target = event.target; - const button = target.closest(SELECTOR_FULLSCREEN_TOGGLE); - if (button) { - const data = new FullScreen(button, undefined); - data.toggleFullScreen(); - } - }); - }); - }); - - const DATA_KEY = 'lte.push-menu'; - const EVENT_KEY = `.${DATA_KEY}`; - const EVENT_OPEN = `open${EVENT_KEY}`; - const EVENT_COLLAPSE = `collapse${EVENT_KEY}`; - const CLASS_NAME_SIDEBAR_MINI = 'sidebar-mini'; - const CLASS_NAME_SIDEBAR_EXPAND = 'sidebar-expand'; - const CLASS_NAME_SIDEBAR_OVERLAY = 'sidebar-overlay'; - const CLASS_NAME_SIDEBAR_COLLAPSE = 'sidebar-collapse'; - const CLASS_NAME_SIDEBAR_OPEN = 'sidebar-open'; - const SELECTOR_APP_SIDEBAR = '.app-sidebar'; - const SELECTOR_APP_WRAPPER = '.app-wrapper'; - const SELECTOR_SIDEBAR_EXPAND = `[class*="${CLASS_NAME_SIDEBAR_EXPAND}"]`; - const SELECTOR_SIDEBAR_TOGGLE = '[data-lte-toggle="sidebar"]'; - const STORAGE_KEY_SIDEBAR_STATE = 'lte.sidebar.state'; - const Defaults = { - sidebarBreakpoint: 992, - enablePersistence: false - }; - class PushMenu { - _element; - _config; - constructor(element, config) { - this._element = element; - this._config = { ...Defaults, ...config }; - } - isCollapsed() { - return document.body.classList.contains(CLASS_NAME_SIDEBAR_COLLAPSE); - } - isExplicitlyOpen() { - return document.body.classList.contains(CLASS_NAME_SIDEBAR_OPEN); - } - isMiniMode() { - return document.body.classList.contains(CLASS_NAME_SIDEBAR_MINI); - } - isMobileSize() { - return globalThis.innerWidth <= this._config.sidebarBreakpoint; - } - expand() { - document.body.classList.remove(CLASS_NAME_SIDEBAR_COLLAPSE); - if (this.isMobileSize()) { - document.body.classList.add(CLASS_NAME_SIDEBAR_OPEN); - } - this._element.dispatchEvent(new Event(EVENT_OPEN)); - } - collapse() { - document.body.classList.remove(CLASS_NAME_SIDEBAR_OPEN); - document.body.classList.add(CLASS_NAME_SIDEBAR_COLLAPSE); - this._element.dispatchEvent(new Event(EVENT_COLLAPSE)); - } - toggle() { - const isCollapsed = this.isCollapsed(); - if (isCollapsed) { - this.expand(); - } - else { - this.collapse(); - } - if (this._config.enablePersistence) { - this.saveSidebarState(isCollapsed ? CLASS_NAME_SIDEBAR_OPEN : CLASS_NAME_SIDEBAR_COLLAPSE); - } - } - setupSidebarBreakPoint() { - const sidebarExpand = document.querySelector(SELECTOR_SIDEBAR_EXPAND); - if (!sidebarExpand) { - return; - } - const content = globalThis.getComputedStyle(sidebarExpand, '::before') - .getPropertyValue('content'); - if (!content || content === 'none') { - return; - } - const breakpointValue = Number(content.replace(/[^\d.-]/g, '')); - if (Number.isNaN(breakpointValue)) { - return; - } - this._config = { ...this._config, sidebarBreakpoint: breakpointValue }; - } - updateStateByResponsiveLogic() { - if (this.isMobileSize()) { - if (!this.isExplicitlyOpen()) { - this.collapse(); - } - } - else { - if (!(this.isMiniMode() && this.isCollapsed())) { - this.expand(); - } - } - } - saveSidebarState(state) { - if (globalThis.localStorage === undefined) { - return; - } - try { - localStorage.setItem(STORAGE_KEY_SIDEBAR_STATE, state); - } - catch { - } - } - loadSidebarState() { - if (globalThis.localStorage === undefined) { - return; - } - try { - const storedState = localStorage.getItem(STORAGE_KEY_SIDEBAR_STATE); - if (storedState === CLASS_NAME_SIDEBAR_COLLAPSE) { - this.collapse(); - } - else if (storedState === CLASS_NAME_SIDEBAR_OPEN) { - this.expand(); - } - else { - this.updateStateByResponsiveLogic(); - } - } - catch { - this.updateStateByResponsiveLogic(); - } - } - clearSidebarState() { - if (globalThis.localStorage === undefined) { - return; - } - try { - localStorage.removeItem(STORAGE_KEY_SIDEBAR_STATE); - } - catch { - } - } - init() { - this.setupSidebarBreakPoint(); - if (!this._config.enablePersistence) { - this.clearSidebarState(); - } - if (this._config.enablePersistence && !this.isMobileSize()) { - this.loadSidebarState(); - } - else { - this.updateStateByResponsiveLogic(); - } - } - } - onDOMContentLoaded(() => { - const sidebar = document?.querySelector(SELECTOR_APP_SIDEBAR); - if (!sidebar) { - return; - } - const sidebarBreakpointAttr = sidebar.dataset.sidebarBreakpoint; - const enablePersistenceAttr = sidebar.dataset.enablePersistence; - const config = { - sidebarBreakpoint: sidebarBreakpointAttr === undefined ? - Defaults.sidebarBreakpoint : - Number(sidebarBreakpointAttr), - enablePersistence: enablePersistenceAttr === undefined ? - Defaults.enablePersistence : - enablePersistenceAttr === 'true' - }; - const pushMenu = new PushMenu(sidebar, config); - pushMenu.init(); - window.addEventListener('resize', () => { - pushMenu.setupSidebarBreakPoint(); - pushMenu.updateStateByResponsiveLogic(); - }); - const sidebarOverlay = document.createElement('div'); - sidebarOverlay.className = CLASS_NAME_SIDEBAR_OVERLAY; - document.querySelector(SELECTOR_APP_WRAPPER)?.append(sidebarOverlay); - let overlayTouchMoved = false; - sidebarOverlay.addEventListener('touchstart', () => { - overlayTouchMoved = false; - }, { passive: true }); - sidebarOverlay.addEventListener('touchmove', () => { - overlayTouchMoved = true; - }, { passive: true }); - sidebarOverlay.addEventListener('touchend', event => { - if (!overlayTouchMoved) { - event.preventDefault(); - pushMenu.collapse(); - } - overlayTouchMoved = false; - }, { passive: false }); - sidebarOverlay.addEventListener('click', event => { - event.preventDefault(); - pushMenu.collapse(); - }); - const fullBtn = document.querySelectorAll(SELECTOR_SIDEBAR_TOGGLE); - fullBtn.forEach(btn => { - btn.addEventListener('click', event => { - event.preventDefault(); - let button = event.currentTarget; - if (button?.dataset.lteToggle !== 'sidebar') { - button = button?.closest(SELECTOR_SIDEBAR_TOGGLE); - } - if (button) { - event?.preventDefault(); - pushMenu.toggle(); - } - }); - }); - }); - - class AccessibilityManager { - config; - liveRegion = null; - focusHistory = []; - constructor(config = {}) { - this.config = { - announcements: true, - skipLinks: true, - focusManagement: true, - keyboardNavigation: true, - reducedMotion: true, - ...config - }; - this.init(); - } - init() { - if (this.config.announcements) { - this.createLiveRegion(); - } - if (this.config.skipLinks) { - this.addSkipLinks(); - } - if (this.config.focusManagement) { - this.initFocusManagement(); - } - if (this.config.keyboardNavigation) { - this.initKeyboardNavigation(); - } - if (this.config.reducedMotion) { - this.respectReducedMotion(); - } - this.initErrorAnnouncements(); - this.initTableAccessibility(); - this.initFormAccessibility(); - } - createLiveRegion() { - if (this.liveRegion) - return; - this.liveRegion = document.createElement('div'); - this.liveRegion.id = 'live-region'; - this.liveRegion.className = 'live-region'; - this.liveRegion.setAttribute('aria-live', 'polite'); - this.liveRegion.setAttribute('aria-atomic', 'true'); - this.liveRegion.setAttribute('role', 'status'); - document.body.append(this.liveRegion); - } - addSkipLinks() { - const skipLinksContainer = document.createElement('div'); - skipLinksContainer.className = 'skip-links'; - const skipToMain = document.createElement('a'); - skipToMain.href = '#main'; - skipToMain.className = 'skip-link'; - skipToMain.textContent = 'Skip to main content'; - const skipToNav = document.createElement('a'); - skipToNav.href = '#navigation'; - skipToNav.className = 'skip-link'; - skipToNav.textContent = 'Skip to navigation'; - skipLinksContainer.append(skipToMain); - skipLinksContainer.append(skipToNav); - document.body.insertBefore(skipLinksContainer, document.body.firstChild); - this.ensureSkipTargets(); - } - ensureSkipTargets() { - const main = document.querySelector('#main, main, [role="main"]'); - if (main && !main.id) { - main.id = 'main'; - } - if (main && !main.hasAttribute('tabindex')) { - main.setAttribute('tabindex', '-1'); - } - const nav = document.querySelector('#navigation, nav, [role="navigation"]'); - if (nav && !nav.id) { - nav.id = 'navigation'; - } - if (nav && !nav.hasAttribute('tabindex')) { - nav.setAttribute('tabindex', '-1'); - } - } - initFocusManagement() { - document.addEventListener('keydown', (event) => { - if (event.key === 'Tab') { - this.handleTabNavigation(event); - } - if (event.key === 'Escape') { - this.handleEscapeKey(event); - } - }); - this.initModalFocusManagement(); - this.initDropdownFocusManagement(); - } - handleTabNavigation(event) { - const focusableElements = this.getFocusableElements(); - const currentIndex = focusableElements.indexOf(document.activeElement); - if (event.shiftKey) { - if (currentIndex <= 0) { - event.preventDefault(); - focusableElements.at(-1)?.focus(); - } - } - else if (currentIndex >= focusableElements.length - 1) { - event.preventDefault(); - focusableElements[0]?.focus(); - } - } - getFocusableElements() { - const selector = [ - 'a[href]', - 'button:not([disabled])', - 'input:not([disabled])', - 'select:not([disabled])', - 'textarea:not([disabled])', - '[tabindex]:not([tabindex="-1"])', - '[contenteditable="true"]' - ].join(', '); - return Array.from(document.querySelectorAll(selector)); - } - handleEscapeKey(event) { - const activeModal = document.querySelector('.modal.show'); - if (activeModal) { - return; - } - const activeDropdown = document.querySelector('.dropdown-menu.show'); - if (activeDropdown) { - const toggleButton = document.querySelector('[data-bs-toggle="dropdown"][aria-expanded="true"]'); - toggleButton?.click(); - event.preventDefault(); - } - } - initKeyboardNavigation() { - document.addEventListener('keydown', (event) => { - const target = event.target; - if (target.closest('.nav, .navbar-nav, .dropdown-menu')) { - this.handleMenuNavigation(event); - } - if ((event.key === 'Enter' || event.key === ' ') && target.hasAttribute('role') && target.getAttribute('role') === 'button' && !target.matches('button, input[type="button"], input[type="submit"]')) { - event.preventDefault(); - target.click(); - } - }); - } - handleMenuNavigation(event) { - if (!['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(event.key)) { - return; - } - const currentElement = event.target; - const menuItems = Array.from(currentElement.closest('.nav, .navbar-nav, .dropdown-menu')?.querySelectorAll('a, button') || []); - const currentIndex = menuItems.indexOf(currentElement); - let nextIndex; - switch (event.key) { - case 'ArrowDown': - case 'ArrowRight': { - nextIndex = currentIndex < menuItems.length - 1 ? currentIndex + 1 : 0; - break; - } - case 'ArrowUp': - case 'ArrowLeft': { - nextIndex = currentIndex > 0 ? currentIndex - 1 : menuItems.length - 1; - break; - } - case 'Home': { - nextIndex = 0; - break; - } - case 'End': { - nextIndex = menuItems.length - 1; - break; - } - default: { - return; - } - } - event.preventDefault(); - menuItems[nextIndex]?.focus(); - } - respectReducedMotion() { - const prefersReducedMotion = globalThis.matchMedia('(prefers-reduced-motion: reduce)').matches; - if (prefersReducedMotion) { - document.body.classList.add('reduce-motion'); - document.documentElement.style.scrollBehavior = 'auto'; - const style = document.createElement('style'); - style.textContent = ` - *, *::before, *::after { - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; - } - `; - document.head.append(style); - } - } - initErrorAnnouncements() { - const observer = new MutationObserver((mutations) => { - mutations.forEach((mutation) => { - mutation.addedNodes.forEach((node) => { - if (node.nodeType === Node.ELEMENT_NODE) { - const element = node; - if (element.matches('.alert-danger, .invalid-feedback, .error')) { - this.announce(element.textContent || 'Error occurred', 'assertive'); - } - if (element.matches('.alert-success, .success')) { - this.announce(element.textContent || 'Success', 'polite'); - } - } - }); - }); - }); - observer.observe(document.body, { - childList: true, - subtree: true - }); - } - initTableAccessibility() { - document.querySelectorAll('table').forEach((table) => { - if (!table.hasAttribute('role')) { - table.setAttribute('role', 'table'); - } - table.querySelectorAll('th').forEach((th) => { - if (!th.hasAttribute('scope')) { - const isInThead = th.closest('thead'); - const isFirstColumn = th.cellIndex === 0; - if (isInThead) { - th.setAttribute('scope', 'col'); - } - else if (isFirstColumn) { - th.setAttribute('scope', 'row'); - } - } - }); - if (!table.querySelector('caption') && table.hasAttribute('title')) { - const caption = document.createElement('caption'); - caption.textContent = table.getAttribute('title') || ''; - table.insertBefore(caption, table.firstChild); - } - }); - } - initFormAccessibility() { - document.querySelectorAll('input, select, textarea').forEach((input) => { - const htmlInput = input; - if (!htmlInput.labels?.length && !htmlInput.hasAttribute('aria-label') && !htmlInput.hasAttribute('aria-labelledby')) { - const placeholder = htmlInput.getAttribute('placeholder'); - if (placeholder) { - htmlInput.setAttribute('aria-label', placeholder); - } - } - if (htmlInput.hasAttribute('required')) { - const label = htmlInput.labels?.[0]; - if (label && !label.querySelector('.required-indicator')) { - const indicator = document.createElement('span'); - indicator.className = 'required-indicator sr-only'; - indicator.textContent = ' (required)'; - label.append(indicator); - } - } - if (!htmlInput.classList.contains('disable-adminlte-validations')) { - htmlInput.addEventListener('invalid', () => { - this.handleFormError(htmlInput); - }); - } - }); - } - handleFormError(input) { - const errorId = `${input.id || input.name}-error`; - let errorElement = document.getElementById(errorId); - if (!errorElement) { - errorElement = document.createElement('div'); - errorElement.id = errorId; - errorElement.className = 'invalid-feedback'; - errorElement.setAttribute('role', 'alert'); - input.parentNode?.append(errorElement); - } - errorElement.textContent = input.validationMessage; - input.setAttribute('aria-describedby', errorId); - input.classList.add('is-invalid'); - this.announce(`Error in ${input.labels?.[0]?.textContent || input.name}: ${input.validationMessage}`, 'assertive'); - } - initModalFocusManagement() { - document.addEventListener('shown.bs.modal', (event) => { - const modal = event.target; - const focusableElements = modal.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'); - if (focusableElements.length > 0) { - focusableElements[0].focus(); - } - this.focusHistory.push(document.activeElement); - }); - document.addEventListener('hidden.bs.modal', () => { - const previousElement = this.focusHistory.pop(); - if (previousElement) { - previousElement.focus(); - } - }); - } - initDropdownFocusManagement() { - document.addEventListener('shown.bs.dropdown', (event) => { - const dropdown = event.target; - const menu = dropdown.querySelector('.dropdown-menu'); - const firstItem = menu?.querySelector('a, button'); - if (firstItem) { - firstItem.focus(); - } - }); - } - announce(message, priority = 'polite') { - if (!this.liveRegion) { - this.createLiveRegion(); - } - if (this.liveRegion) { - this.liveRegion.setAttribute('aria-live', priority); - this.liveRegion.textContent = message; - setTimeout(() => { - if (this.liveRegion) { - this.liveRegion.textContent = ''; - } - }, 1000); - } - } - focusElement(selector) { - const element = document.querySelector(selector); - if (element) { - element.focus(); - element.scrollIntoView({ behavior: 'smooth', block: 'center' }); - } - } - trapFocus(container) { - const focusableElements = container.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'); - const focusableArray = Array.from(focusableElements); - const firstElement = focusableArray[0]; - const lastElement = focusableArray.at(-1); - container.addEventListener('keydown', (event) => { - if (event.key === 'Tab') { - if (event.shiftKey) { - if (document.activeElement === firstElement) { - lastElement?.focus(); - event.preventDefault(); - } - } - else if (document.activeElement === lastElement) { - firstElement.focus(); - event.preventDefault(); - } - } - }); - } - addLandmarks() { - const main = document.querySelector('main'); - if (!main) { - const appMain = document.querySelector('.app-main'); - if (appMain) { - appMain.setAttribute('role', 'main'); - appMain.id = 'main'; - } - } - document.querySelectorAll('.navbar-nav, .nav').forEach((nav, index) => { - if (!nav.hasAttribute('role')) { - nav.setAttribute('role', 'navigation'); - } - if (!nav.hasAttribute('aria-label')) { - nav.setAttribute('aria-label', `Navigation ${index + 1}`); - } - }); - const searchForm = document.querySelector('form[role="search"], .navbar-search'); - if (searchForm && !searchForm.hasAttribute('role')) { - searchForm.setAttribute('role', 'search'); - } - } - } - const initAccessibility = (config) => { - return new AccessibilityManager(config); - }; - - onDOMContentLoaded(() => { - const accessibilityManager = initAccessibility({ - announcements: true, - skipLinks: true, - focusManagement: true, - keyboardNavigation: true, - reducedMotion: true - }); - accessibilityManager.addLandmarks(); - }); - - exports.CardWidget = CardWidget; - exports.DirectChat = DirectChat; - exports.FullScreen = FullScreen; - exports.Layout = Layout; - exports.PushMenu = PushMenu; - exports.Treeview = Treeview; - exports.initAccessibility = initAccessibility; - -})); -//# sourceMappingURL=adminlte.js.map diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/js/adminlte.js.map b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/js/adminlte.js.map deleted file mode 100644 index 4344c78f..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/js/adminlte.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"adminlte.js","sources":["../../src/ts/util/index.ts","../../src/ts/layout.ts","../../src/ts/card-widget.ts","../../src/ts/treeview.ts","../../src/ts/direct-chat.ts","../../src/ts/fullscreen.ts","../../src/ts/push-menu.ts","../../src/ts/accessibility.ts","../../src/ts/adminlte.ts"],"sourcesContent":[null,null,null,null,null,null,null,null,null],"names":["DATA_KEY","EVENT_KEY","EVENT_COLLAPSED","EVENT_EXPANDED","EVENT_MAXIMIZED","EVENT_MINIMIZED","Default","SELECTOR_DATA_TOGGLE"],"mappings":"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"} \ No newline at end of file diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/js/adminlte.min.js b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/js/adminlte.min.js deleted file mode 100644 index 9182ae35..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/js/adminlte.min.js +++ /dev/null @@ -1,7 +0,0 @@ -/*! - * AdminLTE v4.0.0 (https://adminlte.io) - * Copyright 2014-2026 Colorlib - * Licensed under MIT (https://github.com/ColorlibHQ/AdminLTE/blob/master/LICENSE) - */ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).adminlte={})}(this,function(e){"use strict";const t=[],n=e=>{"loading"===document.readyState?(t.length||document.addEventListener("DOMContentLoaded",()=>{for(const e of t)e()}),t.push(e)):e()},i=(e,t=500)=>{t<=1?e.style.display="none":(e.style.transitionProperty="height, margin, padding",e.style.transitionDuration=`${t}ms`,e.style.boxSizing="border-box",e.style.height=`${e.offsetHeight}px`,e.style.overflow="hidden",globalThis.setTimeout(()=>{e.style.height="0",e.style.paddingTop="0",e.style.paddingBottom="0",e.style.marginTop="0",e.style.marginBottom="0"},1),globalThis.setTimeout(()=>{e.style.display="none",e.style.removeProperty("height"),e.style.removeProperty("padding-top"),e.style.removeProperty("padding-bottom"),e.style.removeProperty("margin-top"),e.style.removeProperty("margin-bottom"),e.style.removeProperty("overflow"),e.style.removeProperty("transition-duration"),e.style.removeProperty("transition-property")},t))},o=(e,t=500)=>{e.style.removeProperty("display");let{display:n}=globalThis.getComputedStyle(e);if("none"===n&&(n="block"),e.style.display=n,t<=1)return;const i=e.offsetHeight;e.style.overflow="hidden",e.style.height="0",e.style.paddingTop="0",e.style.paddingBottom="0",e.style.marginTop="0",e.style.marginBottom="0",globalThis.setTimeout(()=>{e.style.boxSizing="border-box",e.style.transitionProperty="height, margin, padding",e.style.transitionDuration=`${t}ms`,e.style.height=`${i}px`,e.style.removeProperty("padding-top"),e.style.removeProperty("padding-bottom"),e.style.removeProperty("margin-top"),e.style.removeProperty("margin-bottom")},1),globalThis.setTimeout(()=>{e.style.removeProperty("height"),e.style.removeProperty("overflow"),e.style.removeProperty("transition-duration"),e.style.removeProperty("transition-property")},t)},s="hold-transition";class a{_element;_holdTransitionTimer;constructor(e){this._element=e,this._holdTransitionTimer=void 0}holdTransition(e=100){this._holdTransitionTimer&&clearTimeout(this._holdTransitionTimer),document.body.classList.add(s),this._holdTransitionTimer=setTimeout(()=>{document.body.classList.remove(s)},e)}}n(()=>{const e=new a(document.body);window.addEventListener("resize",()=>e.holdTransition(200)),setTimeout(()=>{document.body.classList.add("app-loaded")},400)});const r=".lte.card-widget",l=`collapsed${r}`,c=`expanded${r}`,d=`remove${r}`,u=`maximized${r}`,m=`minimized${r}`,h="card",p="collapsed-card",g="collapsing-card",v="expanding-card",y="was-collapsed",b="maximized-card",f='[data-lte-toggle="card-remove"]',E='[data-lte-toggle="card-collapse"]',S='[data-lte-toggle="card-maximize"]',_=`.${h}`,L=".card-body",A=".card-footer",w={animationSpeed:500,collapseTrigger:E,removeTrigger:f,maximizeTrigger:S};class x{_element;_parent;_clone;_config;constructor(e,t){this._element=e,this._parent=e.closest(_),e.classList.contains(h)&&(this._parent=e),this._config={...w,...t}}collapse(){const e=new Event(l);if(this._parent){this._parent.classList.add(g);const e=this._parent?.querySelectorAll(`:scope > ${L}, :scope > ${A}`);e.forEach(e=>{e instanceof HTMLElement&&i(e,this._config.animationSpeed)}),setTimeout(()=>{this._parent&&(this._parent.classList.add(p),this._parent.classList.remove(g))},this._config.animationSpeed)}this._element?.dispatchEvent(e)}expand(){const e=new Event(c);if(this._parent){this._parent.classList.add(v);const e=this._parent?.querySelectorAll(`:scope > ${L}, :scope > ${A}`);e.forEach(e=>{e instanceof HTMLElement&&o(e,this._config.animationSpeed)}),setTimeout(()=>{this._parent&&this._parent.classList.remove(p,v)},this._config.animationSpeed)}this._element?.dispatchEvent(e)}remove(){const e=new Event(d);this._parent&&i(this._parent,this._config.animationSpeed),this._element?.dispatchEvent(e)}toggle(){this._parent?.classList.contains(p)?this.expand():this.collapse()}maximize(){const e=new Event(u);this._parent&&(this._parent.style.height=`${this._parent.offsetHeight}px`,this._parent.style.width=`${this._parent.offsetWidth}px`,this._parent.style.transition="all .15s",setTimeout(()=>{const e=document.querySelector("html");e&&e.classList.add(b),this._parent&&(this._parent.classList.add(b),this._parent.classList.contains(p)&&this._parent.classList.add(y))},150)),this._element?.dispatchEvent(e)}minimize(){const e=new Event(m);this._parent&&(this._parent.style.height="auto",this._parent.style.width="auto",this._parent.style.transition="all .15s",setTimeout(()=>{const e=document.querySelector("html");e&&e.classList.remove(b),this._parent&&(this._parent.classList.remove(b),this._parent?.classList.contains(y)&&this._parent.classList.remove(y))},10)),this._element?.dispatchEvent(e)}toggleMaximize(){this._parent?.classList.contains(b)?this.minimize():this.maximize()}}n(()=>{document.querySelectorAll(E).forEach(e=>{e.addEventListener("click",e=>{e.preventDefault();const t=e.target;new x(t,w).toggle()})}),document.querySelectorAll(f).forEach(e=>{e.addEventListener("click",e=>{e.preventDefault();const t=e.target;new x(t,w).remove()})}),document.querySelectorAll(S).forEach(e=>{e.addEventListener("click",e=>{e.preventDefault();const t=e.target;new x(t,w).toggleMaximize()})})});const k=".lte.treeview",T=`expanded${k}`,q=`collapsed${k}`,M=`load${k}`,$="menu-open",P=".nav-item",N=".nav-treeview",R={animationSpeed:300,accordion:!0};class D{_element;_config;constructor(e,t){this._element=e,this._config={...R,...t}}open(){const e=new Event(T);if(this._config.accordion){const e=this._element.parentElement?.querySelectorAll(`${P}.${$}`);e?.forEach(e=>{if(e!==this._element.parentElement){e.classList.remove($);const t=e?.querySelector(N);t&&i(t,this._config.animationSpeed)}})}this._element.classList.add($);const t=this._element?.querySelector(N);t&&o(t,this._config.animationSpeed),this._element.dispatchEvent(e)}close(){const e=new Event(q);this._element.classList.remove($);const t=this._element?.querySelector(N);t&&i(t,this._config.animationSpeed),this._element.dispatchEvent(e)}toggle(){this._element.classList.contains($)?this.close():this.open()}}n(()=>{document.querySelectorAll(`${P}.${$}`).forEach(e=>{const t=e.querySelector(N);if(t){o(t,0);const n=new Event(M);e.dispatchEvent(n)}}),document.querySelectorAll('[data-lte-toggle="treeview"]').forEach(e=>{e.addEventListener("click",e=>{const t=e.target,n=t.closest(P),i=t.closest(".nav-link"),o=n?.querySelector(N),s=e.currentTarget;if(o&&("#"!==t?.getAttribute("href")&&"#"!==i?.getAttribute("href")||e.preventDefault(),n)){const e=s.dataset.accordion,t=s.dataset.animationSpeed,i={accordion:void 0===e?R.accordion:"true"===e,animationSpeed:void 0===t?R.animationSpeed:Number(t)};new D(n,i).toggle()}})})});const z=".lte.direct-chat",B=`expanded${z}`,F=`collapsed${z}`,C="direct-chat-contacts-open";class H{_element;constructor(e){this._element=e}toggle(){if(this._element.classList.contains(C)){const e=new Event(F);this._element.classList.remove(C),this._element.dispatchEvent(e)}else{const e=new Event(B);this._element.classList.add(C),this._element.dispatchEvent(e)}}}n(()=>{document.querySelectorAll('[data-lte-toggle="chat-pane"]').forEach(e=>{e.addEventListener("click",e=>{e.preventDefault();const t=e.target.closest(".direct-chat");t&&new H(t).toggle()})})});const O=".lte.fullscreen",I=`maximized${O}`,K=`minimized${O}`,W='[data-lte-toggle="fullscreen"]',j='[data-lte-icon="maximize"]',U='[data-lte-icon="minimize"]';class V{_element;_config;constructor(e,t){this._element=e,this._config=t}inFullScreen(){const e=new Event(I),t=document.querySelector(j),n=document.querySelector(U);document.documentElement.requestFullscreen(),t&&t.classList.add("d-none"),n&&n.classList.remove("d-none"),this._element.dispatchEvent(e)}outFullscreen(){const e=new Event(K),t=document.querySelector(j),n=document.querySelector(U);document.exitFullscreen(),t&&t.classList.remove("d-none"),n&&n.classList.add("d-none"),this._element.dispatchEvent(e)}toggleFullScreen(){document.fullscreenEnabled&&(document.fullscreenElement?this.outFullscreen():this.inFullScreen())}}n(()=>{document.querySelectorAll(W).forEach(e=>{e.addEventListener("click",e=>{e.preventDefault();const t=e.target.closest(W);t&&new V(t,void 0).toggleFullScreen()})})});const G=".lte.push-menu",J=`open${G}`,Q=`collapse${G}`,X="sidebar-collapse",Y="sidebar-open",Z='[data-lte-toggle="sidebar"]',ee="lte.sidebar.state",te={sidebarBreakpoint:992,enablePersistence:!1};class ne{_element;_config;constructor(e,t){this._element=e,this._config={...te,...t}}isCollapsed(){return document.body.classList.contains(X)}isExplicitlyOpen(){return document.body.classList.contains(Y)}isMiniMode(){return document.body.classList.contains("sidebar-mini")}isMobileSize(){return globalThis.innerWidth<=this._config.sidebarBreakpoint}expand(){document.body.classList.remove(X),this.isMobileSize()&&document.body.classList.add(Y),this._element.dispatchEvent(new Event(J))}collapse(){document.body.classList.remove(Y),document.body.classList.add(X),this._element.dispatchEvent(new Event(Q))}toggle(){const e=this.isCollapsed();e?this.expand():this.collapse(),this._config.enablePersistence&&this.saveSidebarState(e?Y:X)}setupSidebarBreakPoint(){const e=document.querySelector('[class*="sidebar-expand"]');if(!e)return;const t=globalThis.getComputedStyle(e,"::before").getPropertyValue("content");if(!t||"none"===t)return;const n=Number(t.replace(/[^\d.-]/g,""));Number.isNaN(n)||(this._config={...this._config,sidebarBreakpoint:n})}updateStateByResponsiveLogic(){this.isMobileSize()?this.isExplicitlyOpen()||this.collapse():this.isMiniMode()&&this.isCollapsed()||this.expand()}saveSidebarState(e){if(void 0!==globalThis.localStorage)try{localStorage.setItem(ee,e)}catch{}}loadSidebarState(){if(void 0!==globalThis.localStorage)try{const e=localStorage.getItem(ee);e===X?this.collapse():e===Y?this.expand():this.updateStateByResponsiveLogic()}catch{this.updateStateByResponsiveLogic()}}clearSidebarState(){if(void 0!==globalThis.localStorage)try{localStorage.removeItem(ee)}catch{}}init(){this.setupSidebarBreakPoint(),this._config.enablePersistence||this.clearSidebarState(),this._config.enablePersistence&&!this.isMobileSize()?this.loadSidebarState():this.updateStateByResponsiveLogic()}}n(()=>{const e=document?.querySelector(".app-sidebar");if(!e)return;const t=e.dataset.sidebarBreakpoint,n=e.dataset.enablePersistence,i={sidebarBreakpoint:void 0===t?te.sidebarBreakpoint:Number(t),enablePersistence:void 0===n?te.enablePersistence:"true"===n},o=new ne(e,i);o.init(),window.addEventListener("resize",()=>{o.setupSidebarBreakPoint(),o.updateStateByResponsiveLogic()});const s=document.createElement("div");s.className="sidebar-overlay",document.querySelector(".app-wrapper")?.append(s);let a=!1;s.addEventListener("touchstart",()=>{a=!1},{passive:!0}),s.addEventListener("touchmove",()=>{a=!0},{passive:!0}),s.addEventListener("touchend",e=>{a||(e.preventDefault(),o.collapse()),a=!1},{passive:!1}),s.addEventListener("click",e=>{e.preventDefault(),o.collapse()}),document.querySelectorAll(Z).forEach(e=>{e.addEventListener("click",e=>{e.preventDefault();let t=e.currentTarget;"sidebar"!==t?.dataset.lteToggle&&(t=t?.closest(Z)),t&&(e?.preventDefault(),o.toggle())})})});class ie{config;liveRegion=null;focusHistory=[];constructor(e={}){this.config={announcements:!0,skipLinks:!0,focusManagement:!0,keyboardNavigation:!0,reducedMotion:!0,...e},this.init()}init(){this.config.announcements&&this.createLiveRegion(),this.config.skipLinks&&this.addSkipLinks(),this.config.focusManagement&&this.initFocusManagement(),this.config.keyboardNavigation&&this.initKeyboardNavigation(),this.config.reducedMotion&&this.respectReducedMotion(),this.initErrorAnnouncements(),this.initTableAccessibility(),this.initFormAccessibility()}createLiveRegion(){this.liveRegion||(this.liveRegion=document.createElement("div"),this.liveRegion.id="live-region",this.liveRegion.className="live-region",this.liveRegion.setAttribute("aria-live","polite"),this.liveRegion.setAttribute("aria-atomic","true"),this.liveRegion.setAttribute("role","status"),document.body.append(this.liveRegion))}addSkipLinks(){const e=document.createElement("div");e.className="skip-links";const t=document.createElement("a");t.href="#main",t.className="skip-link",t.textContent="Skip to main content";const n=document.createElement("a");n.href="#navigation",n.className="skip-link",n.textContent="Skip to navigation",e.append(t),e.append(n),document.body.insertBefore(e,document.body.firstChild),this.ensureSkipTargets()}ensureSkipTargets(){const e=document.querySelector('#main, main, [role="main"]');e&&!e.id&&(e.id="main"),e&&!e.hasAttribute("tabindex")&&e.setAttribute("tabindex","-1");const t=document.querySelector('#navigation, nav, [role="navigation"]');t&&!t.id&&(t.id="navigation"),t&&!t.hasAttribute("tabindex")&&t.setAttribute("tabindex","-1")}initFocusManagement(){document.addEventListener("keydown",e=>{"Tab"===e.key&&this.handleTabNavigation(e),"Escape"===e.key&&this.handleEscapeKey(e)}),this.initModalFocusManagement(),this.initDropdownFocusManagement()}handleTabNavigation(e){const t=this.getFocusableElements(),n=t.indexOf(document.activeElement);e.shiftKey?n<=0&&(e.preventDefault(),t.at(-1)?.focus()):n>=t.length-1&&(e.preventDefault(),t[0]?.focus())}getFocusableElements(){const e=["a[href]","button:not([disabled])","input:not([disabled])","select:not([disabled])","textarea:not([disabled])",'[tabindex]:not([tabindex="-1"])','[contenteditable="true"]'].join(", ");return Array.from(document.querySelectorAll(e))}handleEscapeKey(e){if(!document.querySelector(".modal.show")&&document.querySelector(".dropdown-menu.show")){const t=document.querySelector('[data-bs-toggle="dropdown"][aria-expanded="true"]');t?.click(),e.preventDefault()}}initKeyboardNavigation(){document.addEventListener("keydown",e=>{const t=e.target;t.closest(".nav, .navbar-nav, .dropdown-menu")&&this.handleMenuNavigation(e),"Enter"!==e.key&&" "!==e.key||!t.hasAttribute("role")||"button"!==t.getAttribute("role")||t.matches('button, input[type="button"], input[type="submit"]')||(e.preventDefault(),t.click())})}handleMenuNavigation(e){if(!["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Home","End"].includes(e.key))return;const t=e.target,n=Array.from(t.closest(".nav, .navbar-nav, .dropdown-menu")?.querySelectorAll("a, button")||[]),i=n.indexOf(t);let o;switch(e.key){case"ArrowDown":case"ArrowRight":o=i0?i-1:n.length-1;break;case"Home":o=0;break;case"End":o=n.length-1;break;default:return}e.preventDefault(),n[o]?.focus()}respectReducedMotion(){if(globalThis.matchMedia("(prefers-reduced-motion: reduce)").matches){document.body.classList.add("reduce-motion"),document.documentElement.style.scrollBehavior="auto";const e=document.createElement("style");e.textContent="\n *, *::before, *::after {\n animation-duration: 0.01ms !important;\n animation-iteration-count: 1 !important;\n transition-duration: 0.01ms !important;\n }\n ",document.head.append(e)}}initErrorAnnouncements(){new MutationObserver(e=>{e.forEach(e=>{e.addedNodes.forEach(e=>{if(e.nodeType===Node.ELEMENT_NODE){const t=e;t.matches(".alert-danger, .invalid-feedback, .error")&&this.announce(t.textContent||"Error occurred","assertive"),t.matches(".alert-success, .success")&&this.announce(t.textContent||"Success","polite")}})})}).observe(document.body,{childList:!0,subtree:!0})}initTableAccessibility(){document.querySelectorAll("table").forEach(e=>{if(e.hasAttribute("role")||e.setAttribute("role","table"),e.querySelectorAll("th").forEach(e=>{if(!e.hasAttribute("scope")){const t=e.closest("thead"),n=0===e.cellIndex;t?e.setAttribute("scope","col"):n&&e.setAttribute("scope","row")}}),!e.querySelector("caption")&&e.hasAttribute("title")){const t=document.createElement("caption");t.textContent=e.getAttribute("title")||"",e.insertBefore(t,e.firstChild)}})}initFormAccessibility(){document.querySelectorAll("input, select, textarea").forEach(e=>{const t=e;if(!t.labels?.length&&!t.hasAttribute("aria-label")&&!t.hasAttribute("aria-labelledby")){const e=t.getAttribute("placeholder");e&&t.setAttribute("aria-label",e)}if(t.hasAttribute("required")){const e=t.labels?.[0];if(e&&!e.querySelector(".required-indicator")){const t=document.createElement("span");t.className="required-indicator sr-only",t.textContent=" (required)",e.append(t)}}t.classList.contains("disable-adminlte-validations")||t.addEventListener("invalid",()=>{this.handleFormError(t)})})}handleFormError(e){const t=`${e.id||e.name}-error`;let n=document.getElementById(t);n||(n=document.createElement("div"),n.id=t,n.className="invalid-feedback",n.setAttribute("role","alert"),e.parentNode?.append(n)),n.textContent=e.validationMessage,e.setAttribute("aria-describedby",t),e.classList.add("is-invalid"),this.announce(`Error in ${e.labels?.[0]?.textContent||e.name}: ${e.validationMessage}`,"assertive")}initModalFocusManagement(){document.addEventListener("shown.bs.modal",e=>{const t=e.target.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])');t.length>0&&t[0].focus(),this.focusHistory.push(document.activeElement)}),document.addEventListener("hidden.bs.modal",()=>{const e=this.focusHistory.pop();e&&e.focus()})}initDropdownFocusManagement(){document.addEventListener("shown.bs.dropdown",e=>{const t=e.target.querySelector(".dropdown-menu"),n=t?.querySelector("a, button");n&&n.focus()})}announce(e,t="polite"){this.liveRegion||this.createLiveRegion(),this.liveRegion&&(this.liveRegion.setAttribute("aria-live",t),this.liveRegion.textContent=e,setTimeout(()=>{this.liveRegion&&(this.liveRegion.textContent="")},1e3))}focusElement(e){const t=document.querySelector(e);t&&(t.focus(),t.scrollIntoView({behavior:"smooth",block:"center"}))}trapFocus(e){const t=e.querySelectorAll('button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'),n=Array.from(t),i=n[0],o=n.at(-1);e.addEventListener("keydown",e=>{"Tab"===e.key&&(e.shiftKey?document.activeElement===i&&(o?.focus(),e.preventDefault()):document.activeElement===o&&(i.focus(),e.preventDefault()))})}addLandmarks(){if(!document.querySelector("main")){const e=document.querySelector(".app-main");e&&(e.setAttribute("role","main"),e.id="main")}document.querySelectorAll(".navbar-nav, .nav").forEach((e,t)=>{e.hasAttribute("role")||e.setAttribute("role","navigation"),e.hasAttribute("aria-label")||e.setAttribute("aria-label",`Navigation ${t+1}`)});const e=document.querySelector('form[role="search"], .navbar-search');e&&!e.hasAttribute("role")&&e.setAttribute("role","search")}}const oe=e=>new ie(e);n(()=>{oe({announcements:!0,skipLinks:!0,focusManagement:!0,keyboardNavigation:!0,reducedMotion:!0}).addLandmarks()}),e.CardWidget=x,e.DirectChat=H,e.FullScreen=V,e.Layout=a,e.PushMenu=ne,e.Treeview=D,e.initAccessibility=oe}); -//# sourceMappingURL=adminlte.min.js.map \ No newline at end of file diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/js/adminlte.min.js.map b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/js/adminlte.min.js.map deleted file mode 100644 index 8d1f43a3..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/js/adminlte.min.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"names":["domContentLoadedCallbacks","onDOMContentLoaded","callback","document","readyState","length","addEventListener","push","slideUp","target","duration","style","display","transitionProperty","transitionDuration","boxSizing","height","offsetHeight","overflow","globalThis","setTimeout","paddingTop","paddingBottom","marginTop","marginBottom","removeProperty","slideDown","getComputedStyle","CLASS_NAME_HOLD_TRANSITIONS","Layout","_element","_holdTransitionTimer","constructor","element","this","undefined","holdTransition","time","clearTimeout","body","classList","add","remove","layout","window","EVENT_KEY","EVENT_COLLAPSED","EVENT_EXPANDED","EVENT_REMOVE","EVENT_MAXIMIZED","EVENT_MINIMIZED","CLASS_NAME_CARD","CLASS_NAME_COLLAPSED","CLASS_NAME_COLLAPSING","CLASS_NAME_EXPANDING","CLASS_NAME_WAS_COLLAPSED","CLASS_NAME_MAXIMIZED","SELECTOR_DATA_REMOVE","SELECTOR_DATA_COLLAPSE","SELECTOR_DATA_MAXIMIZE","SELECTOR_CARD","SELECTOR_CARD_BODY","SELECTOR_CARD_FOOTER","Default","animationSpeed","collapseTrigger","removeTrigger","maximizeTrigger","CardWidget","_parent","_clone","_config","config","closest","contains","collapse","event","Event","elm","querySelectorAll","forEach","el","HTMLElement","dispatchEvent","expand","toggle","maximize","width","offsetWidth","transition","htmlTag","querySelector","minimize","toggleMaximize","btn","preventDefault","EVENT_LOAD_DATA_API","CLASS_NAME_MENU_OPEN","SELECTOR_NAV_ITEM","SELECTOR_TREEVIEW_MENU","accordion","Treeview","open","openMenuList","parentElement","openMenu","childElement","close","menuItem","targetItem","targetLink","targetTreeviewMenu","lteToggleElement","currentTarget","getAttribute","accordionAttr","dataset","animationSpeedAttr","Number","CLASS_NAME_DIRECT_CHAT_OPEN","DirectChat","chatPane","SELECTOR_FULLSCREEN_TOGGLE","SELECTOR_MAXIMIZE_ICON","SELECTOR_MINIMIZE_ICON","FullScreen","inFullScreen","iconMaximize","iconMinimize","documentElement","requestFullscreen","outFullscreen","exitFullscreen","toggleFullScreen","fullscreenEnabled","fullscreenElement","button","EVENT_OPEN","EVENT_COLLAPSE","CLASS_NAME_SIDEBAR_COLLAPSE","CLASS_NAME_SIDEBAR_OPEN","SELECTOR_SIDEBAR_TOGGLE","STORAGE_KEY_SIDEBAR_STATE","Defaults","sidebarBreakpoint","enablePersistence","PushMenu","isCollapsed","isExplicitlyOpen","isMiniMode","isMobileSize","innerWidth","saveSidebarState","setupSidebarBreakPoint","sidebarExpand","content","getPropertyValue","breakpointValue","replace","isNaN","updateStateByResponsiveLogic","state","localStorage","setItem","loadSidebarState","storedState","getItem","clearSidebarState","removeItem","init","sidebar","sidebarBreakpointAttr","enablePersistenceAttr","pushMenu","sidebarOverlay","createElement","className","append","overlayTouchMoved","passive","lteToggle","AccessibilityManager","liveRegion","focusHistory","announcements","skipLinks","focusManagement","keyboardNavigation","reducedMotion","createLiveRegion","addSkipLinks","initFocusManagement","initKeyboardNavigation","respectReducedMotion","initErrorAnnouncements","initTableAccessibility","initFormAccessibility","id","setAttribute","skipLinksContainer","skipToMain","href","textContent","skipToNav","insertBefore","firstChild","ensureSkipTargets","main","hasAttribute","nav","key","handleTabNavigation","handleEscapeKey","initModalFocusManagement","initDropdownFocusManagement","focusableElements","getFocusableElements","currentIndex","indexOf","activeElement","shiftKey","at","focus","selector","join","Array","from","toggleButton","click","handleMenuNavigation","matches","includes","currentElement","menuItems","nextIndex","matchMedia","scrollBehavior","head","MutationObserver","mutations","mutation","addedNodes","node","nodeType","Node","ELEMENT_NODE","announce","observe","childList","subtree","table","th","isInThead","isFirstColumn","cellIndex","caption","input","htmlInput","labels","placeholder","label","indicator","handleFormError","errorId","name","errorElement","getElementById","parentNode","validationMessage","previousElement","pop","menu","firstItem","message","priority","focusElement","scrollIntoView","behavior","block","trapFocus","container","focusableArray","firstElement","lastElement","addLandmarks","appMain","index","searchForm","initAccessibility"],"sources":["../../src/ts/util/index.ts","../../src/ts/layout.ts","../../src/ts/card-widget.ts","../../src/ts/treeview.ts","../../src/ts/direct-chat.ts","../../src/ts/fullscreen.ts","../../src/ts/push-menu.ts","../../src/ts/accessibility.ts","../../src/ts/adminlte.ts"],"mappings":"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","ignoreList":[]} \ No newline at end of file diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_accessibility.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_accessibility.scss deleted file mode 100644 index a07fc9bd..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_accessibility.scss +++ /dev/null @@ -1,337 +0,0 @@ -/* ========================================================================== - AdminLTE Accessibility Styles - WCAG 2.1 AA Compliance - ========================================================================== */ - -/* Skip Links - WCAG 2.4.1: Bypass Blocks */ -.skip-link { - position: absolute; - top: -40px; - left: 6px; - z-index: 999999; - padding: 8px 16px; - font-weight: 600; - color: var(--bs-white); - text-decoration: none; - background: var(--bs-primary); - - &:focus { - top: 0; - outline: 3px solid var(--bs-warning); - outline-offset: 2px; - } - - &:hover { - color: var(--bs-white); - text-decoration: none; - background: var(--bs-primary-emphasis); - } -} - -/* Enhanced Focus Indicators - WCAG 2.4.7: Focus Visible */ -.focus-enhanced { - &:focus { - outline: 3px solid var(--bs-focus-ring-color, #0d6efd); - outline-offset: 2px; - box-shadow: 0 0 0 .25rem rgba(13, 110, 253, .25); - } -} - -/* High Contrast Mode Support */ -@media (prefers-contrast: high) { - .card { - border: 2px solid; - } - - .btn { - border-width: 2px; - } - - .nav-link { - border: 1px solid transparent; - - &:hover, - &:focus { - border-color: currentcolor; - } - } -} - -/* Reduced Motion Support - WCAG 2.3.3: Animation from Interactions */ -@media (prefers-reduced-motion: reduce) { - *, - *::before, - *::after { - transition-duration: .01ms !important; - animation-duration: .01ms !important; - animation-iteration-count: 1 !important; - scroll-behavior: auto !important; - } - - .fade { - opacity: 1 !important; - /* stylelint-disable-next-line property-disallowed-list */ - transition: none !important; - } - - .collapse { - /* stylelint-disable-next-line property-disallowed-list */ - transition: none !important; - } - - .modal.fade .modal-dialog { - transform: none !important; - } -} - -/* Screen Reader Only Content */ -.sr-only { - position: absolute !important; - width: 1px !important; - height: 1px !important; - padding: 0 !important; - margin: -1px !important; - overflow: hidden !important; - clip: rect(0, 0, 0, 0) !important; - white-space: nowrap !important; - border: 0 !important; -} - -.sr-only-focusable:focus { - position: static !important; - width: auto !important; - height: auto !important; - padding: inherit !important; - margin: inherit !important; - overflow: visible !important; - clip: auto !important; - white-space: normal !important; -} - -/* Focus Trap Utilities */ -.focus-trap { - &:focus { - box-shadow: 0 0 0 .25rem rgba(13, 110, 253, .25); - } -} - -/* Accessible Color Combinations - WCAG 1.4.3: Contrast (Minimum) */ -.text-accessible-primary { - color: #003d82; /* 4.5:1 contrast on white */ -} - -.text-accessible-success { - color: #0f5132; /* 4.5:1 contrast on white */ -} - -.text-accessible-danger { - color: #842029; /* 4.5:1 contrast on white */ -} - -.text-accessible-warning { - color: #664d03; /* 4.5:1 contrast on white */ -} - -/* ARIA Live Regions */ -.live-region { - position: absolute; - left: -10000px; - width: 1px; - height: 1px; - overflow: hidden; - - &.live-region-visible { - position: static; - left: auto; - width: auto; - height: auto; - overflow: visible; - } -} - -/* Enhanced Error States - WCAG 3.3.1: Error Identification */ -.form-control.is-invalid { - border-color: var(--bs-danger); - - &:focus { - border-color: var(--bs-danger); - box-shadow: 0 0 0 .25rem rgba(220, 53, 69, .25); - } -} - -.invalid-feedback { - &[role="alert"] { - font-weight: 600; - } -} - -/* Target Size - WCAG 2.5.8: Target Size (Minimum) */ -.touch-target { - min-width: 44px; - min-height: 44px; - - &.touch-target-small { - min-width: 24px; - min-height: 24px; - } -} - -/* Table Accessibility */ -.table-accessible { - th { - font-weight: 600; - background-color: var(--bs-secondary-bg); - - &[scope="col"] { - border-bottom: 2px solid var(--bs-border-color); - } - - &[scope="row"] { - border-right: 2px solid var(--bs-border-color); - } - } - - caption { - padding: .75rem; - font-weight: 600; - color: var(--bs-secondary); - text-align: left; - caption-side: top; - } -} - -/* Navigation Landmarks */ -nav[role="navigation"] { - &:not([aria-label]):not([aria-labelledby]) { - &::before { - position: absolute; - left: -10000px; - content: "Navigation"; - } - } -} - -/* Form Fieldset Styling */ -fieldset { - padding: 1rem; - margin-bottom: 1rem; - border: 1px solid var(--bs-border-color); - - legend { - padding: 0 .5rem; - margin-bottom: .5rem; - font-size: 1.1em; - font-weight: 600; - } -} - -/* Loading States */ -.loading[aria-busy="true"] { - position: relative; - pointer-events: none; - - &::after { - position: absolute; - top: 50%; - left: 50%; - width: 20px; - height: 20px; - margin-top: -10px; - margin-left: -10px; - content: ""; - border: 2px solid var(--bs-primary); - border-top-color: transparent; - animation: spin 1s linear infinite; - } - - @media (prefers-reduced-motion: reduce) { - &::after { - border-top-color: var(--bs-primary); - animation: none; - } - } -} - -@keyframes spin { - to { - transform: rotate(360deg); - } -} - -/* Dark Mode Accessibility */ -[data-bs-theme="dark"] { - .text-accessible-primary { - color: #6ea8fe; - } - - .text-accessible-success { - color: #75b798; - } - - .text-accessible-danger { - color: #f1aeb5; - } - - .text-accessible-warning { - color: #ffda6a; - } -} - -/* Print Accessibility */ -@media print { - .skip-link, - .btn, - .nav-link { - color: #000 !important; - background: transparent !important; - border: 1px solid #000 !important; - } - - a[href^="http"]::after { - font-size: .8em; - content: " (" attr(href) ")"; - } - - /* Print Layout Fix - Ensure sidebar and main content are both visible */ - .app-wrapper { - display: grid !important; - grid-template-rows: auto 1fr auto !important; - grid-template-columns: auto 1fr !important; - } - - .sidebar-overlay { - display: none !important; - } - - .app-sidebar { - position: static !important; - display: block !important; - min-width: 200px !important; - max-width: 200px !important; - max-height: none !important; - margin-left: 0 !important; - overflow: visible !important; - } - - .sidebar-wrapper { - height: auto !important; - overflow: visible !important; - } - - .app-header { - position: static !important; - } - - .app-main { - width: auto !important; - max-width: 100% !important; - overflow: visible !important; - } - - .app-content { - overflow: visible !important; - } - - .app-footer { - position: static !important; - } -} diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_app-content.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_app-content.scss deleted file mode 100644 index d9dfcb9b..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_app-content.scss +++ /dev/null @@ -1,3 +0,0 @@ -.app-content { - padding: $lte-content-padding-y $lte-content-padding-x; -} diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_app-footer.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_app-footer.scss deleted file mode 100644 index afa9e037..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_app-footer.scss +++ /dev/null @@ -1,32 +0,0 @@ -// -// Core: Main Footer -// - -.app-footer { - grid-area: #{$lte-prefix}app-footer; - width: inherit; - max-width: 100vw; - min-height: 3rem; - padding: $lte-app-footer-padding; - color: $lte-app-footer-color; - background-color: $lte-app-footer-bg; - border-top: $lte-app-footer-border-top; - @include transition($lte-transition-speed $lte-transition-fn); -} - -.fixed-footer { - .app-footer { - position: sticky; - bottom: 0; - z-index: $lte-zindex-fixed-footer; - } - - // When layout-fixed is used, app-main has overflow: auto which prevents - // position: sticky from working on the footer. Ensure the grid keeps the - // footer pinned at the bottom while app-main scrolls independently. - &.layout-fixed { - .app-main { - min-height: 0; - } - } -} diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_app-header.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_app-header.scss deleted file mode 100644 index 0d9aff9b..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_app-header.scss +++ /dev/null @@ -1,61 +0,0 @@ -// -// Core: Main Header -// - -.app-header { - z-index: $lte-zindex-app-header; - grid-area: #{$lte-prefix}app-header; - max-width: 100vw; - border-bottom: $lte-app-header-bottom-border; - @include transition($lte-transition-speed $lte-transition-fn); - - .nav-link { - position: relative; - height: $nav-link-height; - } -} - -// Navbar badge -.navbar-badge { - position: absolute; - top: 9px; - right: 5px; - padding: 2px 4px; - font-size: .6rem; - font-weight: 400; -} - -.fixed-header { - .app-header { - position: sticky; - top: 0; - z-index: $lte-zindex-fixed-header; - } - - // Fixes #6020: when only `fixed-header` is used (without `layout-fixed`), - // the sidebar — including its branding — used to scroll with the page - // because nothing made it sticky. Pin the sidebar so the brand and menu - // stay visible while the page content scrolls beneath the fixed header. - // Only applied on `sidebar-expand-*` breakpoints, since the mobile sidebar - // is an off-canvas overlay that handles its own positioning. - @each $breakpoint in map-keys($grid-breakpoints) { - $next: breakpoint-next($breakpoint, $grid-breakpoints); - $infix: breakpoint-infix($next, $grid-breakpoints); - - &.sidebar-expand#{$infix} { - @include media-breakpoint-up($next) { - .app-sidebar { - position: sticky; - top: 0; - max-height: 100vh; - - .sidebar-wrapper { - height: subtract(100vh, add($lte-app-header-height, 1px)); - overflow-x: hidden; - overflow-y: auto; - } - } - } - } - } -} diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_app-main.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_app-main.scss deleted file mode 100644 index 617cf045..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_app-main.scss +++ /dev/null @@ -1,41 +0,0 @@ -.app-main { - position: relative; - display: flex; - flex-direction: column; - grid-area: #{$lte-prefix}app-main; - max-width: 100vw; - padding-bottom: $lte-app-main-padding-bottom; - @include transition($lte-transition-speed $lte-transition-fn); - - .app-content-header { - padding: 1rem $lte-content-padding-x; - - .breadcrumb { - padding: 0; - margin-bottom: 0; - line-height: 2.5rem; - - a { - text-decoration: none; - } - } - } - - .app-content-top-area, - .app-content-bottom-area { - color: $lte-app-content-bottom-area-color; - background-color: $lte-app-content-bottom-area-bg; - } - - .app-content-top-area { - padding: $lte-app-content-top-area-padding-y $lte-app-content-top-area-padding-x; - border-bottom: $lte-app-content-top-area-top-border; - } - - .app-content-bottom-area { - padding: $lte-app-content-bottom-area-padding-y $lte-app-content-bottom-area-padding-x; - margin-top: auto; - margin-bottom: $lte-app-content-bottom-area-margin-bottom; - border-top: $lte-app-content-bottom-area-top-border; - } -} diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_app-sidebar.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_app-sidebar.scss deleted file mode 100644 index 5bf4ff05..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_app-sidebar.scss +++ /dev/null @@ -1,619 +0,0 @@ -.app-sidebar { - --#{$lte-prefix}sidebar-hover-bg: #{$lte-sidebar-hover-bg}; - --#{$lte-prefix}sidebar-color: #{$lte-sidebar-color}; - --#{$lte-prefix}sidebar-hover-color: #{$lte-sidebar-hover-color}; - --#{$lte-prefix}sidebar-active-color: #{$lte-sidebar-active-color}; - --#{$lte-prefix}sidebar-menu-active-bg: #{$lte-sidebar-menu-active-bg}; - --#{$lte-prefix}sidebar-menu-active-color: #{$lte-sidebar-menu-active-color}; - --#{$lte-prefix}sidebar-submenu-bg: #{$lte-sidebar-submenu-bg}; - --#{$lte-prefix}sidebar-submenu-color: #{$lte-sidebar-submenu-color}; - --#{$lte-prefix}sidebar-submenu-hover-color: #{$lte-sidebar-submenu-hover-color}; - --#{$lte-prefix}sidebar-submenu-hover-bg: #{$lte-sidebar-submenu-hover-bg}; - --#{$lte-prefix}sidebar-submenu-active-color: #{$lte-sidebar-submenu-active-color}; - --#{$lte-prefix}sidebar-submenu-active-bg: #{$lte-sidebar-submenu-active-bg}; - --#{$lte-prefix}sidebar-header-color: #{$lte-sidebar-header-color}; - - z-index: $lte-zindex-sidebar; - grid-area: #{$lte-prefix}app-sidebar; - min-width: var(--#{$lte-prefix}sidebar-width); - max-width: var(--#{$lte-prefix}sidebar-width); - @include transition($lte-sidebar-transition); -} - -.sidebar-brand { - display: flex; - align-items: center; - justify-content: center; - height: $lte-app-header-height; - padding: $lte-brand-link-padding-y $lte-brand-link-padding-x; - overflow: hidden; - font-size: $navbar-brand-font-size; - white-space: nowrap; - border-bottom: $lte-brand-link-border-buttom solid var(--#{$prefix}border-color); - @include transition(width $lte-transition-speed $lte-transition-fn); - - .brand-link { - display: flex; - align-items: center; - text-decoration: none; - - .brand-image { - float: left; - width: auto; - max-height: 33px; - line-height: .8; - } - - .brand-image-xs { - float: left; - width: auto; - max-height: 33px; - margin-top: -.1rem; - line-height: .8; - } - - .brand-image-xl { - width: auto; - max-height: 40px; - line-height: .8; - - &.single { - margin-top: -.3rem; - } - } - } - - .brand-text { - margin-left: .5rem; - color: rgba(var(--#{$prefix}emphasis-color-rgb), .8); - @include transition(flex $lte-transition-speed $lte-transition-fn, width $lte-transition-speed $lte-transition-fn); - &:hover { - color: var(--#{$prefix}emphasis-color); - } - } -} - -.sidebar-wrapper { - padding-top: $lte-sidebar-padding-y; - padding-right: $lte-sidebar-padding-x; - padding-bottom: $lte-sidebar-padding-y; - padding-left: $lte-sidebar-padding-x; - overscroll-behavior: contain; - @include scrollbar-color-gray(); - @include scrollbar-width-thin(); - - .nav-item { - max-width: 100%; - } - - .nav-link { - display: flex; - justify-content: flex-start; - - p { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - } - .nav-icon { - display: flex; - align-items: center; - justify-content: center; - min-width: 1.5rem; - max-width: 1.5rem; - } - - // Sidebar Menu. First level links - .sidebar-menu > .nav-item { - // links - > .nav-link { - color: var(--#{$lte-prefix}sidebar-color); - - &:hover, - &:focus { - color: var(--#{$lte-prefix}sidebar-hover-color); - background-color: var(--#{$lte-prefix}sidebar-hover-bg); - } - - &.active { - color: var(--#{$lte-prefix}sidebar-menu-active-color); - background-color: var(--#{$lte-prefix}sidebar-menu-active-bg); - } - } - - // Open state - &.menu-open > .nav-link { - color: var(--#{$lte-prefix}sidebar-hover-color); - background-color: var(--#{$lte-prefix}sidebar-hover-bg); - - &.active { - color: var(--#{$lte-prefix}sidebar-menu-active-color); - background-color: var(--#{$lte-prefix}sidebar-menu-active-bg); - } - } - - // First Level Submenu - > .nav-treeview { - background-color: var(--#{$lte-prefix}sidebar-submenu-bg); - } - } - - // Section Heading - .nav-header { - display: inline; - overflow: hidden; - color: var(--#{$lte-prefix}sidebar-header-color); - text-overflow: ellipsis; - background-color: inherit; - } - - // All links within the sidebar menu - a { - color: var(--#{$lte-prefix}sidebar-color); - } - - // All submenus - .nav-treeview { - > .nav-item { - > .nav-link { - color: var(--#{$lte-prefix}sidebar-submenu-color); - - &:hover, - &:focus { - color: var(--#{$lte-prefix}sidebar-submenu-hover-color); - background-color: var(--#{$lte-prefix}sidebar-submenu-hover-bg); - } - - &.active { - color: var(--#{$lte-prefix}sidebar-submenu-active-color); - background-color: var(--#{$lte-prefix}sidebar-submenu-active-bg); - } - } - } - } -} - -// Sidebar navigation menu -.sidebar-menu { - // All levels - .nav-item { - > .nav-link { - margin-bottom: .2rem; - - .nav-arrow { - @include transition(transform $lte-transition-fn $lte-transition-speed); - transform: translateY(-50%) #{"/*rtl:append:rotate(180deg)*/"}; - animation-name: fadeIn; - animation-duration: $lte-transition-speed; - animation-fill-mode: both; - } - } - } - - // All levels - .nav-link > .nav-badge, - .nav-link > p > .nav-badge { - position: absolute; - top: 50%; - right: 1rem; - transform: translateY(-50%); - } - - .nav-link > .nav-arrow, - .nav-link > p > .nav-arrow { - position: absolute; - top: 50%; - right: 1rem; - } - - .nav-link { - position: relative; - width: 100%; - @include transition(width $lte-transition-fn $lte-transition-speed); - @include border-radius($border-radius); - - p { - display: inline; - padding-left: .5rem; - margin: 0; - } - } - - .nav-header { - position: relative; - width: 100%; - padding: $nav-link-padding-y ($nav-link-padding-y * 1.5); - font-size: .9rem; - @include transition(width $lte-transition-fn $lte-transition-speed); - } - - // Tree view menu - .nav-treeview { - display: none; - padding: 0; - list-style: none; - } - - .menu-open { - > .nav-treeview { - display: block; - } - - > .nav-link { - .nav-arrow { - transform: translateY(-50%) rotate(90deg) #{"/*rtl:ignore*/"}; - } - } - } - - // Override: ensure badges/arrows are aligned to the end (right in LTR, left in RTL) - .nav-link > .nav-badge, - .nav-link > p > .nav-badge, - .nav-link > .nav-arrow, - .nav-link > p > .nav-arrow { - right: 1rem !important; // place at end in LTR - left: auto !important; - } -} - -.nav-indent { - .nav-treeview { - padding-left: $lte-sidebar-padding-x; - } -} - -.nav-compact.nav-indent { - .nav-treeview { - padding-left: 0; - - .nav-item { - padding-left: $lte-sidebar-padding-x; - } - } -} - -.sidebar-mini.sidebar-collapse.nav-indent { - .app-sidebar:hover { - .nav-treeview { - padding-left: 0; - - .nav-item { - padding-left: $lte-sidebar-padding-x; - } - } - } -} - -.sidebar-collapse { - &.nav-compact.nav-indent { - .nav-treeview { - .nav-item { - padding-left: 0; - } - } - } -} - -.nav-compact .nav-link { - @include border-radius(0); - margin-bottom: 0 !important; -} - -// A fix for text overflow while transitioning from sidebar mini to full sidebar -.sidebar-menu, -.sidebar-menu > .nav-header, -.sidebar-menu .nav-link { - white-space: nowrap; -} - -// Logo style -.logo-xs, -.logo-xl { - position: absolute; - visibility: visible; - opacity: 1; - - &.brand-image-xs { - top: 12px; - left: 18px; - } - - &.brand-image-xl { - top: 6px; - left: 12px; - } -} - -.logo-xs { - visibility: hidden; - opacity: 0; - - &.brand-image-xl { - top: 8px; - left: 16px; - } -} - -.brand-link { - &.logo-switch { - &::before { - content: "\00a0"; - } - } -} - -.sidebar-mini.sidebar-collapse { - .app-sidebar { - min-width: $lte-sidebar-mini-width; - max-width: $lte-sidebar-mini-width; - } - - // Make the sidebar headers - .sidebar-menu .nav-header { - display: none; - } - - .sidebar-menu { - .nav-link { - width: $lte-sidebar-mini-width - $lte-sidebar-padding-x * 2; - - p { - display: inline-block; - width: 0; - white-space: nowrap; - } - } - .nav-badge, - .nav-arrow { - display: none; - animation-name: fadeOut; - animation-duration: $lte-transition-speed; - animation-fill-mode: both; - } - } - - .brand-text { - display: inline-block; - max-width: 0; - overflow: hidden; - } - - .sidebar-menu .nav-link p, - .brand-text, - .logo-xl, - .nav-arrow { - visibility: hidden; - animation-name: fadeOut; - animation-duration: $lte-transition-speed; - animation-fill-mode: both; - } - - - .logo-xs { - display: inline-block; - visibility: visible; - animation-name: fadeIn; - animation-duration: $lte-transition-speed; - animation-fill-mode: both; - } - - &:not(.sidebar-without-hover) { - .app-sidebar:hover { - min-width: var(--#{$lte-prefix}sidebar-width); - max-width: var(--#{$lte-prefix}sidebar-width); - - .sidebar-menu .nav-header { - display: inline; - } - - .sidebar-menu .nav-link { - width: auto; - } - - .sidebar-menu .nav-link p, - .brand-text, - .logo-xl { - width: auto; - margin-left: 0; - visibility: visible; - animation-name: fadeIn; - animation-duration: $lte-transition-speed; - animation-fill-mode: both; - } - - .brand-text { - display: inline; - max-width: inherit; - margin-left: .5rem; - animation-name: fadeIn; - animation-duration: $lte-transition-speed; - animation-fill-mode: both; - } - - .nav-badge, - .nav-arrow { - display: inline-block; - visibility: visible; - animation-name: fadeIn; - animation-duration: $lte-transition-speed; - animation-fill-mode: both; - animation-delay: $lte-transition-speed; - } - - .nav-link p { - padding-left: .5rem; - } - - .logo-xs { - visibility: hidden; - animation-name: fadeOut; - animation-duration: $lte-transition-speed; - animation-fill-mode: both; - } - } - } -} - -.sidebar-collapse:not(.sidebar-mini) { - .app-sidebar { - margin-left: calc(var(--#{$lte-prefix}sidebar-width) * -1); // stylelint-disable-line function-disallowed-list - } -} - -.sidebar-expand { - @each $breakpoint in map-keys($grid-breakpoints) { - $next: breakpoint-next($breakpoint, $grid-breakpoints); - $infix: breakpoint-infix($next, $grid-breakpoints); - - /* stylelint-disable-next-line scss/selector-no-union-class-name */ - &#{$infix} { - @include media-breakpoint-up($next) { - &.layout-fixed { - .app-main-wrapper { - display: flex; - flex-direction: column; - min-height: 100vh; - } - .app-sidebar-wrapper { - position: relative; - } - .app-main { - flex: 1 1 auto; - overflow: auto; - } - .app-sidebar { - position: sticky; - top: 0; - bottom: 0; - max-height: 100vh; - - .sidebar-wrapper { - height: subtract(100vh, add($lte-app-header-height, 1px)); - overflow-x: hidden; - overflow-y: auto; - } - } - } - - &.sidebar-open { - .nav-link > .nav-badge, - .nav-link > p > .nav-badge { - animation-name: fadeIn; - animation-duration: $lte-transition-speed; - animation-fill-mode: both; - animation-delay: $lte-transition-speed; - } - .nav-link > .nav-arrow, - .nav-link > p > .nav-arrow { - animation-name: fadeIn; - animation-duration: $lte-transition-speed; - animation-fill-mode: both; - animation-delay: $lte-transition-speed; - } - } - } - - @include media-breakpoint-down($next) { - $max: breakpoint-max($next); - - &::before { - display: none; - content: "#{$max}"; - } - - .app-sidebar { - position: fixed; - top: 0; - bottom: 0; - max-height: 100vh; - margin-left: calc(var(--#{$lte-prefix}sidebar-width) * -1); // stylelint-disable-line function-disallowed-list - - .sidebar-wrapper { - height: subtract(100vh, add($lte-app-header-height, 1px)); - overflow-x: hidden; - overflow-y: auto; - } - } - - &.sidebar-open { - .app-sidebar { - margin-left: 0; - } - - .sidebar-overlay { - position: absolute; - inset: 0; - z-index: $lte-zindex-sidebar-overlay; - width: 100%; - height: 100%; - cursor: pointer; - visibility: visible; - background-color: rgba(0, 0, 0, .2); - animation-name: fadeIn; - animation-fill-mode: both; - } - } - } - } - } -} - -.sidebar-menu .nav-link p, -.app-sidebar .brand-text, -.app-sidebar .logo-xs, -.app-sidebar .logo-xl { - @include transition(margin-left $lte-transition-speed linear, opacity $lte-transition-speed ease, visibility $lte-transition-speed ease); -} - -// To prevent onload transition and animation -.app-loaded { - &.sidebar-mini.sidebar-collapse { - .sidebar-menu .nav-link p, - .brand-text { - animation-duration: $lte-transition-speed; - } - } -} -body:not(.app-loaded) { - .app-header, - .app-sidebar, - .app-main, - .app-footer { - @include transition(none !important); - animation-duration: 0s !important; - } -} -.hold-transition { - .app-header, - .app-sidebar, - .app-main, - .app-footer, - .nav-arrow, - .nav-badge { - @include transition(none !important); - animation-duration: 0s !important; - } -} - -@if $enable-dark-mode { - @include color-mode(dark) { - &.app-sidebar, - .app-sidebar { - --#{$lte-prefix}sidebar-hover-bg: #{$lte-sidebar-hover-bg-dark}; - --#{$lte-prefix}sidebar-color: #{$lte-sidebar-color-dark}; - --#{$lte-prefix}sidebar-hover-color: #{$lte-sidebar-hover-color-dark}; - --#{$lte-prefix}sidebar-active-color: #{$lte-sidebar-active-color-dark}; - --#{$lte-prefix}sidebar-menu-active-bg: #{$lte-sidebar-menu-active-bg-dark}; - --#{$lte-prefix}sidebar-menu-active-color: #{$lte-sidebar-menu-active-color-dark}; - --#{$lte-prefix}sidebar-submenu-bg: #{$lte-sidebar-submenu-bg-dark}; - --#{$lte-prefix}sidebar-submenu-color: #{$lte-sidebar-submenu-color-dark}; - --#{$lte-prefix}sidebar-submenu-hover-color: #{$lte-sidebar-submenu-hover-color-dark}; - --#{$lte-prefix}sidebar-submenu-hover-bg: #{$lte-sidebar-submenu-hover-bg-dark}; - --#{$lte-prefix}sidebar-submenu-active-color: #{$lte-sidebar-submenu-active-color-dark}; - --#{$lte-prefix}sidebar-submenu-active-bg: #{$lte-sidebar-submenu-active-bg-dark}; - --#{$lte-prefix}sidebar-header-color: #{$lte-sidebar-header-color-dark}; - } - } -} - diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_app-wrapper.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_app-wrapper.scss deleted file mode 100644 index 5ecc611d..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_app-wrapper.scss +++ /dev/null @@ -1,23 +0,0 @@ -// -// Core: Layout -// - -.app-wrapper { - position: relative; - display: grid; - grid-template-areas: - "#{$lte-prefix}app-sidebar #{$lte-prefix}app-header" - "#{$lte-prefix}app-sidebar #{$lte-prefix}app-main" - "#{$lte-prefix}app-sidebar #{$lte-prefix}app-footer"; - grid-template-rows: min-content 1fr min-content; - grid-template-columns: auto 1fr; - grid-gap: 0; - align-content: stretch; - align-items: stretch; - max-width: 100vw; - min-height: 100vh; - - > * { - min-width: 0; - } -} diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_bootstrap-variables.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_bootstrap-variables.scss deleted file mode 100644 index d4c851a9..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_bootstrap-variables.scss +++ /dev/null @@ -1,1766 +0,0 @@ -// AdminLTE Custom Variables Changes. -// Some of the variables are modified of Bootstrap Original Variables. -// You can find modified variables easily by searching keyword "// adminlte-modified" - -// Variables -// -// Variables should follow the `$component-state-property-size` formula for -// consistent naming. Ex: $nav-link-disabled-color and $modal-content-box-shadow-xs. - -// Color system - -// scss-docs-start gray-color-variables -$white: #fff !default; -$gray-100: #f8f9fa !default; -$gray-200: #e9ecef !default; -$gray-300: #dee2e6 !default; -$gray-400: #ced4da !default; -$gray-500: #adb5bd !default; -$gray-600: #6c757d !default; -$gray-700: #495057 !default; -$gray-800: #343a40 !default; -$gray-900: #212529 !default; -$black: #000 !default; -// scss-docs-end gray-color-variables - -// fusv-disable -// scss-docs-start gray-colors-map -$grays: ( - "100": $gray-100, - "200": $gray-200, - "300": $gray-300, - "400": $gray-400, - "500": $gray-500, - "600": $gray-600, - "700": $gray-700, - "800": $gray-800, - "900": $gray-900 -) !default; -// scss-docs-end gray-colors-map -// fusv-enable - -// scss-docs-start color-variables -$blue: #0d6efd !default; -$indigo: #6610f2 !default; -$purple: #6f42c1 !default; -$pink: #d63384 !default; -$red: #dc3545 !default; -$orange: #fd7e14 !default; -$yellow: #ffc107 !default; -$green: #198754 !default; -$teal: #20c997 !default; -$cyan: #0dcaf0 !default; -// scss-docs-end color-variables - -// scss-docs-start colors-map -$colors: ( - "blue": $blue, - "indigo": $indigo, - "purple": $purple, - "pink": $pink, - "red": $red, - "orange": $orange, - "yellow": $yellow, - "green": $green, - "teal": $teal, - "cyan": $cyan, - "black": $black, - "white": $white, - "gray": $gray-600, - "gray-dark": $gray-800 -) !default; -// scss-docs-end colors-map - -// The contrast ratio to reach against white, to determine if color changes from "light" to "dark". Acceptable values for WCAG 2.2 are 3, 4.5 and 7. -// See https://www.w3.org/TR/WCAG/#contrast-minimum -$min-contrast-ratio: 4.5 !default; - -// Customize the light and dark text colors for use in our color contrast function. -$color-contrast-dark: $black !default; -$color-contrast-light: $white !default; - -// fusv-disable -$blue-100: tint-color($blue, 80%) !default; -$blue-200: tint-color($blue, 60%) !default; -$blue-300: tint-color($blue, 40%) !default; -$blue-400: tint-color($blue, 20%) !default; -$blue-500: $blue !default; -$blue-600: shade-color($blue, 20%) !default; -$blue-700: shade-color($blue, 40%) !default; -$blue-800: shade-color($blue, 60%) !default; -$blue-900: shade-color($blue, 80%) !default; - -$indigo-100: tint-color($indigo, 80%) !default; -$indigo-200: tint-color($indigo, 60%) !default; -$indigo-300: tint-color($indigo, 40%) !default; -$indigo-400: tint-color($indigo, 20%) !default; -$indigo-500: $indigo !default; -$indigo-600: shade-color($indigo, 20%) !default; -$indigo-700: shade-color($indigo, 40%) !default; -$indigo-800: shade-color($indigo, 60%) !default; -$indigo-900: shade-color($indigo, 80%) !default; - -$purple-100: tint-color($purple, 80%) !default; -$purple-200: tint-color($purple, 60%) !default; -$purple-300: tint-color($purple, 40%) !default; -$purple-400: tint-color($purple, 20%) !default; -$purple-500: $purple !default; -$purple-600: shade-color($purple, 20%) !default; -$purple-700: shade-color($purple, 40%) !default; -$purple-800: shade-color($purple, 60%) !default; -$purple-900: shade-color($purple, 80%) !default; - -$pink-100: tint-color($pink, 80%) !default; -$pink-200: tint-color($pink, 60%) !default; -$pink-300: tint-color($pink, 40%) !default; -$pink-400: tint-color($pink, 20%) !default; -$pink-500: $pink !default; -$pink-600: shade-color($pink, 20%) !default; -$pink-700: shade-color($pink, 40%) !default; -$pink-800: shade-color($pink, 60%) !default; -$pink-900: shade-color($pink, 80%) !default; - -$red-100: tint-color($red, 80%) !default; -$red-200: tint-color($red, 60%) !default; -$red-300: tint-color($red, 40%) !default; -$red-400: tint-color($red, 20%) !default; -$red-500: $red !default; -$red-600: shade-color($red, 20%) !default; -$red-700: shade-color($red, 40%) !default; -$red-800: shade-color($red, 60%) !default; -$red-900: shade-color($red, 80%) !default; - -$orange-100: tint-color($orange, 80%) !default; -$orange-200: tint-color($orange, 60%) !default; -$orange-300: tint-color($orange, 40%) !default; -$orange-400: tint-color($orange, 20%) !default; -$orange-500: $orange !default; -$orange-600: shade-color($orange, 20%) !default; -$orange-700: shade-color($orange, 40%) !default; -$orange-800: shade-color($orange, 60%) !default; -$orange-900: shade-color($orange, 80%) !default; - -$yellow-100: tint-color($yellow, 80%) !default; -$yellow-200: tint-color($yellow, 60%) !default; -$yellow-300: tint-color($yellow, 40%) !default; -$yellow-400: tint-color($yellow, 20%) !default; -$yellow-500: $yellow !default; -$yellow-600: shade-color($yellow, 20%) !default; -$yellow-700: shade-color($yellow, 40%) !default; -$yellow-800: shade-color($yellow, 60%) !default; -$yellow-900: shade-color($yellow, 80%) !default; - -$green-100: tint-color($green, 80%) !default; -$green-200: tint-color($green, 60%) !default; -$green-300: tint-color($green, 40%) !default; -$green-400: tint-color($green, 20%) !default; -$green-500: $green !default; -$green-600: shade-color($green, 20%) !default; -$green-700: shade-color($green, 40%) !default; -$green-800: shade-color($green, 60%) !default; -$green-900: shade-color($green, 80%) !default; - -$teal-100: tint-color($teal, 80%) !default; -$teal-200: tint-color($teal, 60%) !default; -$teal-300: tint-color($teal, 40%) !default; -$teal-400: tint-color($teal, 20%) !default; -$teal-500: $teal !default; -$teal-600: shade-color($teal, 20%) !default; -$teal-700: shade-color($teal, 40%) !default; -$teal-800: shade-color($teal, 60%) !default; -$teal-900: shade-color($teal, 80%) !default; - -$cyan-100: tint-color($cyan, 80%) !default; -$cyan-200: tint-color($cyan, 60%) !default; -$cyan-300: tint-color($cyan, 40%) !default; -$cyan-400: tint-color($cyan, 20%) !default; -$cyan-500: $cyan !default; -$cyan-600: shade-color($cyan, 20%) !default; -$cyan-700: shade-color($cyan, 40%) !default; -$cyan-800: shade-color($cyan, 60%) !default; -$cyan-900: shade-color($cyan, 80%) !default; - -$blues: ( - "blue-100": $blue-100, - "blue-200": $blue-200, - "blue-300": $blue-300, - "blue-400": $blue-400, - "blue-500": $blue-500, - "blue-600": $blue-600, - "blue-700": $blue-700, - "blue-800": $blue-800, - "blue-900": $blue-900 -) !default; - -$indigos: ( - "indigo-100": $indigo-100, - "indigo-200": $indigo-200, - "indigo-300": $indigo-300, - "indigo-400": $indigo-400, - "indigo-500": $indigo-500, - "indigo-600": $indigo-600, - "indigo-700": $indigo-700, - "indigo-800": $indigo-800, - "indigo-900": $indigo-900 -) !default; - -$purples: ( - "purple-100": $purple-100, - "purple-200": $purple-200, - "purple-300": $purple-300, - "purple-400": $purple-400, - "purple-500": $purple-500, - "purple-600": $purple-600, - "purple-700": $purple-700, - "purple-800": $purple-800, - "purple-900": $purple-900 -) !default; - -$pinks: ( - "pink-100": $pink-100, - "pink-200": $pink-200, - "pink-300": $pink-300, - "pink-400": $pink-400, - "pink-500": $pink-500, - "pink-600": $pink-600, - "pink-700": $pink-700, - "pink-800": $pink-800, - "pink-900": $pink-900 -) !default; - -$reds: ( - "red-100": $red-100, - "red-200": $red-200, - "red-300": $red-300, - "red-400": $red-400, - "red-500": $red-500, - "red-600": $red-600, - "red-700": $red-700, - "red-800": $red-800, - "red-900": $red-900 -) !default; - -$oranges: ( - "orange-100": $orange-100, - "orange-200": $orange-200, - "orange-300": $orange-300, - "orange-400": $orange-400, - "orange-500": $orange-500, - "orange-600": $orange-600, - "orange-700": $orange-700, - "orange-800": $orange-800, - "orange-900": $orange-900 -) !default; - -$yellows: ( - "yellow-100": $yellow-100, - "yellow-200": $yellow-200, - "yellow-300": $yellow-300, - "yellow-400": $yellow-400, - "yellow-500": $yellow-500, - "yellow-600": $yellow-600, - "yellow-700": $yellow-700, - "yellow-800": $yellow-800, - "yellow-900": $yellow-900 -) !default; - -$greens: ( - "green-100": $green-100, - "green-200": $green-200, - "green-300": $green-300, - "green-400": $green-400, - "green-500": $green-500, - "green-600": $green-600, - "green-700": $green-700, - "green-800": $green-800, - "green-900": $green-900 -) !default; - -$teals: ( - "teal-100": $teal-100, - "teal-200": $teal-200, - "teal-300": $teal-300, - "teal-400": $teal-400, - "teal-500": $teal-500, - "teal-600": $teal-600, - "teal-700": $teal-700, - "teal-800": $teal-800, - "teal-900": $teal-900 -) !default; - -$cyans: ( - "cyan-100": $cyan-100, - "cyan-200": $cyan-200, - "cyan-300": $cyan-300, - "cyan-400": $cyan-400, - "cyan-500": $cyan-500, - "cyan-600": $cyan-600, - "cyan-700": $cyan-700, - "cyan-800": $cyan-800, - "cyan-900": $cyan-900 -) !default; -// fusv-enable - -// scss-docs-start theme-color-variables -$primary: $blue !default; -$secondary: $gray-600 !default; -$success: $green !default; -$info: $cyan !default; -$warning: $yellow !default; -$danger: $red !default; -$light: $gray-100 !default; -$dark: $gray-900 !default; -// scss-docs-end theme-color-variables - -// scss-docs-start theme-colors-map -$theme-colors: ( - "primary": $primary, - "secondary": $secondary, - "success": $success, - "info": $info, - "warning": $warning, - "danger": $danger, - "light": $light, - "dark": $dark -) !default; -// scss-docs-end theme-colors-map - -// scss-docs-start theme-text-variables -$primary-text-emphasis: shade-color($primary, 60%) !default; -$secondary-text-emphasis: shade-color($secondary, 60%) !default; -$success-text-emphasis: shade-color($success, 60%) !default; -$info-text-emphasis: shade-color($info, 60%) !default; -$warning-text-emphasis: shade-color($warning, 60%) !default; -$danger-text-emphasis: shade-color($danger, 60%) !default; -$light-text-emphasis: $gray-700 !default; -$dark-text-emphasis: $gray-700 !default; -// scss-docs-end theme-text-variables - -// scss-docs-start theme-bg-subtle-variables -$primary-bg-subtle: tint-color($primary, 80%) !default; -$secondary-bg-subtle: tint-color($secondary, 80%) !default; -$success-bg-subtle: tint-color($success, 80%) !default; -$info-bg-subtle: tint-color($info, 80%) !default; -$warning-bg-subtle: tint-color($warning, 80%) !default; -$danger-bg-subtle: tint-color($danger, 80%) !default; -$light-bg-subtle: mix($gray-100, $white) !default; -$dark-bg-subtle: $gray-400 !default; -// scss-docs-end theme-bg-subtle-variables - -// scss-docs-start theme-border-subtle-variables -$primary-border-subtle: tint-color($primary, 60%) !default; -$secondary-border-subtle: tint-color($secondary, 60%) !default; -$success-border-subtle: tint-color($success, 60%) !default; -$info-border-subtle: tint-color($info, 60%) !default; -$warning-border-subtle: tint-color($warning, 60%) !default; -$danger-border-subtle: tint-color($danger, 60%) !default; -$light-border-subtle: $gray-200 !default; -$dark-border-subtle: $gray-500 !default; -// scss-docs-end theme-border-subtle-variables - -// Characters which are escaped by the escape-svg function -$escaped-characters: ( - ("<", "%3c"), - (">", "%3e"), - ("#", "%23"), - ("(", "%28"), - (")", "%29"), -) !default; - -// Options -// -// Quickly modify global styling by enabling or disabling optional features. - -$enable-caret: true !default; -$enable-rounded: true !default; -$enable-shadows: true !default; // adminlte-modified -$enable-gradients: false !default; -$enable-transitions: true !default; -$enable-reduced-motion: true !default; -$enable-smooth-scroll: true !default; -$enable-grid-classes: true !default; -$enable-container-classes: true !default; -$enable-cssgrid: false !default; -$enable-button-pointers: true !default; -$enable-rfs: true !default; -$enable-validation-icons: true !default; -$enable-negative-margins: true !default; // adminlte-modified -$enable-deprecation-messages: true !default; -$enable-important-utilities: true !default; - -$enable-dark-mode: true !default; -$color-mode-type: data !default; // `data` or `media-query` - -// Prefix for :root CSS variables - -$variable-prefix: bs- !default; // Deprecated in v5.2.0 for the shorter `$prefix` -$prefix: $variable-prefix !default; - -// Gradient -// -// The gradient which is added to components if `$enable-gradients` is `true` -// This gradient is also added to elements with `.bg-gradient` -// scss-docs-start variable-gradient -$gradient: linear-gradient(180deg, rgba($white, .15), rgba($white, 0)) !default; -// scss-docs-end variable-gradient - -// Spacing -// -// Control the default styling of most Bootstrap elements by modifying these -// variables. Mostly focused on spacing. -// You can add more entries to the $spacers map, should you need more variation. - -// scss-docs-start spacer-variables-maps -$spacer: 1rem !default; -$spacers: ( - 0: 0, - 1: $spacer * .25, - 2: $spacer * .5, - 3: $spacer, - 4: $spacer * 1.5, - 5: $spacer * 3, -) !default; -// scss-docs-end spacer-variables-maps - -// Position -// -// Define the edge positioning anchors of the position utilities. - -// scss-docs-start position-map -$position-values: ( - 0: 0, - 50: 50%, - 100: 100% -) !default; -// scss-docs-end position-map - -// Body -// -// Settings for the `` element. - -$body-text-align: null !default; -$body-color: $gray-900 !default; -$body-bg: $white !default; - -$body-secondary-color: rgba($body-color, .75) !default; -$body-secondary-bg: $gray-200 !default; - -$body-tertiary-color: rgba($body-color, .5) !default; -$body-tertiary-bg: $gray-100 !default; - -$body-emphasis-color: $black !default; - -// Links -// -// Style anchor elements. - -$link-color: $primary !default; -$link-decoration: underline !default; -$link-shade-percentage: 20% !default; -$link-hover-color: shift-color($link-color, $link-shade-percentage) !default; -$link-hover-decoration: null !default; - -$stretched-link-pseudo-element: after !default; -$stretched-link-z-index: 1 !default; - -// Icon links -// scss-docs-start icon-link-variables -$icon-link-gap: .375rem !default; -$icon-link-underline-offset: .25em !default; -$icon-link-icon-size: 1em !default; -$icon-link-icon-transition: .2s ease-in-out transform !default; -$icon-link-icon-transform: translate3d(.25em, 0, 0) !default; -// scss-docs-end icon-link-variables - -// Paragraphs -// -// Style p element. - -$paragraph-margin-bottom: 1rem !default; - - -// Grid breakpoints -// -// Define the minimum dimensions at which your layout will change, -// adapting to different screen sizes, for use in media queries. - -// scss-docs-start grid-breakpoints -$grid-breakpoints: ( - xs: 0, - sm: 576px, - md: 768px, - lg: 992px, - xl: 1200px, - xxl: 1400px -) !default; -// scss-docs-end grid-breakpoints - -@include _assert-ascending($grid-breakpoints, "$grid-breakpoints"); -@include _assert-starts-at-zero($grid-breakpoints, "$grid-breakpoints"); - - -// Grid containers -// -// Define the maximum width of `.container` for different screen sizes. - -// scss-docs-start container-max-widths -$container-max-widths: ( - sm: 540px, - md: 720px, - lg: 960px, - xl: 1140px, - xxl: 1320px -) !default; -// scss-docs-end container-max-widths - -@include _assert-ascending($container-max-widths, "$container-max-widths"); - - -// Grid columns -// -// Set the number of columns and specify the width of the gutters. - -$grid-columns: 12 !default; -$grid-gutter-width: 1.5rem !default; -$grid-row-columns: 6 !default; - -// Container padding - -$container-padding-x: $grid-gutter-width !default; - - -// Components -// -// Define common padding and border radius sizes and more. - -// scss-docs-start border-variables -$border-width: 1px !default; -$border-widths: ( - 1: 1px, - 2: 2px, - 3: 3px, - 4: 4px, - 5: 5px -) !default; -$border-style: solid !default; -$border-color: $gray-300 !default; -$border-color-translucent: rgba($black, .175) !default; -// scss-docs-end border-variables - -// scss-docs-start border-radius-variables -$border-radius: .375rem !default; -$border-radius-sm: .25rem !default; -$border-radius-lg: .5rem !default; -$border-radius-xl: 1rem !default; -$border-radius-xxl: 2rem !default; -$border-radius-pill: 50rem !default; -// scss-docs-end border-radius-variables -// fusv-disable -$border-radius-2xl: $border-radius-xxl !default; // Deprecated in v5.3.0 -// fusv-enable - -// scss-docs-start box-shadow-variables -$box-shadow: 0 .5rem 1rem rgba($black, .15) !default; -$box-shadow-sm: 0 .125rem .25rem rgba($black, .075) !default; -$box-shadow-lg: 0 1rem 3rem rgba($black, .175) !default; -$box-shadow-inset: inset 0 1px 2px rgba($black, .075) !default; -// scss-docs-end box-shadow-variables - -$component-active-color: $white !default; -$component-active-bg: $primary !default; - -// scss-docs-start focus-ring-variables -$focus-ring-width: .25rem !default; -$focus-ring-opacity: .25 !default; -$focus-ring-color: rgba($primary, $focus-ring-opacity) !default; -$focus-ring-blur: 0 !default; -$focus-ring-box-shadow: 0 0 $focus-ring-blur $focus-ring-width $focus-ring-color !default; -// scss-docs-end focus-ring-variables - -// scss-docs-start caret-variables -$caret-width: .3em !default; -$caret-vertical-align: $caret-width * .85 !default; -$caret-spacing: $caret-width * .85 !default; -// scss-docs-end caret-variables - -$transition-base: all .2s ease-in-out !default; -$transition-fade: opacity .15s linear !default; -// scss-docs-start collapse-transition -$transition-collapse: height .35s ease !default; -$transition-collapse-width: width .35s ease !default; -// scss-docs-end collapse-transition - -// stylelint-disable function-disallowed-list -// scss-docs-start aspect-ratios -$aspect-ratios: ( - "1x1": 100%, - "4x3": calc(3 / 4 * 100%), - "16x9": calc(9 / 16 * 100%), - "21x9": calc(9 / 21 * 100%) -) !default; -// scss-docs-end aspect-ratios -// stylelint-enable function-disallowed-list - -// Typography -// -// Font, line-height, and color for body text, headings, and more. - -// scss-docs-start font-variables -// stylelint-disable value-keyword-case -$font-family-sans-serif: "Source Sans 3", system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", "Liberation Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" !default; // adminlte-modified -$font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !default; -// stylelint-enable value-keyword-case -$font-family-base: var(--#{$prefix}font-sans-serif) !default; -$font-family-code: var(--#{$prefix}font-monospace) !default; - -// $font-size-root affects the value of `rem`, which is used for as well font sizes, paddings, and margins -// $font-size-base affects the font size of the body text -$font-size-root: null !default; -$font-size-base: 1rem !default; // Assumes the browser default, typically `16px` -$font-size-sm: $font-size-base * .875 !default; -$font-size-lg: $font-size-base * 1.25 !default; -$font-size-xs: $font-size-base * .75 !default; // adminlte-modified -$font-size-xl: $font-size-base * 2 !default; // adminlte-modified - -$font-weight-lighter: lighter !default; -$font-weight-light: 300 !default; -$font-weight-normal: 400 !default; -$font-weight-medium: 500 !default; -$font-weight-semibold: 600 !default; -$font-weight-bold: 700 !default; -$font-weight-bolder: bolder !default; - -$font-weight-base: $font-weight-normal !default; - -$line-height-base: 1.5 !default; -$line-height-sm: 1.25 !default; -$line-height-lg: 2 !default; - -$h1-font-size: $font-size-base * 2.5 !default; -$h2-font-size: $font-size-base * 2 !default; -$h3-font-size: $font-size-base * 1.75 !default; -$h4-font-size: $font-size-base * 1.5 !default; -$h5-font-size: $font-size-base * 1.25 !default; -$h6-font-size: $font-size-base !default; -$h7-font-size: $font-size-sm !default; // adminlte-modified -$h8-font-size: $font-size-xs !default; // adminlte-modified -// scss-docs-end font-variables - -// scss-docs-start font-sizes -$font-sizes: ( - 1: $h1-font-size, - 2: $h2-font-size, - 3: $h3-font-size, - 4: $h4-font-size, - 5: $h5-font-size, - 6: $h6-font-size, - 7: $h7-font-size, // adminlte-modified - 8: $h8-font-size // adminlte-modified -) !default; -// scss-docs-end font-sizes - -// scss-docs-start headings-variables -$headings-margin-bottom: $spacer * .5 !default; -$headings-font-family: null !default; -$headings-font-style: null !default; -$headings-font-weight: 500 !default; -$headings-line-height: 1.2 !default; -$headings-color: inherit !default; -// scss-docs-end headings-variables - -// scss-docs-start display-headings -$display-font-sizes: ( - 1: 5rem, - 2: 4.5rem, - 3: 4rem, - 4: 3.5rem, - 5: 3rem, - 6: 2.5rem -) !default; - -$display-font-family: null !default; -$display-font-style: null !default; -$display-font-weight: 300 !default; -$display-line-height: $headings-line-height !default; -// scss-docs-end display-headings - -// scss-docs-start type-variables -$lead-font-size: $font-size-base * 1.25 !default; -$lead-font-weight: 300 !default; - -$small-font-size: .875em !default; - -$sub-sup-font-size: .75em !default; - -// fusv-disable -$text-muted: var(--#{$prefix}secondary-color) !default; // Deprecated in 5.3.0 -// fusv-enable - -$initialism-font-size: $small-font-size !default; - -$blockquote-margin-y: $spacer !default; -$blockquote-font-size: $font-size-base * 1.25 !default; -$blockquote-footer-color: $gray-600 !default; -$blockquote-footer-font-size: $small-font-size !default; - -$hr-margin-y: $spacer !default; -$hr-color: inherit !default; - -// fusv-disable -$hr-bg-color: null !default; // Deprecated in v5.2.0 -$hr-height: null !default; // Deprecated in v5.2.0 -// fusv-enable - -$hr-border-color: null !default; // Allows for inherited colors -$hr-border-width: var(--#{$prefix}border-width) !default; -$hr-opacity: .25 !default; - -// scss-docs-start vr-variables -$vr-border-width: var(--#{$prefix}border-width) !default; -// scss-docs-end vr-variables - -$legend-margin-bottom: .5rem !default; -$legend-font-size: 1.5rem !default; -$legend-font-weight: null !default; - -$dt-font-weight: $font-weight-bold !default; - -$list-inline-padding: .5rem !default; - -$mark-padding: .1875em !default; -$mark-color: $body-color !default; -$mark-bg: $yellow-100 !default; -// scss-docs-end type-variables - - -// Tables -// -// Customizes the `.table` component with basic values, each used across all table variations. - -// scss-docs-start table-variables -$table-cell-padding-y: .5rem !default; -$table-cell-padding-x: .5rem !default; -$table-cell-padding-y-sm: .25rem !default; -$table-cell-padding-x-sm: .25rem !default; - -$table-cell-vertical-align: top !default; - -$table-color: var(--#{$prefix}emphasis-color) !default; -$table-bg: var(--#{$prefix}body-bg) !default; -$table-accent-bg: transparent !default; - -$table-th-font-weight: null !default; - -$table-striped-color: $table-color !default; -$table-striped-bg-factor: .05 !default; -$table-striped-bg: rgba(var(--#{$prefix}emphasis-color-rgb), $table-striped-bg-factor) !default; - -$table-active-color: $table-color !default; -$table-active-bg-factor: .1 !default; -$table-active-bg: rgba(var(--#{$prefix}emphasis-color-rgb), $table-active-bg-factor) !default; - -$table-hover-color: $table-color !default; -$table-hover-bg-factor: .075 !default; -$table-hover-bg: rgba(var(--#{$prefix}emphasis-color-rgb), $table-hover-bg-factor) !default; - -$table-border-factor: .2 !default; -$table-border-width: var(--#{$prefix}border-width) !default; -$table-border-color: var(--#{$prefix}border-color) !default; - -$table-striped-order: odd !default; -$table-striped-columns-order: even !default; - -$table-group-separator-color: currentcolor !default; - -$table-caption-color: var(--#{$prefix}secondary-color) !default; - -$table-bg-scale: -80% !default; -// scss-docs-end table-variables - -// scss-docs-start table-loop -$table-variants: ( - "primary": shift-color($primary, $table-bg-scale), - "secondary": shift-color($secondary, $table-bg-scale), - "success": shift-color($success, $table-bg-scale), - "info": shift-color($info, $table-bg-scale), - "warning": shift-color($warning, $table-bg-scale), - "danger": shift-color($danger, $table-bg-scale), - "light": $light, - "dark": $dark, -) !default; -// scss-docs-end table-loop - - -// Buttons + Forms -// -// Shared variables that are reassigned to `$input-` and `$btn-` specific variables. - -// scss-docs-start input-btn-variables -$input-btn-padding-y: .375rem !default; -$input-btn-padding-x: .75rem !default; -$input-btn-font-family: null !default; -$input-btn-font-size: $font-size-base !default; -$input-btn-line-height: $line-height-base !default; - -$input-btn-focus-width: $focus-ring-width !default; -$input-btn-focus-color-opacity: $focus-ring-opacity !default; -$input-btn-focus-color: $focus-ring-color !default; -$input-btn-focus-blur: $focus-ring-blur !default; -$input-btn-focus-box-shadow: $focus-ring-box-shadow !default; - -$input-btn-padding-y-sm: .25rem !default; -$input-btn-padding-x-sm: .5rem !default; -$input-btn-font-size-sm: $font-size-sm !default; - -$input-btn-padding-y-lg: .5rem !default; -$input-btn-padding-x-lg: 1rem !default; -$input-btn-font-size-lg: $font-size-lg !default; - -$input-btn-border-width: var(--#{$prefix}border-width) !default; -// scss-docs-end input-btn-variables - - -// Buttons -// -// For each of Bootstrap's buttons, define text, background, and border color. - -// scss-docs-start btn-variables -$btn-color: var(--#{$prefix}body-color) !default; -$btn-padding-y: $input-btn-padding-y !default; -$btn-padding-x: $input-btn-padding-x !default; -$btn-font-family: $input-btn-font-family !default; -$btn-font-size: $input-btn-font-size !default; -$btn-line-height: $input-btn-line-height !default; -$btn-white-space: null !default; // Set to `nowrap` to prevent text wrapping - -$btn-padding-y-sm: $input-btn-padding-y-sm !default; -$btn-padding-x-sm: $input-btn-padding-x-sm !default; -$btn-font-size-sm: $input-btn-font-size-sm !default; - -$btn-padding-y-lg: $input-btn-padding-y-lg !default; -$btn-padding-x-lg: $input-btn-padding-x-lg !default; -$btn-font-size-lg: $input-btn-font-size-lg !default; - -$btn-border-width: $input-btn-border-width !default; - -$btn-font-weight: $font-weight-normal !default; -$btn-box-shadow: inset 0 1px 0 rgba($white, .15), 0 1px 1px rgba($black, .075) !default; -$btn-focus-width: $input-btn-focus-width !default; -$btn-focus-box-shadow: $input-btn-focus-box-shadow !default; -$btn-disabled-opacity: .65 !default; -$btn-active-box-shadow: inset 0 3px 5px rgba($black, .125) !default; - -$btn-link-color: var(--#{$prefix}link-color) !default; -$btn-link-hover-color: var(--#{$prefix}link-hover-color) !default; -$btn-link-disabled-color: $gray-600 !default; -$btn-link-focus-shadow-rgb: to-rgb(mix(color-contrast($link-color), $link-color, 15%)) !default; - -// Allows for customizing button radius independently from global border radius -$btn-border-radius: var(--#{$prefix}border-radius) !default; -$btn-border-radius-sm: var(--#{$prefix}border-radius-sm) !default; -$btn-border-radius-lg: var(--#{$prefix}border-radius-lg) !default; - -$btn-transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out !default; - -$btn-hover-bg-shade-amount: 15% !default; -$btn-hover-bg-tint-amount: 15% !default; -$btn-hover-border-shade-amount: 20% !default; -$btn-hover-border-tint-amount: 10% !default; -$btn-active-bg-shade-amount: 20% !default; -$btn-active-bg-tint-amount: 20% !default; -$btn-active-border-shade-amount: 25% !default; -$btn-active-border-tint-amount: 10% !default; -// scss-docs-end btn-variables - - -// Forms - -// scss-docs-start form-text-variables -$form-text-margin-top: .25rem !default; -$form-text-font-size: $small-font-size !default; -$form-text-font-style: null !default; -$form-text-font-weight: null !default; -$form-text-color: var(--#{$prefix}secondary-color) !default; -// scss-docs-end form-text-variables - -// scss-docs-start form-label-variables -$form-label-margin-bottom: .5rem !default; -$form-label-font-size: null !default; -$form-label-font-style: null !default; -$form-label-font-weight: null !default; -$form-label-color: null !default; -// scss-docs-end form-label-variables - -// scss-docs-start form-input-variables -$input-padding-y: $input-btn-padding-y !default; -$input-padding-x: $input-btn-padding-x !default; -$input-font-family: $input-btn-font-family !default; -$input-font-size: $input-btn-font-size !default; -$input-font-weight: $font-weight-base !default; -$input-line-height: $input-btn-line-height !default; - -$input-padding-y-sm: $input-btn-padding-y-sm !default; -$input-padding-x-sm: $input-btn-padding-x-sm !default; -$input-font-size-sm: $input-btn-font-size-sm !default; - -$input-padding-y-lg: $input-btn-padding-y-lg !default; -$input-padding-x-lg: $input-btn-padding-x-lg !default; -$input-font-size-lg: $input-btn-font-size-lg !default; - -$input-bg: var(--#{$prefix}body-bg) !default; -$input-disabled-color: null !default; -$input-disabled-bg: var(--#{$prefix}secondary-bg) !default; -$input-disabled-border-color: null !default; - -$input-color: var(--#{$prefix}body-color) !default; -$input-border-color: var(--#{$prefix}border-color) !default; -$input-border-width: $input-btn-border-width !default; -$input-box-shadow: var(--#{$prefix}box-shadow-inset) !default; - -$input-border-radius: var(--#{$prefix}border-radius) !default; -$input-border-radius-sm: var(--#{$prefix}border-radius-sm) !default; -$input-border-radius-lg: var(--#{$prefix}border-radius-lg) !default; - -$input-focus-bg: $input-bg !default; -$input-focus-border-color: tint-color($component-active-bg, 50%) !default; -$input-focus-color: $input-color !default; -$input-focus-width: $input-btn-focus-width !default; -$input-focus-box-shadow: $input-btn-focus-box-shadow !default; - -$input-placeholder-color: var(--#{$prefix}secondary-color) !default; -$input-plaintext-color: var(--#{$prefix}body-color) !default; - -$input-height-border: calc(#{$input-border-width} * 2) !default; // stylelint-disable-line function-disallowed-list - -$input-height-inner: add($input-line-height * 1em, $input-padding-y * 2) !default; -$input-height-inner-half: add($input-line-height * .5em, $input-padding-y) !default; -$input-height-inner-quarter: add($input-line-height * .25em, $input-padding-y * .5) !default; - -$input-height: add($input-line-height * 1em, add($input-padding-y * 2, $input-height-border, false)) !default; -$input-height-sm: add($input-line-height * 1em, add($input-padding-y-sm * 2, $input-height-border, false)) !default; -$input-height-lg: add($input-line-height * 1em, add($input-padding-y-lg * 2, $input-height-border, false)) !default; - -$input-transition: border-color .15s ease-in-out, box-shadow .15s ease-in-out !default; - -$form-color-width: 3rem !default; -// scss-docs-end form-input-variables - -// scss-docs-start form-check-variables -$form-check-input-width: 1em !default; -$form-check-min-height: $font-size-base * $line-height-base !default; -$form-check-padding-start: $form-check-input-width + .5em !default; -$form-check-margin-bottom: .125rem !default; -$form-check-label-color: null !default; -$form-check-label-cursor: null !default; -$form-check-transition: null !default; - -$form-check-input-active-filter: brightness(90%) !default; - -$form-check-input-bg: $input-bg !default; -$form-check-input-border: var(--#{$prefix}border-width) solid var(--#{$prefix}border-color) !default; -$form-check-input-border-radius: .25em !default; -$form-check-radio-border-radius: 50% !default; -$form-check-input-focus-border: $input-focus-border-color !default; -$form-check-input-focus-box-shadow: $focus-ring-box-shadow !default; - -$form-check-input-checked-color: $component-active-color !default; -$form-check-input-checked-bg-color: $component-active-bg !default; -$form-check-input-checked-border-color: $form-check-input-checked-bg-color !default; -$form-check-input-checked-bg-image: url("data:image/svg+xml,") !default; -$form-check-radio-checked-bg-image: url("data:image/svg+xml,") !default; - -$form-check-input-indeterminate-color: $component-active-color !default; -$form-check-input-indeterminate-bg-color: $component-active-bg !default; -$form-check-input-indeterminate-border-color: $form-check-input-indeterminate-bg-color !default; -$form-check-input-indeterminate-bg-image: url("data:image/svg+xml,") !default; - -$form-check-input-disabled-opacity: .5 !default; -$form-check-label-disabled-opacity: $form-check-input-disabled-opacity !default; -$form-check-btn-check-disabled-opacity: $btn-disabled-opacity !default; - -$form-check-inline-margin-end: 1rem !default; -// scss-docs-end form-check-variables - -// scss-docs-start form-switch-variables -$form-switch-color: rgba($black, .25) !default; -$form-switch-width: 2em !default; -$form-switch-padding-start: $form-switch-width + .5em !default; -$form-switch-bg-image: url("data:image/svg+xml,") !default; -$form-switch-border-radius: $form-switch-width !default; -$form-switch-transition: background-position .15s ease-in-out !default; - -$form-switch-focus-color: $input-focus-border-color !default; -$form-switch-focus-bg-image: url("data:image/svg+xml,") !default; - -$form-switch-checked-color: $component-active-color !default; -$form-switch-checked-bg-image: url("data:image/svg+xml,") !default; -$form-switch-checked-bg-position: right center !default; -// scss-docs-end form-switch-variables - -// scss-docs-start input-group-variables -$input-group-addon-padding-y: $input-padding-y !default; -$input-group-addon-padding-x: $input-padding-x !default; -$input-group-addon-font-weight: $input-font-weight !default; -$input-group-addon-color: $input-color !default; -$input-group-addon-bg: var(--#{$prefix}tertiary-bg) !default; -$input-group-addon-border-color: $input-border-color !default; -// scss-docs-end input-group-variables - -// scss-docs-start form-select-variables -$form-select-padding-y: $input-padding-y !default; -$form-select-padding-x: $input-padding-x !default; -$form-select-font-family: $input-font-family !default; -$form-select-font-size: $input-font-size !default; -$form-select-indicator-padding: $form-select-padding-x * 3 !default; // Extra padding for background-image -$form-select-font-weight: $input-font-weight !default; -$form-select-line-height: $input-line-height !default; -$form-select-color: $input-color !default; -$form-select-bg: $input-bg !default; -$form-select-disabled-color: null !default; -$form-select-disabled-bg: $input-disabled-bg !default; -$form-select-disabled-border-color: $input-disabled-border-color !default; -$form-select-bg-position: right $form-select-padding-x center !default; -$form-select-bg-size: 16px 12px !default; // In pixels because image dimensions -$form-select-indicator-color: $gray-800 !default; -$form-select-indicator: url("data:image/svg+xml,") !default; - -$form-select-feedback-icon-padding-end: $form-select-padding-x * 2.5 + $form-select-indicator-padding !default; -$form-select-feedback-icon-position: center right $form-select-indicator-padding !default; -$form-select-feedback-icon-size: $input-height-inner-half $input-height-inner-half !default; - -$form-select-border-width: $input-border-width !default; -$form-select-border-color: $input-border-color !default; -$form-select-border-radius: $input-border-radius !default; -$form-select-box-shadow: var(--#{$prefix}box-shadow-inset) !default; - -$form-select-focus-border-color: $input-focus-border-color !default; -$form-select-focus-width: $input-focus-width !default; -$form-select-focus-box-shadow: 0 0 0 $form-select-focus-width $input-btn-focus-color !default; - -$form-select-padding-y-sm: $input-padding-y-sm !default; -$form-select-padding-x-sm: $input-padding-x-sm !default; -$form-select-font-size-sm: $input-font-size-sm !default; -$form-select-border-radius-sm: $input-border-radius-sm !default; - -$form-select-padding-y-lg: $input-padding-y-lg !default; -$form-select-padding-x-lg: $input-padding-x-lg !default; -$form-select-font-size-lg: $input-font-size-lg !default; -$form-select-border-radius-lg: $input-border-radius-lg !default; - -$form-select-transition: $input-transition !default; -// scss-docs-end form-select-variables - -// scss-docs-start form-range-variables -$form-range-track-width: 100% !default; -$form-range-track-height: .5rem !default; -$form-range-track-cursor: pointer !default; -$form-range-track-bg: var(--#{$prefix}secondary-bg) !default; -$form-range-track-border-radius: 1rem !default; -$form-range-track-box-shadow: var(--#{$prefix}box-shadow-inset) !default; - -$form-range-thumb-width: 1rem !default; -$form-range-thumb-height: $form-range-thumb-width !default; -$form-range-thumb-bg: $component-active-bg !default; -$form-range-thumb-border: 0 !default; -$form-range-thumb-border-radius: 1rem !default; -$form-range-thumb-box-shadow: 0 .1rem .25rem rgba($black, .1) !default; -$form-range-thumb-focus-box-shadow: 0 0 0 1px $body-bg, $input-focus-box-shadow !default; -$form-range-thumb-focus-box-shadow-width: $input-focus-width !default; // For focus box shadow issue in Edge -$form-range-thumb-active-bg: tint-color($component-active-bg, 70%) !default; -$form-range-thumb-disabled-bg: var(--#{$prefix}secondary-color) !default; -$form-range-thumb-transition: background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out !default; -// scss-docs-end form-range-variables - -// scss-docs-start form-file-variables -$form-file-button-color: $input-color !default; -$form-file-button-bg: var(--#{$prefix}tertiary-bg) !default; -$form-file-button-hover-bg: var(--#{$prefix}secondary-bg) !default; -// scss-docs-end form-file-variables - -// scss-docs-start form-floating-variables -$form-floating-height: add(3.5rem, $input-height-border) !default; -$form-floating-line-height: 1.25 !default; -$form-floating-padding-x: $input-padding-x !default; -$form-floating-padding-y: 1rem !default; -$form-floating-input-padding-t: 1.625rem !default; -$form-floating-input-padding-b: .625rem !default; -$form-floating-label-height: 1.5em !default; -$form-floating-label-opacity: .65 !default; -$form-floating-label-transform: scale(.85) translateY(-.5rem) translateX(.15rem) !default; -$form-floating-label-disabled-color: $gray-600 !default; -$form-floating-transition: opacity .1s ease-in-out, transform .1s ease-in-out !default; -// scss-docs-end form-floating-variables - -// Form validation - -// scss-docs-start form-feedback-variables -$form-feedback-margin-top: $form-text-margin-top !default; -$form-feedback-font-size: $form-text-font-size !default; -$form-feedback-font-style: $form-text-font-style !default; -$form-feedback-valid-color: $success !default; -$form-feedback-invalid-color: $danger !default; - -$form-feedback-icon-valid-color: $form-feedback-valid-color !default; -$form-feedback-icon-valid: url("data:image/svg+xml,") !default; -$form-feedback-icon-invalid-color: $form-feedback-invalid-color !default; -$form-feedback-icon-invalid: url("data:image/svg+xml,") !default; -// scss-docs-end form-feedback-variables - -// scss-docs-start form-validation-colors -$form-valid-color: $form-feedback-valid-color !default; -$form-valid-border-color: $form-feedback-valid-color !default; -$form-invalid-color: $form-feedback-invalid-color !default; -$form-invalid-border-color: $form-feedback-invalid-color !default; -// scss-docs-end form-validation-colors - -// scss-docs-start form-validation-states -$form-validation-states: ( - "valid": ( - "color": var(--#{$prefix}form-valid-color), - "icon": $form-feedback-icon-valid, - "tooltip-color": #fff, - "tooltip-bg-color": var(--#{$prefix}success), - "focus-box-shadow": 0 0 $input-btn-focus-blur $input-focus-width rgba(var(--#{$prefix}success-rgb), $input-btn-focus-color-opacity), - "border-color": var(--#{$prefix}form-valid-border-color), - ), - "invalid": ( - "color": var(--#{$prefix}form-invalid-color), - "icon": $form-feedback-icon-invalid, - "tooltip-color": #fff, - "tooltip-bg-color": var(--#{$prefix}danger), - "focus-box-shadow": 0 0 $input-btn-focus-blur $input-focus-width rgba(var(--#{$prefix}danger-rgb), $input-btn-focus-color-opacity), - "border-color": var(--#{$prefix}form-invalid-border-color), - ) -) !default; -// scss-docs-end form-validation-states - -// Z-index master list -// -// Warning: Avoid customizing these values. They're used for a bird's eye view -// of components dependent on the z-axis and are designed to all work together. - -// scss-docs-start zindex-stack -$zindex-dropdown: 1000 !default; -$zindex-sticky: 1020 !default; -$zindex-fixed: 1030 !default; -$zindex-offcanvas-backdrop: 1040 !default; -$zindex-offcanvas: 1045 !default; -$zindex-modal-backdrop: 1050 !default; -$zindex-modal: 1055 !default; -$zindex-popover: 1070 !default; -$zindex-tooltip: 1080 !default; -$zindex-toast: 1090 !default; -// scss-docs-end zindex-stack - -// scss-docs-start zindex-levels-map -$zindex-levels: ( - n1: -1, - 0: 0, - 1: 1, - 2: 2, - 3: 3 -) !default; -// scss-docs-end zindex-levels-map - - -// Navs - -// scss-docs-start nav-variables -$nav-link-padding-y: .5rem !default; -$nav-link-padding-x: 1rem !default; -$nav-link-font-size: null !default; -$nav-link-font-weight: null !default; -$nav-link-color: var(--#{$prefix}link-color) !default; -$nav-link-hover-color: var(--#{$prefix}link-hover-color) !default; -$nav-link-transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out !default; -$nav-link-disabled-color: var(--#{$prefix}secondary-color) !default; -$nav-link-focus-box-shadow: $focus-ring-box-shadow !default; - -$nav-tabs-border-color: var(--#{$prefix}border-color) !default; -$nav-tabs-border-width: var(--#{$prefix}border-width) !default; -$nav-tabs-border-radius: var(--#{$prefix}border-radius) !default; -$nav-tabs-link-hover-border-color: var(--#{$prefix}secondary-bg) var(--#{$prefix}secondary-bg) $nav-tabs-border-color !default; -$nav-tabs-link-active-color: var(--#{$prefix}emphasis-color) !default; -$nav-tabs-link-active-bg: var(--#{$prefix}body-bg) !default; -$nav-tabs-link-active-border-color: var(--#{$prefix}border-color) var(--#{$prefix}border-color) $nav-tabs-link-active-bg !default; - -$nav-pills-border-radius: var(--#{$prefix}border-radius) !default; -$nav-pills-link-active-color: $component-active-color !default; -$nav-pills-link-active-bg: $component-active-bg !default; - -$nav-underline-gap: 1rem !default; -$nav-underline-border-width: .125rem !default; -$nav-underline-link-active-color: var(--#{$prefix}emphasis-color) !default; -// scss-docs-end nav-variables - - -// Navbar - -// scss-docs-start navbar-variables -$navbar-padding-y: $spacer * .5 !default; -$navbar-padding-x: null !default; - -$navbar-nav-link-padding-x: 1rem !default; // adminlte-modified - -$navbar-brand-font-size: $font-size-lg !default; -// Compute the navbar-brand padding-y so the navbar-brand will have the same height as navbar-text and nav-link -$nav-link-height: $font-size-base * $line-height-base + $nav-link-padding-y * 2 !default; -$navbar-brand-height: $navbar-brand-font-size * $line-height-base !default; -$navbar-brand-padding-y: ($nav-link-height - $navbar-brand-height) * .5 !default; -$navbar-brand-margin-end: 1rem !default; - -$navbar-toggler-padding-y: .25rem !default; -$navbar-toggler-padding-x: .75rem !default; -$navbar-toggler-font-size: $font-size-lg !default; -$navbar-toggler-border-radius: $btn-border-radius !default; -$navbar-toggler-focus-width: $btn-focus-width !default; -$navbar-toggler-transition: box-shadow .15s ease-in-out !default; - -$navbar-light-color: rgba(var(--#{$prefix}emphasis-color-rgb), .65) !default; -$navbar-light-hover-color: rgba(var(--#{$prefix}emphasis-color-rgb), .8) !default; -$navbar-light-active-color: rgba(var(--#{$prefix}emphasis-color-rgb), 1) !default; -$navbar-light-disabled-color: rgba(var(--#{$prefix}emphasis-color-rgb), .3) !default; -$navbar-light-icon-color: rgba($body-color, .75) !default; -$navbar-light-toggler-icon-bg: url("data:image/svg+xml,") !default; -$navbar-light-toggler-border-color: rgba(var(--#{$prefix}emphasis-color-rgb), .15) !default; -$navbar-light-brand-color: $navbar-light-active-color !default; -$navbar-light-brand-hover-color: $navbar-light-active-color !default; -// scss-docs-end navbar-variables - -// scss-docs-start navbar-dark-variables -$navbar-dark-color: rgba($white, .55) !default; -$navbar-dark-hover-color: rgba($white, .75) !default; -$navbar-dark-active-color: $white !default; -$navbar-dark-disabled-color: rgba($white, .25) !default; -$navbar-dark-icon-color: $navbar-dark-color !default; -$navbar-dark-toggler-icon-bg: url("data:image/svg+xml,") !default; -$navbar-dark-toggler-border-color: rgba($white, .1) !default; -$navbar-dark-brand-color: $navbar-dark-active-color !default; -$navbar-dark-brand-hover-color: $navbar-dark-active-color !default; -// scss-docs-end navbar-dark-variables - - -// Dropdowns -// -// Dropdown menu container and contents. - -// scss-docs-start dropdown-variables -$dropdown-min-width: 10rem !default; -$dropdown-padding-x: 0 !default; -$dropdown-padding-y: .5rem !default; -$dropdown-spacer: .125rem !default; -$dropdown-font-size: $font-size-base !default; -$dropdown-color: var(--#{$prefix}body-color) !default; -$dropdown-bg: var(--#{$prefix}body-bg) !default; -$dropdown-border-color: var(--#{$prefix}border-color-translucent) !default; -$dropdown-border-radius: var(--#{$prefix}border-radius) !default; -$dropdown-border-width: var(--#{$prefix}border-width) !default; -$dropdown-inner-border-radius: calc(#{$dropdown-border-radius} - #{$dropdown-border-width}) !default; // stylelint-disable-line function-disallowed-list -$dropdown-divider-bg: $dropdown-border-color !default; -$dropdown-divider-margin-y: $spacer * .5 !default; -$dropdown-box-shadow: var(--#{$prefix}box-shadow) !default; - -$dropdown-link-color: var(--#{$prefix}body-color) !default; -$dropdown-link-hover-color: $dropdown-link-color !default; -$dropdown-link-hover-bg: var(--#{$prefix}tertiary-bg) !default; - -$dropdown-link-active-color: $component-active-color !default; -$dropdown-link-active-bg: $component-active-bg !default; - -$dropdown-link-disabled-color: var(--#{$prefix}tertiary-color) !default; - -$dropdown-item-padding-y: $spacer * .25 !default; -$dropdown-item-padding-x: $spacer !default; - -$dropdown-header-color: $gray-600 !default; -$dropdown-header-padding-x: $dropdown-item-padding-x !default; -$dropdown-header-padding-y: $dropdown-padding-y !default; -// fusv-disable -$dropdown-header-padding: $dropdown-header-padding-y $dropdown-header-padding-x !default; // Deprecated in v5.2.0 -// fusv-enable -// scss-docs-end dropdown-variables - -// scss-docs-start dropdown-dark-variables -$dropdown-dark-color: $gray-300 !default; -$dropdown-dark-bg: $gray-800 !default; -$dropdown-dark-border-color: $dropdown-border-color !default; -$dropdown-dark-divider-bg: $dropdown-divider-bg !default; -$dropdown-dark-box-shadow: null !default; -$dropdown-dark-link-color: $dropdown-dark-color !default; -$dropdown-dark-link-hover-color: $white !default; -$dropdown-dark-link-hover-bg: rgba($white, .15) !default; -$dropdown-dark-link-active-color: $dropdown-link-active-color !default; -$dropdown-dark-link-active-bg: $dropdown-link-active-bg !default; -$dropdown-dark-link-disabled-color: $gray-500 !default; -$dropdown-dark-header-color: $gray-500 !default; -// scss-docs-end dropdown-dark-variables - - -// Pagination - -// scss-docs-start pagination-variables -$pagination-padding-y: .375rem !default; -$pagination-padding-x: .75rem !default; -$pagination-padding-y-sm: .25rem !default; -$pagination-padding-x-sm: .5rem !default; -$pagination-padding-y-lg: .75rem !default; -$pagination-padding-x-lg: 1.5rem !default; - -$pagination-font-size: $font-size-base !default; - -$pagination-color: var(--#{$prefix}link-color) !default; -$pagination-bg: var(--#{$prefix}body-bg) !default; -$pagination-border-radius: var(--#{$prefix}border-radius) !default; -$pagination-border-width: var(--#{$prefix}border-width) !default; -$pagination-margin-start: calc(-1 * #{$pagination-border-width}) !default; // stylelint-disable-line function-disallowed-list -$pagination-border-color: var(--#{$prefix}border-color) !default; - -$pagination-focus-color: var(--#{$prefix}link-hover-color) !default; -$pagination-focus-bg: var(--#{$prefix}secondary-bg) !default; -$pagination-focus-box-shadow: $focus-ring-box-shadow !default; -$pagination-focus-outline: 0 !default; - -$pagination-hover-color: var(--#{$prefix}link-hover-color) !default; -$pagination-hover-bg: var(--#{$prefix}tertiary-bg) !default; -$pagination-hover-border-color: var(--#{$prefix}border-color) !default; // Todo in v6: remove this? - -$pagination-active-color: $component-active-color !default; -$pagination-active-bg: $component-active-bg !default; -$pagination-active-border-color: $component-active-bg !default; - -$pagination-disabled-color: var(--#{$prefix}secondary-color) !default; -$pagination-disabled-bg: var(--#{$prefix}secondary-bg) !default; -$pagination-disabled-border-color: var(--#{$prefix}border-color) !default; - -$pagination-transition: color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out !default; - -$pagination-border-radius-sm: var(--#{$prefix}border-radius-sm) !default; -$pagination-border-radius-lg: var(--#{$prefix}border-radius-lg) !default; -// scss-docs-end pagination-variables - - -// Placeholders - -// scss-docs-start placeholders -$placeholder-opacity-max: .5 !default; -$placeholder-opacity-min: .2 !default; -// scss-docs-end placeholders - -// Cards - -// scss-docs-start card-variables -$card-spacer-y: $spacer !default; -$card-spacer-x: $spacer !default; -$card-title-spacer-y: $spacer * .5 !default; -$card-title-color: null !default; -$card-subtitle-color: null !default; -$card-border-width: var(--#{$prefix}border-width) !default; -$card-border-color: var(--#{$prefix}border-color-translucent) !default; -$card-border-radius: var(--#{$prefix}border-radius) !default; -$card-box-shadow: null !default; -$card-inner-border-radius: subtract($card-border-radius, $card-border-width) !default; -$card-cap-padding-y: $card-spacer-y * .5 !default; -$card-cap-padding-x: $card-spacer-x !default; -$card-cap-bg: rgba(var(--#{$prefix}body-color-rgb), .03) !default; -$card-cap-color: null !default; -$card-height: null !default; -$card-color: null !default; -$card-bg: var(--#{$prefix}body-bg) !default; -$card-img-overlay-padding: $spacer !default; -$card-group-margin: $grid-gutter-width * .5 !default; -// scss-docs-end card-variables - -// Accordion - -// scss-docs-start accordion-variables -$accordion-padding-y: 1rem !default; -$accordion-padding-x: 1.25rem !default; -$accordion-color: var(--#{$prefix}body-color) !default; -$accordion-bg: var(--#{$prefix}body-bg) !default; -$accordion-border-width: var(--#{$prefix}border-width) !default; -$accordion-border-color: var(--#{$prefix}border-color) !default; -$accordion-border-radius: var(--#{$prefix}border-radius) !default; -$accordion-inner-border-radius: subtract($accordion-border-radius, $accordion-border-width) !default; - -$accordion-body-padding-y: $accordion-padding-y !default; -$accordion-body-padding-x: $accordion-padding-x !default; - -$accordion-button-padding-y: $accordion-padding-y !default; -$accordion-button-padding-x: $accordion-padding-x !default; -$accordion-button-color: var(--#{$prefix}body-color) !default; -$accordion-button-bg: var(--#{$prefix}accordion-bg) !default; -$accordion-transition: $btn-transition, border-radius .15s ease !default; -$accordion-button-active-bg: var(--#{$prefix}primary-bg-subtle) !default; -$accordion-button-active-color: var(--#{$prefix}primary-text-emphasis) !default; - -// fusv-disable -$accordion-button-focus-border-color: $input-focus-border-color !default; // Deprecated in v5.3.3 -// fusv-enable -$accordion-button-focus-box-shadow: $btn-focus-box-shadow !default; - -$accordion-icon-width: 1.25rem !default; -$accordion-icon-color: $body-color !default; -$accordion-icon-active-color: $primary-text-emphasis !default; -$accordion-icon-transition: transform .2s ease-in-out !default; -$accordion-icon-transform: rotate(-180deg) !default; - -$accordion-button-icon: url("data:image/svg+xml,") !default; -$accordion-button-active-icon: url("data:image/svg+xml,") !default; -// scss-docs-end accordion-variables - -// Tooltips - -// scss-docs-start tooltip-variables -$tooltip-font-size: $font-size-sm !default; -$tooltip-max-width: 200px !default; -$tooltip-color: var(--#{$prefix}body-bg) !default; -$tooltip-bg: var(--#{$prefix}emphasis-color) !default; -$tooltip-border-radius: var(--#{$prefix}border-radius) !default; -$tooltip-opacity: .9 !default; -$tooltip-padding-y: $spacer * .25 !default; -$tooltip-padding-x: $spacer * .5 !default; -$tooltip-margin: null !default; // TODO: remove this in v6 - -$tooltip-arrow-width: .8rem !default; -$tooltip-arrow-height: .4rem !default; -// fusv-disable -$tooltip-arrow-color: null !default; // Deprecated in Bootstrap 5.2.0 for CSS variables -// fusv-enable -// scss-docs-end tooltip-variables - -// Form tooltips must come after regular tooltips -// scss-docs-start tooltip-feedback-variables -$form-feedback-tooltip-padding-y: $tooltip-padding-y !default; -$form-feedback-tooltip-padding-x: $tooltip-padding-x !default; -$form-feedback-tooltip-font-size: $tooltip-font-size !default; -$form-feedback-tooltip-line-height: null !default; -$form-feedback-tooltip-opacity: $tooltip-opacity !default; -$form-feedback-tooltip-border-radius: $tooltip-border-radius !default; -// scss-docs-end tooltip-feedback-variables - - -// Popovers - -// scss-docs-start popover-variables -$popover-font-size: $font-size-sm !default; -$popover-bg: var(--#{$prefix}body-bg) !default; -$popover-max-width: 276px !default; -$popover-border-width: var(--#{$prefix}border-width) !default; -$popover-border-color: var(--#{$prefix}border-color-translucent) !default; -$popover-border-radius: var(--#{$prefix}border-radius-lg) !default; -$popover-inner-border-radius: calc(#{$popover-border-radius} - #{$popover-border-width}) !default; // stylelint-disable-line function-disallowed-list -$popover-box-shadow: var(--#{$prefix}box-shadow) !default; - -$popover-header-font-size: $font-size-base !default; -$popover-header-bg: var(--#{$prefix}secondary-bg) !default; -$popover-header-color: $headings-color !default; -$popover-header-padding-y: .5rem !default; -$popover-header-padding-x: $spacer !default; - -$popover-body-color: var(--#{$prefix}body-color) !default; -$popover-body-padding-y: $spacer !default; -$popover-body-padding-x: $spacer !default; - -$popover-arrow-width: 1rem !default; -$popover-arrow-height: .5rem !default; -// scss-docs-end popover-variables - -// fusv-disable -// Deprecated in Bootstrap 5.2.0 for CSS variables -$popover-arrow-color: $popover-bg !default; -$popover-arrow-outer-color: var(--#{$prefix}border-color-translucent) !default; -// fusv-enable - - -// Toasts - -// scss-docs-start toast-variables -$toast-max-width: 350px !default; -$toast-padding-x: .75rem !default; -$toast-padding-y: .5rem !default; -$toast-font-size: .875rem !default; -$toast-color: null !default; -$toast-background-color: rgba(var(--#{$prefix}body-bg-rgb), .85) !default; -$toast-border-width: var(--#{$prefix}border-width) !default; -$toast-border-color: var(--#{$prefix}border-color-translucent) !default; -$toast-border-radius: var(--#{$prefix}border-radius) !default; -$toast-box-shadow: var(--#{$prefix}box-shadow) !default; -$toast-spacing: $container-padding-x !default; - -$toast-header-color: var(--#{$prefix}secondary-color) !default; -$toast-header-background-color: rgba(var(--#{$prefix}body-bg-rgb), .85) !default; -$toast-header-border-color: $toast-border-color !default; -// scss-docs-end toast-variables - - -// Badges - -// scss-docs-start badge-variables -$badge-font-size: .75em !default; -$badge-font-weight: $font-weight-bold !default; -$badge-color: $white !default; -$badge-padding-y: .35em !default; -$badge-padding-x: .65em !default; -$badge-border-radius: var(--#{$prefix}border-radius) !default; -// scss-docs-end badge-variables - - -// Modals - -// scss-docs-start modal-variables -$modal-inner-padding: $spacer !default; - -$modal-footer-margin-between: .5rem !default; - -$modal-dialog-margin: .5rem !default; -$modal-dialog-margin-y-sm-up: 1.75rem !default; - -$modal-title-line-height: $line-height-base !default; - -// AdminLTE keeps `$modal-content-color: null` so the modal text colour is -// inherited from the document. Bootstrap's default is `var(--#{$prefix}body-color)`. -$modal-content-color: null !default; -$modal-content-bg: var(--#{$prefix}body-bg) !default; -$modal-content-border-color: var(--#{$prefix}border-color-translucent) !default; -$modal-content-border-width: var(--#{$prefix}border-width) !default; -$modal-content-border-radius: var(--#{$prefix}border-radius-lg) !default; -$modal-content-inner-border-radius: subtract($modal-content-border-radius, $modal-content-border-width) !default; -$modal-content-box-shadow-xs: var(--#{$prefix}box-shadow-sm) !default; -$modal-content-box-shadow-sm-up: var(--#{$prefix}box-shadow) !default; - -$modal-backdrop-bg: $black !default; -$modal-backdrop-opacity: .5 !default; - -$modal-header-border-color: var(--#{$prefix}border-color) !default; -$modal-header-border-width: $modal-content-border-width !default; -$modal-header-padding-y: $modal-inner-padding !default; -$modal-header-padding-x: $modal-inner-padding !default; -$modal-header-padding: $modal-header-padding-y $modal-header-padding-x !default; // Keep this for backwards compatibility - -$modal-footer-bg: null !default; -$modal-footer-border-color: $modal-header-border-color !default; -$modal-footer-border-width: $modal-header-border-width !default; - -$modal-sm: 300px !default; -$modal-md: 500px !default; -$modal-lg: 800px !default; -$modal-xl: 1140px !default; - -$modal-fade-transform: translate(0, -50px) !default; -$modal-show-transform: none !default; -$modal-transition: transform .3s ease-out !default; -$modal-scale-transform: scale(1.02) !default; -// scss-docs-end modal-variables - - -// Alerts -// -// Define alert colors, border radius, and padding. - -// scss-docs-start alert-variables -$alert-padding-y: $spacer !default; -$alert-padding-x: $spacer !default; -$alert-margin-bottom: 1rem !default; -$alert-border-radius: var(--#{$prefix}border-radius) !default; -$alert-link-font-weight: $font-weight-bold !default; -$alert-border-width: var(--#{$prefix}border-width) !default; -$alert-dismissible-padding-r: $alert-padding-x * 3 !default; // 3x covers width of x plus default padding on either side -// scss-docs-end alert-variables - -// fusv-disable -$alert-bg-scale: -80% !default; // Deprecated in v5.2.0, to be removed in v6 -$alert-border-scale: -70% !default; // Deprecated in v5.2.0, to be removed in v6 -$alert-color-scale: 40% !default; // Deprecated in v5.2.0, to be removed in v6 -// fusv-enable - -// Progress bars - -// scss-docs-start progress-variables -$progress-height: 1rem !default; -$progress-font-size: $font-size-base * .75 !default; -$progress-bg: var(--#{$prefix}secondary-bg) !default; -$progress-border-radius: var(--#{$prefix}border-radius) !default; -$progress-box-shadow: var(--#{$prefix}box-shadow-inset) !default; -$progress-bar-color: $white !default; -$progress-bar-bg: $primary !default; -$progress-bar-animation-timing: 1s linear infinite !default; -$progress-bar-transition: width .6s ease !default; -// scss-docs-end progress-variables - - -// List group - -// scss-docs-start list-group-variables -$list-group-color: var(--#{$prefix}body-color) !default; -$list-group-bg: var(--#{$prefix}body-bg) !default; -$list-group-border-color: var(--#{$prefix}border-color) !default; -$list-group-border-width: var(--#{$prefix}border-width) !default; -$list-group-border-radius: var(--#{$prefix}border-radius) !default; - -$list-group-item-padding-y: $spacer * .5 !default; -$list-group-item-padding-x: $spacer !default; -// fusv-disable -$list-group-item-bg-scale: -80% !default; // Deprecated in v5.3.0 -$list-group-item-color-scale: 40% !default; // Deprecated in v5.3.0 -// fusv-enable - -$list-group-hover-bg: var(--#{$prefix}tertiary-bg) !default; -$list-group-active-color: $component-active-color !default; -$list-group-active-bg: $component-active-bg !default; -$list-group-active-border-color: $list-group-active-bg !default; - -$list-group-disabled-color: var(--#{$prefix}secondary-color) !default; -$list-group-disabled-bg: $list-group-bg !default; - -$list-group-action-color: var(--#{$prefix}secondary-color) !default; -$list-group-action-hover-color: var(--#{$prefix}emphasis-color) !default; - -$list-group-action-active-color: var(--#{$prefix}body-color) !default; -$list-group-action-active-bg: var(--#{$prefix}secondary-bg) !default; -// scss-docs-end list-group-variables - - -// Image thumbnails - -// scss-docs-start thumbnail-variables -$thumbnail-padding: .25rem !default; -$thumbnail-bg: var(--#{$prefix}body-bg) !default; -$thumbnail-border-width: var(--#{$prefix}border-width) !default; -$thumbnail-border-color: var(--#{$prefix}border-color) !default; -$thumbnail-border-radius: var(--#{$prefix}border-radius) !default; -$thumbnail-box-shadow: var(--#{$prefix}box-shadow-sm) !default; -// scss-docs-end thumbnail-variables - - -// Figures - -// scss-docs-start figure-variables -$figure-caption-font-size: $small-font-size !default; -$figure-caption-color: var(--#{$prefix}secondary-color) !default; -// scss-docs-end figure-variables - - -// Breadcrumbs - -// scss-docs-start breadcrumb-variables -$breadcrumb-font-size: null !default; -$breadcrumb-padding-y: 0 !default; -$breadcrumb-padding-x: 0 !default; -$breadcrumb-item-padding-x: .5rem !default; -$breadcrumb-margin-bottom: 1rem !default; -$breadcrumb-bg: null !default; -$breadcrumb-divider-color: var(--#{$prefix}secondary-color) !default; -$breadcrumb-active-color: var(--#{$prefix}secondary-color) !default; -$breadcrumb-divider: quote("/") !default; -$breadcrumb-divider-flipped: $breadcrumb-divider !default; -$breadcrumb-border-radius: null !default; -// scss-docs-end breadcrumb-variables - -// Carousel - -// scss-docs-start carousel-variables -$carousel-control-color: $white !default; -$carousel-control-width: 15% !default; -$carousel-control-opacity: .5 !default; -$carousel-control-hover-opacity: .9 !default; -$carousel-control-transition: opacity .15s ease !default; - -$carousel-indicator-width: 30px !default; -$carousel-indicator-height: 3px !default; -$carousel-indicator-hit-area-height: 10px !default; -$carousel-indicator-spacer: 3px !default; -$carousel-indicator-opacity: .5 !default; -$carousel-indicator-active-bg: $white !default; -$carousel-indicator-active-opacity: 1 !default; -$carousel-indicator-transition: opacity .6s ease !default; - -$carousel-caption-width: 70% !default; -$carousel-caption-color: $white !default; -$carousel-caption-padding-y: 1.25rem !default; -$carousel-caption-spacer: 1.25rem !default; - -$carousel-control-icon-width: 2rem !default; - -$carousel-control-prev-icon-bg: url("data:image/svg+xml,") !default; -$carousel-control-next-icon-bg: url("data:image/svg+xml,") !default; - -$carousel-transition-duration: .6s !default; -$carousel-transition: transform $carousel-transition-duration ease-in-out !default; // Define transform transition first if using multiple transitions (e.g., `transform 2s ease, opacity .5s ease-out`) -$carousel-control-icon-filter: null !default; -// scss-docs-end carousel-variables - -// scss-docs-start carousel-dark-variables -// Carousel "dark" variables below have been deprecated in Bootstrap 5.3.4. -// Use the `.carousel` colour-mode rules and `$carousel-control-icon-filter` -// instead. -$carousel-dark-indicator-active-bg: $black !default; // Deprecated in 5.3.4 -$carousel-dark-caption-color: $black !default; // Deprecated in 5.3.4 -$carousel-dark-control-icon-filter: invert(1) grayscale(100) !default; // Deprecated in 5.3.4 -// scss-docs-end carousel-dark-variables - - -// Spinners - -// scss-docs-start spinner-variables -$spinner-width: 2rem !default; -$spinner-height: $spinner-width !default; -$spinner-vertical-align: -.125em !default; -$spinner-border-width: .25em !default; -$spinner-animation-speed: .75s !default; - -$spinner-width-sm: 1rem !default; -$spinner-height-sm: $spinner-width-sm !default; -$spinner-border-width-sm: .2em !default; -// scss-docs-end spinner-variables - - -// Close - -// scss-docs-start close-variables -$btn-close-width: 1em !default; -$btn-close-height: $btn-close-width !default; -$btn-close-padding-x: .25em !default; -$btn-close-padding-y: $btn-close-padding-x !default; -$btn-close-color: $black !default; -$btn-close-bg: url("data:image/svg+xml,") !default; -$btn-close-focus-shadow: $focus-ring-box-shadow !default; -$btn-close-opacity: .5 !default; -$btn-close-hover-opacity: .75 !default; -$btn-close-focus-opacity: 1 !default; -$btn-close-disabled-opacity: .25 !default; -$btn-close-filter: null !default; -$btn-close-white-filter: invert(1) grayscale(100%) brightness(200%) !default; // Deprecated in 5.3.4. Use `$btn-close-filter` with the `.btn-close` colour-mode rules instead. -// scss-docs-end close-variables - - -// Offcanvas - -// scss-docs-start offcanvas-variables -$offcanvas-padding-y: $modal-inner-padding !default; -$offcanvas-padding-x: $modal-inner-padding !default; -$offcanvas-horizontal-width: 400px !default; -$offcanvas-vertical-height: 30vh !default; -$offcanvas-transition-duration: .3s !default; -$offcanvas-border-color: $modal-content-border-color !default; -$offcanvas-border-width: $modal-content-border-width !default; -$offcanvas-title-line-height: $modal-title-line-height !default; -$offcanvas-bg-color: var(--#{$prefix}body-bg) !default; -$offcanvas-color: var(--#{$prefix}body-color) !default; -$offcanvas-box-shadow: $modal-content-box-shadow-xs !default; -$offcanvas-backdrop-bg: $modal-backdrop-bg !default; -$offcanvas-backdrop-opacity: $modal-backdrop-opacity !default; -// scss-docs-end offcanvas-variables - -// Code - -$code-font-size: $small-font-size !default; -$code-color: $pink !default; - -$kbd-padding-y: .1875rem !default; -$kbd-padding-x: .375rem !default; -$kbd-font-size: $code-font-size !default; -$kbd-color: var(--#{$prefix}body-bg) !default; -$kbd-bg: var(--#{$prefix}body-color) !default; -$nested-kbd-font-weight: null !default; // Deprecated in v5.2.0, removing in v6 - -$pre-color: null !default; diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_callouts.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_callouts.scss deleted file mode 100644 index 21302345..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_callouts.scss +++ /dev/null @@ -1,40 +0,0 @@ -// -// Callouts -// - -.callout { - --#{$prefix}link-color-rgb: var(--#{$lte-prefix}callout-link); - --#{$prefix}code-color: var(--#{$lte-prefix}callout-code-color); - - padding: 1.25rem; - color: var(--#{$lte-prefix}callout-color, inherit); - background-color: var(--#{$lte-prefix}callout-bg, var(--bs-gray-100)); - border-left: .25rem solid var(--#{$lte-prefix}callout-border, var(--bs-gray-300)); - - .callout-link { - font-weight: $lte-callout-link-font-weight; - color: var(--#{$prefix}callout-link-color); - } - - h4 { - margin-bottom: .25rem; - } - - > :last-child { - margin-bottom: 0; - } - - + .callout { - margin-top: -.25rem; - } -} - -// Variations -@each $name, $color in $theme-colors { - .callout-#{$name} { - --#{$lte-prefix}callout-color: var(--#{$prefix}#{$name}-text-emphasis); - --#{$lte-prefix}callout-bg: var(--#{$prefix}#{$name}-bg-subtle); - --#{$lte-prefix}callout-border: var(--#{$prefix}#{$name}-border-subtle); - --#{$prefix}callout-link-color: var(--#{$prefix}#{$name}-text-emphasis); - } -} diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_cards.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_cards.scss deleted file mode 100644 index 6d7fc3ab..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_cards.scss +++ /dev/null @@ -1,311 +0,0 @@ -// -// Component: Cards -// - -// Color variants -.card { - @include box-shadow($lte-card-shadow); - - &[class*="card-"]:not(.card-outline), - &[class*="text-bg-"]:not(.card-outline) { - > .card-header { - color: var(--#{$lte-prefix}card-variant-color); - background-color: var(--#{$lte-prefix}card-variant-bg); - - .btn-tool { - --#{$prefix}btn-color: rgba(var(--#{$lte-prefix}card-variant-color-rgb), .8); - --#{$prefix}btn-hover-color: var(--#{$lte-prefix}card-variant-color); - } - } - } - - &.card-outline { - border-top: 3px solid var(--#{$lte-prefix}card-variant-bg); - } - - &.maximized-card { - position: fixed; - top: 0; - left: 0; - z-index: $zindex-modal-backdrop; - width: 100% !important; - max-width: 100% !important; - height: 100% !important; - max-height: 100% !important; - - &.was-collapsed .card-body { - display: block !important; - } - - .card-body { - overflow: auto; - } - - [data-lte-toggle="card-collapse"] { - display: none; - } - - [data-lte-icon="maximize"] { - display: none; - } - - .card-header, - .card-footer { - @include border-radius(0 !important); - } - } - - &:not(.maximized-card) { - [data-lte-icon="minimize"] { - display: none; - } - } - - // collapsed mode - &.collapsed-card { - // Use > .card-header to scope to direct card only, not nested cards - > .card-header [data-lte-icon="collapse"] { - display: none; - } - - > .card-body, - > .card-footer { - display: none; - } - } - - &:not(.collapsed-card) { - // Use > .card-header to scope to direct card only, not nested cards - > .card-header [data-lte-icon="expand"] { - display: none; - } - } - - - .nav.flex-column { - > li { - margin: 0; - border-bottom: 1px solid $card-border-color; - - &:last-of-type { - border-bottom: 0; - } - } - } - - // fixed height to 300px - &.height-control { - .card-body { - max-height: 300px; - overflow: auto; - } - } - - .border-end { - border-right: 1px solid $card-border-color; - } - - .border-start { - border-left: 1px solid $card-border-color; - } - - &.card-tabs { - &:not(.card-outline) { - > .card-header { - border-bottom: 0; - - .nav-item { - &:first-child .nav-link { - border-left-color: transparent; - } - } - } - } - - &.card-outline { - .nav-item { - border-bottom: 0; - - &:first-child .nav-link { - margin-left: 0; - border-left: 0; - } - } - } - - .card-tools { - margin: .3rem .5rem; - } - - &:not(.expanding-card).collapsed-card { - .card-header { - border-bottom: 0; - - .nav-tabs { - border-bottom: 0; - - .nav-item { - margin-bottom: 0; - } - } - } - } - - &.expanding-card { - .card-header { - .nav-tabs { - .nav-item { - margin-bottom: -1px; - } - } - } - } - } - - &.card-outline-tabs { - border-top: 0; - - .card-header { - .nav-item { - &:first-child .nav-link { - margin-left: 0; - border-left: 0; - } - } - - a { - text-decoration: none; - border-top: 3px solid transparent; - - &:hover { - border-top: 3px solid $nav-tabs-border-color; - } - - &.active { - &:hover { - margin-top: 0; - } - } - } - } - - .card-tools { - margin: .5rem .5rem .3rem; - } - - &:not(.expanding-card).collapsed-card .card-header { - border-bottom: 0; - - .nav-tabs { - border-bottom: 0; - - .nav-item { - margin-bottom: 0; - } - } - } - - &.expanding-card { - .card-header { - .nav-tabs { - .nav-item { - margin-bottom: -1px; - } - } - } - } - } - -} - -// Maximized Card Body Scroll fix -html.maximized-card { - overflow: hidden; -} - -// Add clearfix to header, body and footer -.card-header, -.card-body, -.card-footer { - @include clearfix(); -} - -// Box header -.card-header { - position: relative; - padding: (($card-spacer-y * .5) * 2) $card-spacer-x; - background-color: transparent; - border-bottom: 1px solid $card-border-color; - - @if $enable-rounded { - @include border-top-radius($border-radius); - } - - .collapsed-card & { - border-bottom: 0; - } - - > .card-tools { - float: right; - margin-right: -$card-spacer-x * .5; - - .input-group, - .nav, - .pagination { - margin-top: -$card-spacer-y * .4; - margin-bottom: -$card-spacer-y * .4; - } - - [data-bs-toggle="tooltip"] { - position: relative; - } - } -} - -.card-title { - float: left; - margin: 0; - font-size: $lte-card-title-font-size; - font-weight: $lte-card-title-font-weight; -} - -// Box Tools Buttons -.btn-tool { - --#{$prefix}btn-padding-x: .5rem; - --#{$prefix}btn-padding-y: .25rem; - - &:not(.btn-tool-custom) { - --#{$prefix}btn-color: var(--#{$prefix}tertiary-color); - --#{$prefix}btn-bg: transparent; - --#{$prefix}btn-box-shadow: none; - --#{$prefix}btn-hover-color: var(--#{$prefix}secondary-color); - --#{$prefix}btn-active-border-color: transparent; - } - - margin: -$card-spacer-y 0; - font-size: $font-size-sm; -} - -@each $name, $color in $theme-colors { - .card-#{$name}, - .bg-#{$name}, - .text-bg-#{$name} { - --#{$lte-prefix}card-variant-bg: #{$color}; - --#{$lte-prefix}card-variant-bg-rgb: #{to-rgb($color)}; - --#{$lte-prefix}card-variant-color: #{color-contrast($color)}; - --#{$lte-prefix}card-variant-color-rgb: #{to-rgb(color-contrast($color))}; - } -} - -// Box Body -.card-body { - // Tables within the box body - > .table { - margin-bottom: 0; - - > thead > tr > th, - > thead > tr > td { - border-top-width: 0; - } - } -} diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_compact-mode.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_compact-mode.scss deleted file mode 100644 index 22cb5ea4..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_compact-mode.scss +++ /dev/null @@ -1,53 +0,0 @@ -.compact-mode { - .app-header { - max-height: $lte-app-header-height-compact; - - .nav-link { - max-height: $nav-link-height-compact; - } - } - - .nav-link { - --bs-nav-link-padding-y: .25rem; - --bs-nav-link-padding-x: .5rem; - } - - &.sidebar-mini.sidebar-collapse { - .app-sidebar:not(:hover) { - min-width: $lte-sidebar-mini-width-compact; - max-width: $lte-sidebar-mini-width-compact; - - .sidebar-menu { - .nav-link { - width: $lte-sidebar-mini-width-compact - $lte-sidebar-padding-x * 2 !important; - } - } - } - } - - .logo-xs, - .logo-xl { - max-height: $lte-app-header-height-compact; - } - - .brand-image { - width: $nav-link-height-compact; - height: $nav-link-height-compact; - } - - .sidebar-brand { - height: $lte-app-header-height-compact; - } - - .app-footer { - padding: $lte-app-footer-padding-compact; - } - - .sidebar-wrapper { - .nav-icon { - min-width: 1.1rem; - max-width: 1.1rem; - } - } -} - diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_direct-chat.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_direct-chat.scss deleted file mode 100644 index ed961f3e..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_direct-chat.scss +++ /dev/null @@ -1,235 +0,0 @@ -// -// Component: Direct Chat -// - -.direct-chat { - .card-body { - position: relative; - padding: 0; - overflow-x: hidden; - } - - &.chat-pane-open { - .direct-chat-contacts { - transform: translate(0, 0); - } - } - - - &.timestamp-light { - .direct-chat-timestamp { - color: rgba(var(--#{$prefix}body-color-rgb), .65); - } - } - - &.timestamp-dark { - .direct-chat-timestamp { - color: rgba(var(--#{$prefix}body-color-rgb), .9); - } - } -} - -.direct-chat-messages { - height: 250px; - padding: 10px; - overflow: auto; - transform: translate(0, 0); -} - -.direct-chat-msg, -.direct-chat-text { - display: block; -} - -.direct-chat-msg { - @include clearfix(); - margin-bottom: 10px; -} - -.direct-chat-messages, -.direct-chat-contacts { - @include transition(transform .5s ease-in-out); -} - -.direct-chat-text { - @if $enable-rounded { - @include border-radius($border-radius-lg); - } - - position: relative; - padding: 5px 10px; - margin: 5px 0 0 50px; - color: $lte-direct-chat-default-font-color; - background-color: $lte-direct-chat-default-msg-bg; - border: 1px solid $lte-direct-chat-default-msg-border-color; - - //Create the arrow - &::after, - &::before { - position: absolute; - top: 15px; - right: 100%; - width: 0; - height: 0; - pointer-events: none; - content: " "; - border: solid transparent; - border-right-color: $lte-direct-chat-default-msg-border-color; - } - - &::after { - margin-top: -5px; - border-width: 5px; - } - - &::before { - margin-top: -6px; - border-width: 6px; - } - - .end & { - margin-right: 50px; - margin-left: 0; - - &::after, - &::before { - right: auto; - left: 100%; - border-right-color: transparent; - border-left-color: $lte-direct-chat-default-msg-border-color; - } - } -} - -.direct-chat-img { - @include border-radius(50%); - float: left; - width: 40px; - height: 40px; - - .end & { - float: right; - } -} - -.direct-chat-infos { - display: block; - margin-bottom: 2px; - font-size: $font-size-sm; -} - -.direct-chat-name { - font-weight: 600; -} - -.direct-chat-timestamp { - color: rgba(var(--#{$prefix}body-color-rgb), .75); -} - -//Direct chat contacts pane -.direct-chat-contacts-open { - .direct-chat-contacts { - transform: translate(0, 0); - } -} - -.direct-chat-contacts { - position: absolute; - top: 0; - bottom: 0; - width: 100%; - height: 250px; - overflow: auto; - color: var(--#{$prefix}body-bg); - background-color: var(--#{$prefix}body-color); - transform: translate(101%, 0); -} - -.direct-chat-contacts-light { - background-color: var(--#{$prefix}light-bg-subtle); - - .contacts-list-name { - color: var(--#{$prefix}body-color); - } - - .contacts-list-date { - color: var(--#{$prefix}secondary-color); - } - - .contacts-list-msg { - color: var(--#{$prefix}secondary-color); - } -} - -//Contacts list -- for displaying contacts in direct chat contacts pane -.contacts-list { - @include list-unstyled(); - - > li { - @include clearfix(); - padding: 10px; - margin: 0; - text-decoration: none; - border-bottom: 1px solid rgba($black, .2); - - &:last-of-type { - border-bottom: 0; - } - - a { - text-decoration: none; - } - } -} - -.contacts-list-img { - @include border-radius(50%); - float: left; - width: 40px; -} - -.contacts-list-info { - margin-left: 45px; - color: var(--#{$prefix}body-bg); -} - -.contacts-list-name, -.contacts-list-status { - display: block; -} - -.contacts-list-name { - font-weight: 600; -} - -.contacts-list-status { - font-size: $font-size-sm; -} - -.contacts-list-date { - font-weight: 400; - color: var(--#{$prefix}secondary-bg); -} - -.contacts-list-msg { - color: var(--#{$prefix}secondary-bg); -} - -.end > .direct-chat-text { - color: var(--#{$lte-prefix}direct-chat-color); - background-color: var(--#{$lte-prefix}direct-chat-bg); - border-color: var(--#{$lte-prefix}direct-chat-bg); - - &::after, - &::before { - border-left-color: var(--#{$lte-prefix}direct-chat-bg); - } -} - -// Color variants -@each $name, $color in $theme-colors { - .direct-chat-#{$name} { - --#{$lte-prefix}direct-chat-color: #{color-contrast($color)}; - --#{$lte-prefix}direct-chat-bg: #{$color}; - } -} diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_docs.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_docs.scss deleted file mode 100644 index 4e9bdc10..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_docs.scss +++ /dev/null @@ -1,820 +0,0 @@ -// -// Component: Docs -// -// Visual treatment for documentation pages. Body class `.docs-page` opts in. -// Demo pages are not affected. -// - -// Inline code blocks rendered by MDX (Astro highlight default class) -.astro-code { - padding: .75rem; - @include border-radius($border-radius); -} - -.docs-page { - // Constrain the docs card to a comfortable reading width. - // Tables and code blocks inside can still scroll horizontally if needed. - .app-content > .container-fluid { - max-width: 60rem; // ~960px - - @include media-breakpoint-up(xxl) { - max-width: 64rem; - } - } - - // - // Card body — breathing room + prose typography. - // Only applied inside docs content cards so demo-page card content - // stays unchanged. - // - .card > .card-body { - padding: 1.5rem; - font-size: 1rem; - line-height: 1.7; - color: var(--#{$prefix}body-color); - - @include media-breakpoint-up(md) { - padding: 2.5rem; - } - - // - // Heading hierarchy. The MDX content in src/html/components/docs/ uses - // ##### (h5) as the dominant section heading and ###### (h6) as a - // sub-heading (most visible in FAQ.mdx, where h5 is a topic and h6 is - // a question). Style h5 as a major section and h6 as a sub-section so - // the visual hierarchy matches the semantic one. - // - h1, - h2, - h3, - h4, - h5, - h6, - .h1, - .h2, - .h3, - .h4, - .h5, - .h6 { - margin-top: 2em; - margin-bottom: .5em; - font-weight: 600; - line-height: 1.3; - color: var(--#{$prefix}emphasis-color); - scroll-margin-top: 4rem; // anchor-link friendly - - &:first-child { - margin-top: 0; - } - } - - h2, - .h2 { - padding-bottom: .35em; - font-size: 1.75rem; - border-bottom: 1px solid var(--#{$prefix}border-color); - } - - h3, - .h3 { - font-size: 1.4rem; - } - - h4, - .h4 { - font-size: 1.2rem; - } - - // h5 is the dominant section heading across docs MDX files. Give it - // a visible separator above so sections are easy to scan. - h5, - .h5 { - padding-top: 1.5em; - margin-top: 2.5em; - font-size: 1.3rem; - border-top: 1px solid var(--#{$prefix}border-color); - - &:first-child { - padding-top: 0; - border-top: 0; - } - } - - // h6 is the sub-section / FAQ question style. Smaller than h5 but - // still distinctly heading-like, with a touch of accent colour to - // separate from body text. - h6, - .h6 { - margin-top: 1.75em; - font-size: 1.08rem; - color: var(--#{$prefix}emphasis-color); - } - - // - // Paragraphs and lists. - // - p, - ul, - ol, - dl, - blockquote, - table { - margin-bottom: 1.1rem; - } - - ul, - ol { - padding-left: 1.5rem; - - li { - margin-bottom: .35rem; - } - - // Tighter nested lists - ul, - ol { - margin-top: .35rem; - margin-bottom: 0; - } - } - - // Lead-style first paragraph (just after a heading) - h2 + p, - h3 + p, - h5 + p { - margin-top: 0; - } - - // - // Inline code — pill style that complements MDX's syntax-highlighted blocks. - // - p code, - li code, - td code, - th code, - h2 code, - h3 code, - h4 code, - h5 code, - h6 code { - padding: .15em .4em; - font-size: .875em; - color: var(--#{$prefix}emphasis-color); - background: var(--#{$prefix}tertiary-bg); - border: 1px solid var(--#{$prefix}border-color); - @include border-radius($border-radius-sm); - } - - // - // Code blocks. - // - pre.astro-code, - pre { - padding: 1rem 1.25rem; - margin-top: 1.25rem; - margin-bottom: 1.5rem; - overflow-x: auto; - font-size: .875rem; - line-height: 1.6; - @include border-radius($border-radius); - - code { - padding: 0; - background: transparent; - border: 0; - } - } - - // - // Tables — reference-doc styling with hairline borders. - // - table { - width: 100%; - margin-top: 1.25rem; - margin-bottom: 1.5rem; - overflow: hidden; - font-size: .95rem; - border-collapse: collapse; - border: 1px solid var(--#{$prefix}border-color); - @include border-radius($border-radius-sm); - - th, - td { - padding: .65rem .9rem; - text-align: left; - vertical-align: top; - border-bottom: 1px solid var(--#{$prefix}border-color); - } - - th { - font-weight: 600; - color: var(--#{$prefix}emphasis-color); - background: var(--#{$prefix}tertiary-bg); - } - - tr:last-child td { - border-bottom: 0; - } - - code { - white-space: nowrap; - } - } - - // - // Blockquotes used as info callouts. - // - blockquote { - padding: .85rem 1.1rem; - margin-top: 1.25rem; - color: var(--#{$prefix}emphasis-color); - background: var(--#{$prefix}info-bg-subtle); - border-left: 4px solid var(--#{$prefix}info-border-subtle); - @include border-radius($border-radius-sm); - - p:last-child { - margin-bottom: 0; - } - - code { - background: rgba(0, 0, 0, .05); - } - } - - // - // Prose links — stand out more than Bootstrap defaults. - // - a:not(.btn):not(.nav-link) { - color: var(--#{$prefix}primary); - text-decoration: underline; - text-decoration-thickness: 1px; - text-underline-offset: 3px; - - &:hover { - text-decoration-thickness: 2px; - } - } - - // - // Horizontal rule — used as additional section separator. - // - hr { - margin: 2.5rem 0; - border: 0; - border-top: 1px solid var(--#{$prefix}border-color); - opacity: 1; - } - - // - // Lead paragraph — the first paragraph in any doc is a summary. - // Visually heavier so it functions as a deck. - // - > p:first-child, - > p:first-of-type { - margin-bottom: 2rem; - font-size: 1.125rem; - line-height: 1.6; - color: var(--#{$prefix}body-color); - } - - // - // FAQ / disclosure accordion — uses native
/. - // Add `.faq-item` to
to opt in. - // - details.faq-item { - margin-bottom: .5rem; - overflow: hidden; - background: var(--#{$prefix}body-bg); - border: 1px solid var(--#{$prefix}border-color); - @include border-radius($border-radius); - @include transition(border-color .15s ease, box-shadow .15s ease); - - &:hover { - border-color: var(--#{$prefix}primary-border-subtle); - } - - &[open] { - background: var(--#{$prefix}body-bg); - border-color: var(--#{$prefix}primary-border-subtle); - box-shadow: 0 1px 3px rgba(0, 0, 0, .04); - } - - summary { - position: relative; - padding: .9rem 3rem .9rem 1.1rem; - font-size: 1rem; - font-weight: 600; - color: var(--#{$prefix}emphasis-color); - list-style: none; - cursor: pointer; - user-select: none; - @include transition(color .15s ease); - - // Hide the default disclosure triangle in Webkit/Blink - &::-webkit-details-marker { - display: none; - } - - // Hide the marker in modern Firefox too - &::marker { - content: ""; - } - - &:hover { - color: var(--#{$prefix}primary); - } - - // Right-side chevron, rotates on open - &::after { - position: absolute; - top: 50%; - right: 1.1rem; - display: inline-block; - width: .55rem; - height: .55rem; - margin-top: -.4rem; - content: ""; - border-right: 2px solid currentcolor; - border-bottom: 2px solid currentcolor; - opacity: .55; - transform: rotate(45deg); - @include transition(transform .2s ease); - } - } - - &[open] > summary { - color: var(--#{$prefix}primary); - border-bottom: 1px solid var(--#{$prefix}border-color); - - &::after { - margin-top: -.15rem; - opacity: 1; - transform: rotate(-135deg); - } - } - - // Content (everything after ) gets the inner padding - > *:not(summary) { - padding-right: 1.1rem; - padding-left: 1.1rem; - } - - > *:not(summary):first-of-type { - padding-top: 1rem; - } - - > *:not(summary):last-child { - padding-bottom: 1rem; - } - - // Tighten paragraphs/lists inside an FAQ answer - p, - ul, - ol { - margin-bottom: .65rem; - } - - > *:last-child p:last-child, - > p:last-child, - > ul:last-child, - > ol:last-child { - margin-bottom: 0; - } - - // Code blocks inside an FAQ answer get a less-distracting bg - pre.astro-code, - pre { - margin-right: 0; - margin-left: 0; - } - } - } - - // Dark-mode tweak: blockquote info bg needs more contrast on dark - &[data-bs-theme="dark"], - [data-bs-theme="dark"] & { - .card > .card-body blockquote code { - background: rgba(255, 255, 255, .08); - } - } -} - -// -// FAQ page — custom layout that breaks out of the standard docs card -// to give the FAQ a more distinctive visual identity. Opt in via -// body.faq-page (set in pages/docs/faq.astro). -// - -.faq-page { - // The FAQ doesn't use the standard card wrapper used by other docs, - // so the container can stretch wider for the chip strip and section - // grid. - .app-content > .container-fluid { - max-width: 64rem; - - @include media-breakpoint-up(xxl) { - max-width: 72rem; - } - } - - // --- Hero ------------------------------------------------------------- - .faq-hero { - position: relative; - padding: 3rem 1.5rem 2.5rem; - margin-top: 1rem; - overflow: hidden; - background: radial-gradient(ellipse at top left, rgba(var(--#{$prefix}primary-rgb), .12), transparent 60%), radial-gradient(ellipse at bottom right, rgba(var(--#{$prefix}info-rgb), .1), transparent 65%), var(--#{$prefix}body-bg); - border: 1px solid var(--#{$prefix}border-color); - @include border-radius($border-radius-xl); - } - - .faq-hero-eyebrow { - display: inline-flex; - gap: .4rem; - align-items: center; - padding: .35rem .85rem; - margin-bottom: 1rem; - font-size: .75rem; - font-weight: 600; - color: var(--#{$prefix}primary); - text-transform: uppercase; - letter-spacing: .08em; - background: var(--#{$prefix}primary-bg-subtle); - border: 1px solid var(--#{$prefix}primary-border-subtle); - @include border-radius(50rem); - - .bi { - font-size: .9rem; - } - } - - .faq-hero-title { - margin: 0 0 .5rem; - font-size: 2.25rem; - font-weight: 700; - line-height: 1.15; - color: var(--#{$prefix}emphasis-color); - - @include media-breakpoint-up(md) { - font-size: 2.75rem; - } - } - - .faq-hero-lead { - max-width: 36rem; - margin: 0 auto 1.75rem; - font-size: 1.05rem; - line-height: 1.55; - color: var(--#{$prefix}secondary-color); - } - - .faq-search { - position: relative; - max-width: 32rem; - margin: 0 auto; - - .form-control { - height: 3rem; - padding-right: 3rem; - padding-left: 3rem; - font-size: 1rem; - background: var(--#{$prefix}body-bg); - @include border-radius(50rem); - @include transition(box-shadow .15s ease, border-color .15s ease); - - &:focus { - box-shadow: 0 0 0 .25rem rgba(var(--#{$prefix}primary-rgb), .15); - } - } - } - - .faq-search-icon { - position: absolute; - top: 50%; - left: 1.2rem; - color: var(--#{$prefix}secondary-color); - pointer-events: none; - transform: translateY(-50%); - } - - .faq-search-clear { - position: absolute; - top: 50%; - right: .65rem; - display: inline-flex; - align-items: center; - justify-content: center; - width: 2rem; - height: 2rem; - padding: 0; - color: var(--#{$prefix}secondary-color); - background: var(--#{$prefix}tertiary-bg); - border: 0; - transform: translateY(-50%); - @include border-radius(50%); - @include transition(background .15s ease, color .15s ease); - - &:hover { - color: var(--#{$prefix}body-color); - background: var(--#{$prefix}secondary-bg); - } - } - - .faq-empty-state { - max-width: 32rem; - padding: 1rem; - margin: 1.5rem auto 0; - font-size: .95rem; - color: var(--#{$prefix}secondary-color); - background: var(--#{$prefix}tertiary-bg); - @include border-radius($border-radius); - - .bi { - margin-right: .35rem; - } - } - - // --- Section nav chips ------------------------------------------------ - .faq-chips { - display: flex; - flex-wrap: wrap; - gap: .5rem; - justify-content: center; - } - - .faq-chip { - display: inline-flex; - gap: .45rem; - align-items: center; - padding: .45rem .85rem; - font-size: .875rem; - font-weight: 500; - color: var(--#{$prefix}body-color); - text-decoration: none; - background: var(--#{$prefix}body-bg); - border: 1px solid var(--#{$prefix}border-color); - @include border-radius(50rem); - @include transition(transform .15s ease, border-color .15s ease, background-color .15s ease, color .15s ease); - - &:hover { - color: var(--#{$prefix}emphasis-color); - background: var(--#{$prefix}tertiary-bg); - transform: translateY(-1px); - } - - .bi { - font-size: 1rem; - } - } - - .faq-chip-count { - padding: 0 .45rem; - font-size: .7rem; - font-weight: 600; - color: var(--#{$prefix}secondary-color); - background: var(--#{$prefix}tertiary-bg); - @include border-radius(50rem); - } - - // Per-section chip accent on hover/active - @each $tone in (primary, info, warning, success, danger, secondary) { - .faq-chip-#{$tone}:hover { - color: var(--#{$prefix}#{$tone}-text-emphasis); - background: var(--#{$prefix}#{$tone}-bg-subtle); - border-color: var(--#{$prefix}#{$tone}-border-subtle); - - .faq-chip-count { - color: var(--#{$prefix}#{$tone}-text-emphasis); - background: rgba(0, 0, 0, .06); - } - } - } - - // --- Sections --------------------------------------------------------- - .faq-section { - margin-bottom: 2.5rem; - scroll-margin-top: 5rem; - } - - .faq-section-header { - display: flex; - gap: 1rem; - align-items: center; - padding: 1rem 1.25rem; - margin-bottom: 1rem; - background: var(--#{$prefix}body-bg); - border: 1px solid var(--#{$prefix}border-color); - border-left: 4px solid var(--#{$prefix}primary); - @include border-radius($border-radius); - } - - .faq-section-icon { - display: inline-flex; - flex-shrink: 0; - align-items: center; - justify-content: center; - width: 2.75rem; - height: 2.75rem; - font-size: 1.35rem; - color: var(--#{$prefix}primary); - background: var(--#{$prefix}primary-bg-subtle); - @include border-radius($border-radius); - } - - .faq-section-title { - margin: 0; - font-size: 1.4rem; - font-weight: 600; - line-height: 1.2; - color: var(--#{$prefix}emphasis-color); - } - - .faq-section-count { - margin: .1rem 0 0; - font-size: .8rem; - color: var(--#{$prefix}secondary-color); - } - - // Per-section tone accents - @each $tone in (primary, info, warning, success, danger, secondary) { - .faq-section-#{$tone} { - border-left-color: var(--#{$prefix}#{$tone}); - - .faq-section-icon { - color: var(--#{$prefix}#{$tone}-text-emphasis); - background: var(--#{$prefix}#{$tone}-bg-subtle); - } - } - } - - // --- FAQ items (override the generic .docs-page details styling) ------ - .faq-section-items .faq-item { - margin-bottom: .5rem; - overflow: hidden; - background: var(--#{$prefix}body-bg); - border: 1px solid var(--#{$prefix}border-color); - @include border-radius($border-radius); - @include transition(border-color .15s ease, box-shadow .15s ease, transform .15s ease); - - &:hover { - border-color: var(--#{$prefix}primary-border-subtle); - transform: translateX(2px); - } - - &[open] { - border-color: var(--#{$prefix}primary-border-subtle); - box-shadow: 0 2px 8px rgba(0, 0, 0, .04); - transform: none; - } - - summary { - display: flex; - gap: .85rem; - align-items: center; - padding: 1rem 1.25rem; - font-size: 1rem; - font-weight: 500; - color: var(--#{$prefix}emphasis-color); - list-style: none; - cursor: pointer; - user-select: none; - - &::-webkit-details-marker { - display: none; - } - - &::marker { - content: ""; - } - - &::after { - content: none; // disable the generic chevron, we use our own - } - } - - .faq-q-icon { - display: inline-flex; - flex-shrink: 0; - align-items: center; - justify-content: center; - width: 1.75rem; - height: 1.75rem; - font-size: .85rem; - color: var(--#{$prefix}primary); - background: var(--#{$prefix}primary-bg-subtle); - @include border-radius(50%); - } - - .faq-q-text { - flex-grow: 1; - } - - .faq-q-chevron { - flex-shrink: 0; - color: var(--#{$prefix}secondary-color); - @include transition(transform .2s ease, color .2s ease); - } - - &[open] .faq-q-chevron { - color: var(--#{$prefix}primary); - transform: rotate(180deg); - } - - &[open] summary { - color: var(--#{$prefix}primary); - border-bottom: 1px solid var(--#{$prefix}border-color); - } - - .faq-answer { - padding: 1.1rem 1.25rem 1.1rem 3.85rem; // align with the question text (icon + gap) - font-size: .95rem; - line-height: 1.65; - color: var(--#{$prefix}body-color); - - p:last-child, - ul:last-child, - ol:last-child, - pre:last-child { - margin-bottom: 0; - } - - a { - color: var(--#{$prefix}primary); - text-decoration: underline; - text-underline-offset: 2px; - - &:hover { - text-decoration-thickness: 2px; - } - } - - code { - padding: .12em .35em; - font-size: .875em; - color: var(--#{$prefix}emphasis-color); - background: var(--#{$prefix}tertiary-bg); - border: 1px solid var(--#{$prefix}border-color); - @include border-radius($border-radius-sm); - } - - pre.astro-code, - pre { - padding: .85rem 1rem; - margin-top: .75rem; - margin-bottom: .75rem; - overflow-x: auto; - font-size: .85rem; - line-height: 1.55; - @include border-radius($border-radius-sm); - - code { - padding: 0; - background: transparent; - border: 0; - } - } - } - } - - // --- CTA footer ------------------------------------------------------- - .faq-cta { - padding: 2.5rem 1.5rem; - text-align: center; - background: radial-gradient(ellipse at top, rgba(var(--#{$prefix}primary-rgb), .08), transparent 60%), var(--#{$prefix}body-bg); - border: 1px solid var(--#{$prefix}border-color); - @include border-radius($border-radius-xl); - } - - .faq-cta-icon { - display: inline-flex; - align-items: center; - justify-content: center; - width: 3.5rem; - height: 3.5rem; - margin-bottom: 1rem; - font-size: 1.6rem; - color: var(--#{$prefix}primary); - background: var(--#{$prefix}primary-bg-subtle); - @include border-radius(50%); - } - - .faq-cta h2 { - margin: 0 0 .5rem; - font-size: 1.5rem; - font-weight: 600; - color: var(--#{$prefix}emphasis-color); - } - - .faq-cta p { - max-width: 32rem; - margin: 0 auto 1.5rem; - color: var(--#{$prefix}secondary-color); - } - - .faq-cta-actions { - display: inline-flex; - flex-wrap: wrap; - gap: .5rem; - justify-content: center; - } -} diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_dropdown.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_dropdown.scss deleted file mode 100644 index 9ddf2f7c..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_dropdown.scss +++ /dev/null @@ -1,225 +0,0 @@ -// -// Component: Dropdown -// - -// General Dropdown Rules - -// Ensure children cannot overflow and break the dropdown border radius -.dropdown-menu { - overflow: hidden; -} - -.fs-7 { - .dropdown-menu { - font-size: $font-size-sm !important; - } - - .dropdown-toggle::after { - vertical-align: .2rem; - } -} - -.dropdown-item-title { - margin: 0; - font-size: $font-size-base; -} - -.dropdown-icon { - &::after { - margin-left: 0; - } -} - -// Dropdown Sizes -.dropdown-menu-lg { - min-width: 280px; - max-width: 300px; - padding: 0; - - .dropdown-divider { - margin: 0; - } - - .dropdown-item { - padding: $dropdown-padding-y $dropdown-item-padding-x; - } - - p { - margin: 0; - word-wrap: break-word; - white-space: normal; - } -} - -// Dropdown Submenu -.dropdown-submenu { - position: relative; - - > a::after { - @include caret-end(); - float: right; - margin-top: .5rem; - margin-left: .5rem; - } - - > .dropdown-menu { - top: 0; - left: 100%; - margin-top: 0; - margin-left: 0; - } -} - -// Dropdown Hover -.dropdown-hover { - &:hover, - &.nav-item.dropdown:hover, - .dropdown-submenu:hover, - &.dropdown-submenu:hover { - > .dropdown-menu { - display: block; - } - } -} - - -// Dropdown Sizes -.dropdown-menu-xl { - min-width: 360px; - max-width: 420px; - padding: 0; - - .dropdown-divider { - margin: 0; - } - - .dropdown-item { - padding: $dropdown-padding-y $dropdown-item-padding-x; - } - - p { - margin: 0; - word-wrap: break-word; - white-space: normal; - } -} - -// Dropdown header and footer -.dropdown-footer, -.dropdown-header { - display: block; - padding: .5rem $dropdown-item-padding-x; - font-size: $font-size-sm; - text-align: center; -} - -// Add fade animation to dropdown menus by appending -// the class .animated-dropdown-menu to the .dropdown-menu ul (or ol) -.open:not(.dropup) > .animated-dropdown-menu { - animation: flipInX .7s both; - backface-visibility: visible !important; -} - -// Fix dropdown menu in navbars -.navbar-custom-menu > .navbar-nav { - > li { - position: relative; - > .dropdown-menu { - position: absolute; - right: 0; - left: auto; - } - } -} - -@include media-breakpoint-down(sm) { - .navbar-custom-menu > .navbar-nav { - float: right; - > li { - position: static; - > .dropdown-menu { - position: absolute; - right: 5%; - left: auto; - background-color: var(--#{$prefix}body-bg); - border: 1px solid var(--#{$prefix}border-color); - } - } - } -} - -// User Menu -.navbar-nav > .user-menu { - > .nav-link::after { - content: none; - } - - > .dropdown-menu { - width: 280px; - padding: 0; - - // Header menu - > li.user-header { - min-height: 175px; - padding: 10px; - text-align: center; - - // User image - > img { - z-index: 5; - width: 90px; - height: 90px; - border: 3px solid; - border-color: transparent; - border-color: var(--#{$prefix}border-color-translucent); - } - - > p { - z-index: 5; - margin-top: 10px; - font-size: 17px; - word-wrap: break-word; - - > small { - display: block; - font-size: 12px; - } - } - } - - // Menu Body - > .user-body { - @include clearfix(); - padding: 15px; - border-top: 1px solid var(--#{$prefix}border-color); - border-bottom: 1px solid var(--#{$prefix}border-color-translucent); - - a { - text-decoration: none; - } - } - - // Menu Footer - > .user-footer { - @include clearfix(); - padding: 10px; - background-color: var(--#{$prefix}light-bg); - } - } - - .user-image { - @include media-breakpoint-up(sm) { - float: none; - margin-top: -8px; - margin-right: .4rem; - line-height: 10px; - } - - float: left; - width: $lte-sidebar-user-image-width; - height: $lte-sidebar-user-image-width; - margin-top: -2px; - // margin-right: 10px; - @include border-radius(50%); - } -} diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_info-box.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_info-box.scss deleted file mode 100644 index ad65d71e..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_info-box.scss +++ /dev/null @@ -1,131 +0,0 @@ -// -// Component: Info Box -// - -.info-box { - @include box-shadow($lte-card-shadow); - @include border-radius($border-radius); - - position: relative; - display: flex; - width: 100%; - min-height: 80px; - padding: .5rem; - margin-bottom: map-get($spacers, 3); - color: var(--#{$prefix}body-color); - background-color: var(--#{$prefix}body-bg); - - .progress { - height: 2px; - margin: 5px 0; - background-color: rgba(var(--#{$lte-prefix}card-variant-color-rgb), .125); - - .progress-bar { - background-color: var(--#{$lte-prefix}card-variant-color); - } - } - - .info-box-icon { - display: flex; - align-items: center; - justify-content: center; - width: 70px; - font-size: 1.875rem; - text-align: center; - @include border-radius($border-radius); - - > img { - max-width: 100%; - } - } - - .info-box-content { - display: flex; - flex: 1; - flex-direction: column; - justify-content: center; - padding: 0 10px; - line-height: 1.8; - } - - .info-box-number { - display: block; - margin-top: .25rem; - font-weight: $font-weight-bold; - } - - .progress-description, - .info-box-text { - display: block; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - .info-box-more { - display: block; - } - - .progress-description { - margin: 0; - - } - - @include media-breakpoint-up(md) { - .col-xl-2 &, - .col-lg-2 &, - .col-md-2 & { - .progress-description { - display: none; - } - } - - .col-xl-3 &, - .col-lg-3 &, - .col-md-3 & { - .progress-description { - display: none; - } - } - } - - @include media-breakpoint-up(lg) { - .col-xl-2 &, - .col-lg-2 &, - .col-md-2 & { - .progress-description { - @include font-size(.75rem); - display: block; - } - } - - .col-xl-3 &, - .col-lg-3 &, - .col-md-3 & { - .progress-description { - @include font-size(.75rem); - display: block; - } - } - } - - @include media-breakpoint-up(xl) { - .col-xl-2 &, - .col-lg-2 &, - .col-md-2 & { - .progress-description { - @include font-size(1rem); - display: block; - } - } - - .col-xl-3 &, - .col-lg-3 &, - .col-md-3 & { - .progress-description { - @include font-size(1rem); - display: block; - } - } - } -} diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_miscellaneous.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_miscellaneous.scss deleted file mode 100644 index e04ec287..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_miscellaneous.scss +++ /dev/null @@ -1,22 +0,0 @@ -// -// Misc: Miscellaneous -// - -// Image sizes -.img-size-64, -.img-size-50, -.img-size-32 { - height: auto; -} - -.img-size-64 { - width: 64px; -} - -.img-size-50 { - width: 50px; -} - -.img-size-32 { - width: 32px; -} diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_mixins.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_mixins.scss deleted file mode 100644 index 44f6c188..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_mixins.scss +++ /dev/null @@ -1,6 +0,0 @@ -// -// General: Mixins -// - -@import "mixins/animations"; -@import "mixins/scrollbar"; diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_progress-bars.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_progress-bars.scss deleted file mode 100644 index c99605da..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_progress-bars.scss +++ /dev/null @@ -1,66 +0,0 @@ -// -// Component: Progress Bar -// - -//General CSS -.progress { - @include box-shadow(null); - @include border-radius($lte-progress-bar-border-radius); - - // Vertical bars - &.vertical { - position: relative; - display: inline-block; - width: 30px; - height: 200px; - margin-right: 10px; - - > .progress-bar { - position: absolute; - bottom: 0; - width: 100%; - } - - //Sizes - &.sm, - &.progress-sm { - width: 20px; - } - - &.xs, - &.progress-xs { - width: 10px; - } - - &.xxs, - &.progress-xxs { - width: 3px; - } - } -} - -.progress-group { - margin-bottom: map-get($spacers, 2); -} - -// size variation -.progress-sm { - height: 10px; -} - -.progress-xs { - height: 7px; -} - -.progress-xxs { - height: 3px; -} - -// Remove margins from progress bars when put in a table -.table { - tr > td { - .progress { - margin: 0; - } - } -} diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_root.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_root.scss deleted file mode 100644 index 2633fbd2..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_root.scss +++ /dev/null @@ -1,5 +0,0 @@ -:root, -[data-bs-theme="light"] { - // Sidebar - --#{$lte-prefix}sidebar-width: #{$lte-sidebar-width}; -} diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_small-box.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_small-box.scss deleted file mode 100644 index ce2e4863..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_small-box.scss +++ /dev/null @@ -1,127 +0,0 @@ -// -// Component: Small Box -// - -.small-box { - @include border-radius($border-radius); - @include box-shadow($lte-card-shadow); - position: relative; - display: block; - margin-bottom: 1.25rem; - --bs-link-color-rgb: none; - --bs-link-hover-color-rgb: none; - --bs-heading-color: none; - - // content wrapper - > .inner { - padding: 10px; - } - - > .small-box-footer { - position: relative; - z-index: 10; - display: block; - padding: 3px 0; - text-align: center; - background-color: rgba($black, .07); - - &:hover { - background-color: rgba($black, .1); - } - } - - h3 { - @include font-size(2.2rem); - padding: 0; - margin: 0 0 10px; - font-weight: 700; - white-space: nowrap; - } - - @include media-breakpoint-up(lg) { - .col-xl-2 &, - .col-lg-2 &, - .col-md-2 & { - h3 { - @include font-size(1.6rem); - } - } - - .col-xl-3 &, - .col-lg-3 &, - .col-md-3 & { - h3 { - @include font-size(1.6rem); - } - } - } - - @include media-breakpoint-up(xl) { - .col-xl-2 &, - .col-lg-2 &, - .col-md-2 & { - h3 { - @include font-size(2.2rem); - } - } - - .col-xl-3 &, - .col-lg-3 &, - .col-md-3 & { - h3 { - @include font-size(2.2rem); - } - } - } - - p { - font-size: 1rem; - - > small { - display: block; - margin-top: 5px; - font-size: .9rem; - color: $gray-100; - } - } - - h3, - p { - z-index: 5; - } - - // the icon - .small-box-icon { - position: absolute; - top: 15px; - right: 15px; - z-index: 0; - height: 70px; - font-size: 70px; - color: rgba($black, .15); - @include transition(transform $lte-transition-speed linear); - } - - // Small box hover state - &:hover { - // Animate icons on small box hover - .small-box-icon { - transform: scale(1.1); - } - } -} - -@include media-breakpoint-down(sm) { - // No need for icons on very small devices - .small-box { - text-align: center; - - .small-box-icon { - display: none; - } - - p { - font-size: 12px; - } - } -} diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_table.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_table.scss deleted file mode 100644 index 2bac75b7..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_table.scss +++ /dev/null @@ -1,67 +0,0 @@ -// -// Component: Table -// - -.table { - &:not(.table-dark) { - color: inherit; - } - - // fixed table head - // Uses Bootstrap CSS variables so the sticky header follows light/dark mode - // automatically. Fixes #6026. - &.table-head-fixed { - thead tr:nth-child(1) th { - position: sticky; - top: 0; - z-index: 10; - background-color: var(--bs-body-bg, #{$white}); - border-bottom: 0; - box-shadow: inset 0 1px 0 var(--bs-border-color, #{$table-border-color}), inset 0 -1px 0 var(--bs-border-color, #{$table-border-color}); - } - } - - // no border - &.no-border { - &, - td, - th { - border: 0; - } - } - - // .text-center in tables - &.text-center { - &, - td, - th { - text-align: center; - } - } - - &.table-valign-middle { - thead > tr > th, - thead > tr > td, - tbody > tr > th, - tbody > tr > td { - vertical-align: middle; - } - } - - .card-body.p-0 & { - thead > tr > th, - thead > tr > td, - tfoot > tr > th, - tfoot > tr > td, - tbody > tr > th, - tbody > tr > td { - &:first-of-type { - padding-left: map-get($spacers, 4); - } - - &:last-of-type { - padding-right: map-get($spacers, 4); - } - } - } -} diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_timeline.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_timeline.scss deleted file mode 100644 index f1521aee..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_timeline.scss +++ /dev/null @@ -1,121 +0,0 @@ -// -// Component: Timeline -// - -.timeline { - position: relative; - padding: 0; - margin: 0 0 45px; - // The line - &::before { - @include border-radius($border-radius); - position: absolute; - top: 0; - bottom: 0; - left: 31px; - width: 4px; - margin: 0; - content: ""; - background-color: var(--#{$prefix}border-color); - } - // Element - > div { - &::before, - &::after { - display: table; - content: ""; - } - - position: relative; - margin-right: 10px; - margin-bottom: 15px; - // The content - > .timeline-item { - @include box-shadow($lte-card-shadow); - @include border-radius($border-radius); - position: relative; - padding: 0; - margin-top: 0; - margin-right: 15px; - margin-left: 60px; - color: var(--#{$prefix}body-color); - background-color: var(--#{$prefix}body-bg); - // The time and header - > .time { - float: right; - padding: 10px; - font-size: 12px; - color: var(--#{$prefix}secondary-color); - } - // Header - > .timeline-header { - padding: 10px; - margin: 0; - font-size: 16px; - line-height: 1.1; - color: var(--#{$prefix}secondary-color); - border-bottom: 1px solid var(--#{$prefix}border-color); - // Link in header - > a { - font-weight: 600; - text-decoration: none; - } - } - // Item body and footer - > .timeline-body, - > .timeline-footer { - padding: 10px; - } - - > .timeline-body { - > img { - margin: 10px; - } - > dl, - ol, - ul { - margin: 0; - } - } - - - } - - .timeline-icon { - position: absolute; - top: 0; - left: 18px; - width: 30px; - height: 30px; - font-size: 16px; - line-height: 30px; - text-align: center; - background-color: var(--#{$prefix}secondary-bg); - border-radius: 50%; // stylelint-disable-line property-disallowed-list - } - } - // Time label - > .time-label { - > span { - @include border-radius(4px); - display: inline-block; - padding: 5px; - font-weight: 600; - background-color: var(--#{$prefix}body-bg); - } - } -} - -.timeline-inverse { - > div { - > .timeline-item { - @include box-shadow(none); - background-color: var(--#{$prefix}tertiary-bg); - border: 1px solid var(--#{$prefix}border-color); - - > .timeline-header { - border-bottom-color: var(--#{$prefix}border-color); - } - } - } -} diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_toasts.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_toasts.scss deleted file mode 100644 index 14be1ce6..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_toasts.scss +++ /dev/null @@ -1,33 +0,0 @@ -// -// Toast -// - -@each $name, $color in $theme-colors { - .toast-#{$name} { - --#{$prefix}toast-header-color: #{color-contrast($color)}; - --#{$prefix}toast-header-bg: #{$color}; - --#{$prefix}toast-header-border-color: #{$color}; - --#{$prefix}toast-border-color: #{$color}; - --#{$prefix}toast-bg: var(--#{$prefix}#{$name}-bg-subtle); - - @if color-contrast($color) == $color-contrast-light { - .btn-close { - @include btn-close-white(); - } - } - } -} - -@if $enable-dark-mode { - @include color-mode(dark) { - @each $name, $color in $theme-colors { - .toast-#{$name} { - @if color-contrast($color) == $color-contrast-dark { - .btn-close { - --#{$prefix}btn-close-white-filter: none; - } - } - } - } - } -} diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_variables-dark.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_variables-dark.scss deleted file mode 100644 index 9bd8681f..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_variables-dark.scss +++ /dev/null @@ -1,16 +0,0 @@ -// SIDEBAR SKINS -// -------------------------------------------------------- - -$lte-sidebar-hover-bg-dark: rgba(255, 255, 255, .1) !default; -$lte-sidebar-color-dark: #c2c7d0 !default; -$lte-sidebar-hover-color-dark: $white !default; -$lte-sidebar-active-color-dark: $white !default; -$lte-sidebar-menu-active-bg-dark: rgba(255, 255, 255, .1) !default; -$lte-sidebar-menu-active-color-dark: $white !default; -$lte-sidebar-submenu-bg-dark: transparent !default; -$lte-sidebar-submenu-color-dark: #c2c7d0 !default; -$lte-sidebar-submenu-hover-color-dark: $white !default; -$lte-sidebar-submenu-hover-bg-dark: rgba(255, 255, 255, .1) !default; -$lte-sidebar-submenu-active-color-dark: $white !default; -$lte-sidebar-submenu-active-bg-dark: rgba(255, 255, 255, .1) !default; -$lte-sidebar-header-color-dark: tint-color(#c2c7d0, 5%) !default; diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_variables.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_variables.scss deleted file mode 100644 index 35675327..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/_variables.scss +++ /dev/null @@ -1,131 +0,0 @@ -// -// Custom AdminLTE Variables -// - -// Prefix for :root CSS variables and others. -$lte-prefix: lte- !default; - -// TRANSITIONS SETTINGS -// -------------------------------------------------------- -// Transition global options -$lte-transition-speed: .3s !default; -$lte-transition-fn: ease-in-out !default; - -// SIDEBAR -// -------------------------------------------------------- -$lte-sidebar-width: 250px !default; -$lte-sidebar-breakpoint: lg !default; -$lte-sidebar-padding-x: .5rem !default; -$lte-sidebar-padding-x-compact: .5rem !default; -$lte-sidebar-padding-y: .5rem !default; -$lte-sidebar-padding-y-compact: .25rem !default; -$lte-sidebar-transition: min-width $lte-transition-speed $lte-transition-fn, - max-width $lte-transition-speed $lte-transition-fn, - margin-left $lte-transition-speed $lte-transition-fn, - margin-right $lte-transition-speed $lte-transition-fn !default; - -// SIDEBAR SKINS -// -------------------------------------------------------- - -$lte-sidebar-hover-bg: rgba($black, .1) !default; -$lte-sidebar-color: $gray-800 !default; -$lte-sidebar-hover-color: $gray-900 !default; -$lte-sidebar-active-color: $black !default; -$lte-sidebar-menu-active-bg: rgba($black, .1) !default; -$lte-sidebar-menu-active-color: $black !default; -$lte-sidebar-submenu-bg: transparent !default; -$lte-sidebar-submenu-color: #777 !default; -$lte-sidebar-submenu-hover-color: $black !default; -$lte-sidebar-submenu-hover-bg: rgba($black, .1) !default; -$lte-sidebar-submenu-active-color: $gray-900 !default; -$lte-sidebar-submenu-active-bg: rgba($black, .1) !default; -$lte-sidebar-header-color: shade-color($gray-800, 5%) !default; - -// SIDEBAR MINI -// -------------------------------------------------------- -$nav-link-padding-x-compact: .25rem !default; -$lte-sidebar-mini-width: ($nav-link-padding-x + $lte-sidebar-padding-x + .8rem) * 2 !default; -$lte-sidebar-mini-width-compact: ($nav-link-padding-x-compact + $lte-sidebar-padding-x-compact + .8rem) * 2 !default; -$lte-sidebar-nav-icon-width: $lte-sidebar-mini-width - (($lte-sidebar-padding-x + $nav-link-padding-x) * 2) !default; -$lte-sidebar-user-image-width: $lte-sidebar-nav-icon-width + ($nav-link-padding-x * .4) !default; - -// MAIN HEADER -// -------------------------------------------------------- -$nav-link-height-compact: 1.75rem !default; -$lte-app-header-bottom-border-width: $border-width !default; -$lte-app-header-bottom-border-color: var(--#{$prefix}border-color) !default; -$lte-app-header-bottom-border: $lte-app-header-bottom-border-width solid $lte-app-header-bottom-border-color !default; -$lte-app-header-link-padding-y: $navbar-padding-y !default; -$lte-app-header-height: ($nav-link-height + ($lte-app-header-link-padding-y * 2)) !default; -$lte-app-header-height-compact: ($nav-link-height-compact + ($lte-app-header-link-padding-y * 2)) !default; -$lte-zindex-fixed-header: $zindex-fixed !default; - -// APP MAIN -// -------------------------------------------------------- -$lte-app-main-padding-bottom: $grid-gutter-width * .5 !default; - -// CONTENT PADDING -// -------------------------------------------------------- -$lte-content-padding-y: 0 !default; -$lte-content-padding-x: .5rem !default; - -// MAIN FOOTER -// -------------------------------------------------------- -$lte-app-footer-padding: 1rem !default; -$lte-app-footer-padding-compact: .5rem !default; -$lte-app-footer-border-top-width: 1px !default; -$lte-app-footer-border-top-color: var(--#{$prefix}border-color) !default; -$lte-app-footer-border-top: $lte-app-footer-border-top-width solid $lte-app-footer-border-top-color !default; -$lte-app-footer-bg: var(--#{$prefix}body-bg) !default; -$lte-app-footer-color: var(--#{$prefix}secondary-color) !default; -$lte-zindex-fixed-footer: $zindex-fixed !default; -// CONTENT BOTTOM AREA -// -------------------------------------------------------- -$lte-app-content-bottom-area-margin-bottom: -$lte-app-main-padding-bottom !default; -$lte-app-content-bottom-area-color: $lte-app-footer-color !default; -$lte-app-content-bottom-area-bg: $lte-app-footer-bg !default; -$lte-app-content-bottom-area-top-border: $lte-app-footer-border-top !default; -$lte-app-content-bottom-area-padding-y: $lte-app-footer-padding !default; -$lte-app-content-bottom-area-padding-x: 0 !default; - -// CONTENT TOP AREA -// -------------------------------------------------------- -$lte-app-content-top-area-top-border: $lte-app-footer-border-top !default; -$lte-app-content-top-area-padding-y: $lte-app-footer-padding !default; -$lte-app-content-top-area-padding-x: 0 !default; - -// BRAND LINK -// -------------------------------------------------------- -$navbar-brand-padding-y-compact: $navbar-brand-padding-y * .75 !default; -$navbar-padding-y-compact: $navbar-padding-y * .5 !default; -$lte-brand-link-padding-y: $navbar-brand-padding-y + $navbar-padding-y !default; -$lte-brand-link-padding-y-compact: $navbar-brand-padding-y-compact + $navbar-padding-y-compact !default; -$lte-brand-link-padding-x: $lte-sidebar-padding-x !default; -$lte-brand-link-padding-x-compact: $lte-sidebar-padding-x-compact !default; -$lte-brand-link-border-buttom: 1px !default; - -// CARDS -// -------------------------------------------------------- -$lte-card-shadow: 0 0 1px rgba(var(--#{$prefix}body-color-rgb), .125), 0 1px 3px rgba(var(--#{$prefix}body-color-rgb), .2) !default; -$lte-card-title-font-size: 1.1rem !default; -$lte-card-title-font-weight: $font-weight-normal !default; - -// PROGRESS BARS -// -------------------------------------------------------- -$lte-progress-bar-border-radius: 1px !default; - -// CALLOUTS -// -------------------------------------------------------- -$lte-callout-link-font-weight: $alert-link-font-weight !default; - -// DIRECT CHAT -// -------------------------------------------------------- -$lte-direct-chat-default-msg-bg: var(--#{$prefix}secondary-bg) !default; -$lte-direct-chat-default-font-color: var(--#{$prefix}emphasis-color) !default; -$lte-direct-chat-default-msg-border-color: var(--#{$prefix}border-color) !default; - -// Z-INDEX -// -------------------------------------------------------- -$lte-zindex-app-header: $zindex-fixed + 4 !default; -$lte-zindex-sidebar: $zindex-fixed + 8 !default; -$lte-zindex-sidebar-overlay: $lte-zindex-sidebar - 1 !default; diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/adminlte.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/adminlte.scss deleted file mode 100644 index 47a13250..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/adminlte.scss +++ /dev/null @@ -1,81 +0,0 @@ -/*! - * AdminLTE v4.0.0 - * Author: Colorlib - * Website: AdminLTE.io - * License: Open source - MIT - */ - -// Bootstrap Configuration -// --------------------------------------------------- -@import "bootstrap/scss/functions"; - -// AdminLTE Configuration -// --------------------------------------------------- -@import "bootstrap-variables"; // little modified are here - -// Bootstrap Configuration -// --------------------------------------------------- -@import "bootstrap/scss/variables"; -@import "bootstrap/scss/variables-dark"; -@import "bootstrap/scss/maps"; -@import "bootstrap/scss/mixins"; -@import "bootstrap/scss/utilities"; - -// Bootstrap Layout & components -@import "bootstrap/scss/root"; -@import "bootstrap/scss/reboot"; -@import "bootstrap/scss/type"; -@import "bootstrap/scss/images"; -@import "bootstrap/scss/containers"; -@import "bootstrap/scss/grid"; -@import "bootstrap/scss/tables"; -@import "bootstrap/scss/forms"; -@import "bootstrap/scss/buttons"; -@import "bootstrap/scss/transitions"; -@import "bootstrap/scss/dropdown"; -@import "bootstrap/scss/button-group"; -@import "bootstrap/scss/nav"; -@import "bootstrap/scss/navbar"; -@import "bootstrap/scss/card"; -@import "bootstrap/scss/accordion"; -@import "bootstrap/scss/breadcrumb"; -@import "bootstrap/scss/pagination"; -@import "bootstrap/scss/badge"; -@import "bootstrap/scss/alert"; -@import "bootstrap/scss/progress"; -@import "bootstrap/scss/list-group"; -@import "bootstrap/scss/close"; -@import "bootstrap/scss/toasts"; -@import "bootstrap/scss/modal"; -@import "bootstrap/scss/tooltip"; -@import "bootstrap/scss/popover"; -@import "bootstrap/scss/carousel"; -@import "bootstrap/scss/spinners"; -@import "bootstrap/scss/offcanvas"; -@import "bootstrap/scss/placeholders"; - -// Bootstrap Helpers -@import "bootstrap/scss/helpers"; - -// Bootstrap Utilities -@import "bootstrap/scss/utilities/api"; - -// AdminLTE Configuration -// --------------------------------------------------- -@import "variables"; -@import "variables-dark"; -@import "mixins"; - -// AdiminLTE Parts -// --------------------------------------------------- -@import "parts/core"; -@import "parts/components"; -@import "parts/extra-components"; -@import "parts/pages"; -@import "parts/miscellaneous"; - -// AdminLTE Accessibility Styles - WCAG 2.1 AA Compliance -@import "accessibility"; - -// AdminLTE Documentation Styles (only applies to pages with body.docs-page) -@import "docs"; diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/mixins/_animations.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/mixins/_animations.scss deleted file mode 100644 index 5a403175..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/mixins/_animations.scss +++ /dev/null @@ -1,116 +0,0 @@ -// -// Mixins: Animation -// - - -@keyframes flipInX { - 0% { - opacity: 0; - transition-timing-function: ease-in; - transform: perspective(400px) rotate3d(1, 0, 0, 90deg); - } - - 40% { - transition-timing-function: ease-in; - transform: perspective(400px) rotate3d(1, 0, 0, -20deg); - } - - 60% { - opacity: 1; - transform: perspective(400px) rotate3d(1, 0, 0, 10deg); - } - - 80% { - transform: perspective(400px) rotate3d(1, 0, 0, -5deg); - } - - 100% { - transform: perspective(400px); - } -} - -@keyframes fadeIn { - from { - opacity: 0; - } - - to { - opacity: 1; - } -} - -@keyframes fadeOut { - from { - opacity: 1; - } - - to { - opacity: 0; - } -} - -@keyframes shake { - 0% { - transform: translate(2px, 1px) rotate(0deg); - } - 10% { - transform: translate(-1px, -2px) rotate(-2deg); - } - 20% { - transform: translate(-3px, 0) rotate(3deg); - } - 30% { - transform: translate(0, 2px) rotate(0deg); - } - 40% { - transform: translate(1px, -1px) rotate(1deg); - } - 50% { - transform: translate(-1px, 2px) rotate(-1deg); - } - 60% { - transform: translate(-3px, 1px) rotate(0deg); - } - 70% { - transform: translate(2px, 1px) rotate(-2deg); - } - 80% { - transform: translate(-1px, -1px) rotate(4deg); - } - 90% { - transform: translate(2px, 2px) rotate(0deg); - } - 100% { - transform: translate(1px, -2px) rotate(-1deg); - } -} - -@keyframes wobble { - 0% { - transform: none; - } - - 15% { - transform: translate3d(-25%, 0, 0) rotate3d(0, 0, 1, -5deg); - } - - 30% { - transform: translate3d(20%, 0, 0) rotate3d(0, 0, 1, 3deg); - } - - 45% { - transform: translate3d(-15%, 0, 0) rotate3d(0, 0, 1, -3deg); - } - - 60% { - transform: translate3d(10%, 0, 0) rotate3d(0, 0, 1, 2deg); - } - - 75% { - transform: translate3d(-5%, 0, 0) rotate3d(0, 0, 1, -1deg); - } - - 100% { - transform: none; - } -} diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/mixins/_scrollbar.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/mixins/_scrollbar.scss deleted file mode 100644 index 65c3b53d..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/mixins/_scrollbar.scss +++ /dev/null @@ -1,36 +0,0 @@ -// -// Mixins: Scrollbar -// - -@mixin scrollbar-color-gray() { - scrollbar-color: var(--#{$prefix}secondary-bg) transparent; - - &::-webkit-scrollbar-thumb { - background-color: var(--#{$prefix}secondary-bg); - } - - &::-webkit-scrollbar-track { - background-color: transparent; - } - - &::-webkit-scrollbar-corner { - background-color: transparent; - } -} - -@mixin scrollbar-width-thin() { - scrollbar-width: thin; - - &::-webkit-scrollbar { - width: .5rem; - height: .5rem; - } -} - -@mixin scrollbar-width-none() { - scrollbar-width: none; - - &::-webkit-scrollbar { - width: 0; - } -} diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/pages/_lockscreen.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/pages/_lockscreen.scss deleted file mode 100644 index 030e69fd..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/pages/_lockscreen.scss +++ /dev/null @@ -1,75 +0,0 @@ -// -// Pages: Lock Screen -// - -// ADD THIS CLASS TO THE TAG -.lockscreen { - // User name [optional] - .lockscreen-name { - font-weight: 600; - text-align: center; - } - - .lockscreen-logo { - margin-bottom: 25px; - font-size: 35px; - font-weight: 300; - text-align: center; - - a { - color: var(--#{$prefix}emphasis-color); - text-decoration: none; - } - } - - .lockscreen-wrapper { - max-width: 400px; - margin: 0 auto; - margin-top: 10%; - } - - // Will contain the image and the sign in form - .lockscreen-item { - position: relative; - width: 290px; - padding: 0; - margin: 10px auto 30px; - background-color: var(--#{$prefix}body-bg); - @include border-radius(4px); - } - - // User image - .lockscreen-image { - position: absolute; - top: -25px; - left: -10px; - z-index: 10; - padding: 5px; - background-color: var(--#{$prefix}body-bg); - @include border-radius(50%); - - > img { - @include border-radius(50%); - width: 70px; - height: 70px; - } - } - - // Contains the password input and the login button - .lockscreen-credentials { - margin-left: 70px; - - .form-control { - border: 0; - } - - .btn { - padding: 0 10px; - border: 0; - } - } - - .lockscreen-footer { - margin-top: 10px; - } -} diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/pages/_login_and_register.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/pages/_login_and_register.scss deleted file mode 100644 index cc176307..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/pages/_login_and_register.scss +++ /dev/null @@ -1,100 +0,0 @@ -// -// Pages: Login & Register -// - -.login-logo, -.register-logo { - margin-bottom: .9rem; - font-size: 2.1rem; - font-weight: 300; - text-align: center; - - a { - color: var(--#{$prefix}secondary-color); - text-decoration: none; - } -} - -.login-page, -.register-page { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - min-height: 100vh; -} - -.login-box, -.register-box { - width: 400px; - - @media (max-width: map-get($grid-breakpoints, sm)) { - width: 90%; - margin-top: .5rem; - } - - .card { - margin-bottom: 0; - } -} - -.login-card-body, -.register-card-body { - padding: 20px; - color: var(--#{$prefix}secondary-color); - background-color: var(--#{$prefix}body-bg); - border-top: 0; - - .input-group { - .form-control { - &:focus { - box-shadow: none; - - ~ .input-group-prepend .input-group-text, - ~ .input-group-append .input-group-text { - border-color: $input-focus-border-color; - } - } - - &.is-valid { - &:focus { - box-shadow: none; - } - - ~ .input-group-prepend .input-group-text, - ~ .input-group-append .input-group-text { - border-color: $success; - } - } - - &.is-invalid { - &:focus { - box-shadow: none; - } - - ~ .input-group-append .input-group-text { - border-color: $danger; - } - } - } - - .input-group-text { - color: var(--#{$prefix}secondary-color); - background-color: transparent; - @include border-top-end-radius($border-radius); - @include border-bottom-end-radius($border-radius); - @include transition($input-transition); - } - } -} - -.login-box-msg, -.register-box-msg { - padding: 0 20px 20px; - margin: 0; - text-align: center; -} - -.social-auth-links { - margin: 10px 0; -} diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/parts/_components.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/parts/_components.scss deleted file mode 100644 index ec223fd9..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/parts/_components.scss +++ /dev/null @@ -1,7 +0,0 @@ -// -// Part: Components -// - -@import "../progress-bars"; -@import "../cards"; -@import "../table"; diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/parts/_core.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/parts/_core.scss deleted file mode 100644 index 255dc03d..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/parts/_core.scss +++ /dev/null @@ -1,15 +0,0 @@ -// -// Part: Core -// - -@import "../root"; -@import "../app-wrapper"; -@import "../app-content"; -@import "../app-header"; -@import "../app-sidebar"; -@import "../app-main"; -@import "../app-footer"; -@import "../dropdown"; -@import "../callouts"; -@import "../compact-mode"; -@import "../docs"; diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/parts/_extra-components.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/parts/_extra-components.scss deleted file mode 100644 index 1028da98..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/parts/_extra-components.scss +++ /dev/null @@ -1,9 +0,0 @@ -// -// Part: Extra Components -// - -@import "../small-box"; -@import "../info-box"; -@import "../timeline"; -@import "../direct-chat"; -@import "../toasts"; diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/parts/_miscellaneous.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/parts/_miscellaneous.scss deleted file mode 100644 index cfdd705a..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/parts/_miscellaneous.scss +++ /dev/null @@ -1,5 +0,0 @@ -// -// Part: Miscellaneous -// - -@import "../miscellaneous"; diff --git a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/parts/_pages.scss b/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/parts/_pages.scss deleted file mode 100644 index 01cab17e..00000000 --- a/extensions/pagetop-bootsier/assets/adminlte-4.0.0/scss/parts/_pages.scss +++ /dev/null @@ -1,6 +0,0 @@ -// -// Part: Pages -// - -@import "../pages/login_and_register"; -@import "../pages/lockscreen"; diff --git a/extensions/pagetop-bootsier/assets/bootsier.scss b/extensions/pagetop-bootsier/assets/bootsier.scss deleted file mode 100644 index 68b778fa..00000000 --- a/extensions/pagetop-bootsier/assets/bootsier.scss +++ /dev/null @@ -1,91 +0,0 @@ -// AdminLTE v4.0.0 + Bootstrap 5.3.8 - PageTop Bootsier Styles. - -// Bootstrap Configuration -// --------------------------------------------------- -@import "bootstrap-5.3.8/scss/functions"; - -// AdminLTE Configuration (little modified are here) -// --------------------------------------------------- -@import "adminlte-4.0.0/scss/bootstrap-variables"; - -// Bootsier Bootstrap variable overrides -// --------------------------------------------------- -@import "bootsier-variables"; - -// Bootstrap Configuration -// --------------------------------------------------- -@import "bootstrap-5.3.8/scss/variables"; -@import "bootstrap-5.3.8/scss/variables-dark"; -@import "bootstrap-5.3.8/scss/maps"; -@import "bootstrap-5.3.8/scss/mixins"; -@import "bootstrap-5.3.8/scss/utilities"; - -// Bootstrap Layout & components -@import "bootstrap-5.3.8/scss/root"; -@import "bootstrap-5.3.8/scss/reboot"; -@import "bootstrap-5.3.8/scss/type"; -@import "bootstrap-5.3.8/scss/images"; -@import "bootstrap-5.3.8/scss/containers"; -@import "bootstrap-5.3.8/scss/grid"; -@import "bootstrap-5.3.8/scss/tables"; -@import "bootstrap-5.3.8/scss/forms"; -@import "bootstrap-5.3.8/scss/buttons"; -@import "bootstrap-5.3.8/scss/transitions"; -@import "bootstrap-5.3.8/scss/dropdown"; -@import "bootstrap-5.3.8/scss/button-group"; -@import "bootstrap-5.3.8/scss/nav"; -@import "bootstrap-5.3.8/scss/navbar"; -@import "bootstrap-5.3.8/scss/card"; -@import "bootstrap-5.3.8/scss/accordion"; -@import "bootstrap-5.3.8/scss/breadcrumb"; -@import "bootstrap-5.3.8/scss/pagination"; -@import "bootstrap-5.3.8/scss/badge"; -@import "bootstrap-5.3.8/scss/alert"; -@import "bootstrap-5.3.8/scss/progress"; -@import "bootstrap-5.3.8/scss/list-group"; -@import "bootstrap-5.3.8/scss/close"; -@import "bootstrap-5.3.8/scss/toasts"; -@import "bootstrap-5.3.8/scss/modal"; -@import "bootstrap-5.3.8/scss/tooltip"; -@import "bootstrap-5.3.8/scss/popover"; -@import "bootstrap-5.3.8/scss/carousel"; -@import "bootstrap-5.3.8/scss/spinners"; -@import "bootstrap-5.3.8/scss/offcanvas"; -@import "bootstrap-5.3.8/scss/placeholders"; - -// Bootstrap Helpers -@import "bootstrap-5.3.8/scss/helpers"; - -// Bootsier Utilities (must precede utilities/api to extend $utilities) -@import "bootsier-utilities"; - -// Bootstrap Utilities -@import "bootstrap-5.3.8/scss/utilities/api"; - -// AdminLTE Configuration -// --------------------------------------------------- -@import "adminlte-4.0.0/scss/variables"; -@import "adminlte-4.0.0/scss/variables-dark"; -@import "adminlte-4.0.0/scss/mixins"; - -// AdminLTE Parts -// --------------------------------------------------- -@import "adminlte-4.0.0/scss/parts/core"; -@import "adminlte-4.0.0/scss/parts/components"; -@import "adminlte-4.0.0/scss/parts/extra-components"; -@import "adminlte-4.0.0/scss/parts/pages"; -@import "adminlte-4.0.0/scss/parts/miscellaneous"; - -// AdminLTE Accessibility Styles - WCAG 2.1 AA Compliance -@import "adminlte-4.0.0/scss/accessibility"; - -// AdminLTE Documentation Styles (only applies to pages with body.docs-page) -@import "adminlte-4.0.0/scss/docs"; - -// Bootstrap Icons 1.13.1 -// --------------------------------------------------- -@import "bootsier-icons"; - -// Bootsier customizations -// --------------------------------------------------- -@import "bootsier-custom"; diff --git a/extensions/pagetop-bootsier/assets/bootsier.shell.js b/extensions/pagetop-bootsier/assets/bootsier.shell.js deleted file mode 100644 index c2b6e2a5..00000000 --- a/extensions/pagetop-bootsier/assets/bootsier.shell.js +++ /dev/null @@ -1,65 +0,0 @@ -(function () { - 'use strict'; - - // Fullscreen: keeps maximize/minimize icons in sync with the actual fullscreen state. - document.addEventListener('fullscreenchange', function () { - var isFs = !!document.fullscreenElement; - document.querySelectorAll('[data-lte-icon="maximize"]').forEach(function (el) { - el.classList.toggle('d-none', isFs); - }); - document.querySelectorAll('[data-lte-icon="minimize"]').forEach(function (el) { - el.classList.toggle('d-none', !isFs); - }); - }); - - // Color mode selector (light / dark / auto). - var STORAGE_KEY = 'lte-theme'; - var getStored = function () { return localStorage.getItem(STORAGE_KEY); }; - var setStored = function (theme) { localStorage.setItem(STORAGE_KEY, theme); }; - var prefersDark = function () { - return window.matchMedia('(prefers-color-scheme: dark)').matches; - }; - - var setTheme = function (theme) { - var resolved = (theme === 'auto') ? (prefersDark() ? 'dark' : 'light') : theme; - document.documentElement.setAttribute('data-bs-theme', resolved); - }; - - var showActiveTheme = function (theme) { - document.querySelectorAll('[data-bs-theme-value]').forEach(function (el) { - el.classList.remove('active'); - el.setAttribute('aria-pressed', 'false'); - var check = el.querySelector('.bi-check-lg'); - if (check) { check.classList.add('d-none'); } - }); - var active = document.querySelector('[data-bs-theme-value="' + theme + '"]'); - if (active) { - active.classList.add('active'); - active.setAttribute('aria-pressed', 'true'); - var check = active.querySelector('.bi-check-lg'); - if (check) { check.classList.remove('d-none'); } - } - document.querySelectorAll('[data-lte-theme-icon]').forEach(function (icon) { - icon.classList.toggle('d-none', icon.dataset.lteThemeIcon !== theme); - }); - }; - - window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function () { - var stored = getStored(); - if (!stored || stored === 'auto') { setTheme('auto'); } - }); - - document.addEventListener('DOMContentLoaded', function () { - var theme = getStored() || 'auto'; - setTheme(theme); - showActiveTheme(theme); - document.querySelectorAll('[data-bs-theme-value]').forEach(function (toggle) { - toggle.addEventListener('click', function () { - var t = toggle.getAttribute('data-bs-theme-value'); - setStored(t); - setTheme(t); - showActiveTheme(t); - }); - }); - }); -}()); diff --git a/extensions/pagetop-bootsier/assets/bootstrap-icons-1.13.1/bootstrap-icons.css b/extensions/pagetop-bootsier/assets/bootstrap-icons-1.13.1/bootstrap-icons.css deleted file mode 100644 index 5f7ae28e..00000000 --- a/extensions/pagetop-bootsier/assets/bootstrap-icons-1.13.1/bootstrap-icons.css +++ /dev/null @@ -1,2106 +0,0 @@ -/*! - * Bootstrap Icons v1.13.1 (https://icons.getbootstrap.com/) - * Copyright 2019-2024 The Bootstrap Authors - * Licensed under MIT (https://github.com/twbs/icons/blob/main/LICENSE) - */ - -@font-face { - font-display: block; - font-family: "bootstrap-icons"; - src: url("./fonts/bootstrap-icons.woff2?e34853135f9e39acf64315236852cd5a") format("woff2"), -url("./fonts/bootstrap-icons.woff?e34853135f9e39acf64315236852cd5a") format("woff"); -} - -.bi::before, -[class^="bi-"]::before, -[class*=" bi-"]::before { - display: inline-block; - font-family: bootstrap-icons !important; - font-style: normal; - font-weight: normal !important; - font-variant: normal; - text-transform: none; - line-height: 1; - vertical-align: -.125em; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -.bi-123::before { content: "\f67f"; } -.bi-alarm-fill::before { content: "\f101"; } -.bi-alarm::before { content: "\f102"; } -.bi-align-bottom::before { content: "\f103"; } -.bi-align-center::before { content: "\f104"; } -.bi-align-end::before { content: "\f105"; } -.bi-align-middle::before { content: "\f106"; } -.bi-align-start::before { content: "\f107"; } -.bi-align-top::before { content: "\f108"; } -.bi-alt::before { content: "\f109"; } -.bi-app-indicator::before { content: "\f10a"; } -.bi-app::before { content: "\f10b"; } -.bi-archive-fill::before { content: "\f10c"; } -.bi-archive::before { content: "\f10d"; } -.bi-arrow-90deg-down::before { content: "\f10e"; } -.bi-arrow-90deg-left::before { content: "\f10f"; } -.bi-arrow-90deg-right::before { content: "\f110"; } -.bi-arrow-90deg-up::before { content: "\f111"; } -.bi-arrow-bar-down::before { content: "\f112"; } -.bi-arrow-bar-left::before { content: "\f113"; } -.bi-arrow-bar-right::before { content: "\f114"; } -.bi-arrow-bar-up::before { content: "\f115"; } -.bi-arrow-clockwise::before { content: "\f116"; } -.bi-arrow-counterclockwise::before { content: "\f117"; } -.bi-arrow-down-circle-fill::before { content: "\f118"; } -.bi-arrow-down-circle::before { content: "\f119"; } -.bi-arrow-down-left-circle-fill::before { content: "\f11a"; } -.bi-arrow-down-left-circle::before { content: "\f11b"; } -.bi-arrow-down-left-square-fill::before { content: "\f11c"; } -.bi-arrow-down-left-square::before { content: "\f11d"; } -.bi-arrow-down-left::before { content: "\f11e"; } -.bi-arrow-down-right-circle-fill::before { content: "\f11f"; } -.bi-arrow-down-right-circle::before { content: "\f120"; } -.bi-arrow-down-right-square-fill::before { content: "\f121"; } -.bi-arrow-down-right-square::before { content: "\f122"; } -.bi-arrow-down-right::before { content: "\f123"; } -.bi-arrow-down-short::before { content: "\f124"; } -.bi-arrow-down-square-fill::before { content: "\f125"; } -.bi-arrow-down-square::before { content: "\f126"; } -.bi-arrow-down-up::before { content: "\f127"; } -.bi-arrow-down::before { content: "\f128"; } -.bi-arrow-left-circle-fill::before { content: "\f129"; } -.bi-arrow-left-circle::before { content: "\f12a"; } -.bi-arrow-left-right::before { content: "\f12b"; } -.bi-arrow-left-short::before { content: "\f12c"; } -.bi-arrow-left-square-fill::before { content: "\f12d"; } -.bi-arrow-left-square::before { content: "\f12e"; } -.bi-arrow-left::before { content: "\f12f"; } -.bi-arrow-repeat::before { content: "\f130"; } -.bi-arrow-return-left::before { content: "\f131"; } -.bi-arrow-return-right::before { content: "\f132"; } -.bi-arrow-right-circle-fill::before { content: "\f133"; } -.bi-arrow-right-circle::before { content: "\f134"; } -.bi-arrow-right-short::before { content: "\f135"; } -.bi-arrow-right-square-fill::before { content: "\f136"; } -.bi-arrow-right-square::before { content: "\f137"; } -.bi-arrow-right::before { content: "\f138"; } -.bi-arrow-up-circle-fill::before { content: "\f139"; } -.bi-arrow-up-circle::before { content: "\f13a"; } -.bi-arrow-up-left-circle-fill::before { content: "\f13b"; } -.bi-arrow-up-left-circle::before { content: "\f13c"; } -.bi-arrow-up-left-square-fill::before { content: "\f13d"; } -.bi-arrow-up-left-square::before { content: "\f13e"; } -.bi-arrow-up-left::before { content: "\f13f"; } -.bi-arrow-up-right-circle-fill::before { content: "\f140"; } -.bi-arrow-up-right-circle::before { content: "\f141"; } -.bi-arrow-up-right-square-fill::before { content: "\f142"; } -.bi-arrow-up-right-square::before { content: "\f143"; } -.bi-arrow-up-right::before { content: "\f144"; } -.bi-arrow-up-short::before { content: "\f145"; } -.bi-arrow-up-square-fill::before { content: "\f146"; } -.bi-arrow-up-square::before { content: "\f147"; } -.bi-arrow-up::before { content: "\f148"; } -.bi-arrows-angle-contract::before { content: "\f149"; } -.bi-arrows-angle-expand::before { content: "\f14a"; } -.bi-arrows-collapse::before { content: "\f14b"; } -.bi-arrows-expand::before { content: "\f14c"; } -.bi-arrows-fullscreen::before { content: "\f14d"; } -.bi-arrows-move::before { content: "\f14e"; } -.bi-aspect-ratio-fill::before { content: "\f14f"; } -.bi-aspect-ratio::before { content: "\f150"; } -.bi-asterisk::before { content: "\f151"; } -.bi-at::before { content: "\f152"; } -.bi-award-fill::before { content: "\f153"; } -.bi-award::before { content: "\f154"; } -.bi-back::before { content: "\f155"; } -.bi-backspace-fill::before { content: "\f156"; } -.bi-backspace-reverse-fill::before { content: "\f157"; } -.bi-backspace-reverse::before { content: "\f158"; } -.bi-backspace::before { content: "\f159"; } -.bi-badge-3d-fill::before { content: "\f15a"; } -.bi-badge-3d::before { content: "\f15b"; } -.bi-badge-4k-fill::before { content: "\f15c"; } -.bi-badge-4k::before { content: "\f15d"; } -.bi-badge-8k-fill::before { content: "\f15e"; } -.bi-badge-8k::before { content: "\f15f"; } -.bi-badge-ad-fill::before { content: "\f160"; } -.bi-badge-ad::before { content: "\f161"; } -.bi-badge-ar-fill::before { content: "\f162"; } -.bi-badge-ar::before { content: "\f163"; } -.bi-badge-cc-fill::before { content: "\f164"; } -.bi-badge-cc::before { content: "\f165"; } -.bi-badge-hd-fill::before { content: "\f166"; } -.bi-badge-hd::before { content: "\f167"; } -.bi-badge-tm-fill::before { content: "\f168"; } -.bi-badge-tm::before { content: "\f169"; } -.bi-badge-vo-fill::before { content: "\f16a"; } -.bi-badge-vo::before { content: "\f16b"; } -.bi-badge-vr-fill::before { content: "\f16c"; } -.bi-badge-vr::before { content: "\f16d"; } -.bi-badge-wc-fill::before { content: "\f16e"; } -.bi-badge-wc::before { content: "\f16f"; } -.bi-bag-check-fill::before { content: "\f170"; } -.bi-bag-check::before { content: "\f171"; } -.bi-bag-dash-fill::before { content: "\f172"; } -.bi-bag-dash::before { content: "\f173"; } -.bi-bag-fill::before { content: "\f174"; } -.bi-bag-plus-fill::before { content: "\f175"; } -.bi-bag-plus::before { content: "\f176"; } -.bi-bag-x-fill::before { content: "\f177"; } -.bi-bag-x::before { content: "\f178"; } -.bi-bag::before { content: "\f179"; } -.bi-bar-chart-fill::before { content: "\f17a"; } -.bi-bar-chart-line-fill::before { content: "\f17b"; } -.bi-bar-chart-line::before { content: "\f17c"; } -.bi-bar-chart-steps::before { content: "\f17d"; } -.bi-bar-chart::before { content: "\f17e"; } -.bi-basket-fill::before { content: "\f17f"; } -.bi-basket::before { content: "\f180"; } -.bi-basket2-fill::before { content: "\f181"; } -.bi-basket2::before { content: "\f182"; } -.bi-basket3-fill::before { content: "\f183"; } -.bi-basket3::before { content: "\f184"; } -.bi-battery-charging::before { content: "\f185"; } -.bi-battery-full::before { content: "\f186"; } -.bi-battery-half::before { content: "\f187"; } -.bi-battery::before { content: "\f188"; } -.bi-bell-fill::before { content: "\f189"; } -.bi-bell::before { content: "\f18a"; } -.bi-bezier::before { content: "\f18b"; } -.bi-bezier2::before { content: "\f18c"; } -.bi-bicycle::before { content: "\f18d"; } -.bi-binoculars-fill::before { content: "\f18e"; } -.bi-binoculars::before { content: "\f18f"; } -.bi-blockquote-left::before { content: "\f190"; } -.bi-blockquote-right::before { content: "\f191"; } -.bi-book-fill::before { content: "\f192"; } -.bi-book-half::before { content: "\f193"; } -.bi-book::before { content: "\f194"; } -.bi-bookmark-check-fill::before { content: "\f195"; } -.bi-bookmark-check::before { content: "\f196"; } -.bi-bookmark-dash-fill::before { content: "\f197"; } -.bi-bookmark-dash::before { content: "\f198"; } -.bi-bookmark-fill::before { content: "\f199"; } -.bi-bookmark-heart-fill::before { content: "\f19a"; } -.bi-bookmark-heart::before { content: "\f19b"; } -.bi-bookmark-plus-fill::before { content: "\f19c"; } -.bi-bookmark-plus::before { content: "\f19d"; } -.bi-bookmark-star-fill::before { content: "\f19e"; } -.bi-bookmark-star::before { content: "\f19f"; } -.bi-bookmark-x-fill::before { content: "\f1a0"; } -.bi-bookmark-x::before { content: "\f1a1"; } -.bi-bookmark::before { content: "\f1a2"; } -.bi-bookmarks-fill::before { content: "\f1a3"; } -.bi-bookmarks::before { content: "\f1a4"; } -.bi-bookshelf::before { content: "\f1a5"; } -.bi-bootstrap-fill::before { content: "\f1a6"; } -.bi-bootstrap-reboot::before { content: "\f1a7"; } -.bi-bootstrap::before { content: "\f1a8"; } -.bi-border-all::before { content: "\f1a9"; } -.bi-border-bottom::before { content: "\f1aa"; } -.bi-border-center::before { content: "\f1ab"; } -.bi-border-inner::before { content: "\f1ac"; } -.bi-border-left::before { content: "\f1ad"; } -.bi-border-middle::before { content: "\f1ae"; } -.bi-border-outer::before { content: "\f1af"; } -.bi-border-right::before { content: "\f1b0"; } -.bi-border-style::before { content: "\f1b1"; } -.bi-border-top::before { content: "\f1b2"; } -.bi-border-width::before { content: "\f1b3"; } -.bi-border::before { content: "\f1b4"; } -.bi-bounding-box-circles::before { content: "\f1b5"; } -.bi-bounding-box::before { content: "\f1b6"; } -.bi-box-arrow-down-left::before { content: "\f1b7"; } -.bi-box-arrow-down-right::before { content: "\f1b8"; } -.bi-box-arrow-down::before { content: "\f1b9"; } -.bi-box-arrow-in-down-left::before { content: "\f1ba"; } -.bi-box-arrow-in-down-right::before { content: "\f1bb"; } -.bi-box-arrow-in-down::before { content: "\f1bc"; } -.bi-box-arrow-in-left::before { content: "\f1bd"; } -.bi-box-arrow-in-right::before { content: "\f1be"; } -.bi-box-arrow-in-up-left::before { content: "\f1bf"; } -.bi-box-arrow-in-up-right::before { content: "\f1c0"; } -.bi-box-arrow-in-up::before { content: "\f1c1"; } -.bi-box-arrow-left::before { content: "\f1c2"; } -.bi-box-arrow-right::before { content: "\f1c3"; } -.bi-box-arrow-up-left::before { content: "\f1c4"; } -.bi-box-arrow-up-right::before { content: "\f1c5"; } -.bi-box-arrow-up::before { content: "\f1c6"; } -.bi-box-seam::before { content: "\f1c7"; } -.bi-box::before { content: "\f1c8"; } -.bi-braces::before { content: "\f1c9"; } -.bi-bricks::before { content: "\f1ca"; } -.bi-briefcase-fill::before { content: "\f1cb"; } -.bi-briefcase::before { content: "\f1cc"; } -.bi-brightness-alt-high-fill::before { content: "\f1cd"; } -.bi-brightness-alt-high::before { content: "\f1ce"; } -.bi-brightness-alt-low-fill::before { content: "\f1cf"; } -.bi-brightness-alt-low::before { content: "\f1d0"; } -.bi-brightness-high-fill::before { content: "\f1d1"; } -.bi-brightness-high::before { content: "\f1d2"; } -.bi-brightness-low-fill::before { content: "\f1d3"; } -.bi-brightness-low::before { content: "\f1d4"; } -.bi-broadcast-pin::before { content: "\f1d5"; } -.bi-broadcast::before { content: "\f1d6"; } -.bi-brush-fill::before { content: "\f1d7"; } -.bi-brush::before { content: "\f1d8"; } -.bi-bucket-fill::before { content: "\f1d9"; } -.bi-bucket::before { content: "\f1da"; } -.bi-bug-fill::before { content: "\f1db"; } -.bi-bug::before { content: "\f1dc"; } -.bi-building::before { content: "\f1dd"; } -.bi-bullseye::before { content: "\f1de"; } -.bi-calculator-fill::before { content: "\f1df"; } -.bi-calculator::before { content: "\f1e0"; } -.bi-calendar-check-fill::before { content: "\f1e1"; } -.bi-calendar-check::before { content: "\f1e2"; } -.bi-calendar-date-fill::before { content: "\f1e3"; } -.bi-calendar-date::before { content: "\f1e4"; } -.bi-calendar-day-fill::before { content: "\f1e5"; } -.bi-calendar-day::before { content: "\f1e6"; } -.bi-calendar-event-fill::before { content: "\f1e7"; } -.bi-calendar-event::before { content: "\f1e8"; } -.bi-calendar-fill::before { content: "\f1e9"; } -.bi-calendar-minus-fill::before { content: "\f1ea"; } -.bi-calendar-minus::before { content: "\f1eb"; } -.bi-calendar-month-fill::before { content: "\f1ec"; } -.bi-calendar-month::before { content: "\f1ed"; } -.bi-calendar-plus-fill::before { content: "\f1ee"; } -.bi-calendar-plus::before { content: "\f1ef"; } -.bi-calendar-range-fill::before { content: "\f1f0"; } -.bi-calendar-range::before { content: "\f1f1"; } -.bi-calendar-week-fill::before { content: "\f1f2"; } -.bi-calendar-week::before { content: "\f1f3"; } -.bi-calendar-x-fill::before { content: "\f1f4"; } -.bi-calendar-x::before { content: "\f1f5"; } -.bi-calendar::before { content: "\f1f6"; } -.bi-calendar2-check-fill::before { content: "\f1f7"; } -.bi-calendar2-check::before { content: "\f1f8"; } -.bi-calendar2-date-fill::before { content: "\f1f9"; } -.bi-calendar2-date::before { content: "\f1fa"; } -.bi-calendar2-day-fill::before { content: "\f1fb"; } -.bi-calendar2-day::before { content: "\f1fc"; } -.bi-calendar2-event-fill::before { content: "\f1fd"; } -.bi-calendar2-event::before { content: "\f1fe"; } -.bi-calendar2-fill::before { content: "\f1ff"; } -.bi-calendar2-minus-fill::before { content: "\f200"; } -.bi-calendar2-minus::before { content: "\f201"; } -.bi-calendar2-month-fill::before { content: "\f202"; } -.bi-calendar2-month::before { content: "\f203"; } -.bi-calendar2-plus-fill::before { content: "\f204"; } -.bi-calendar2-plus::before { content: "\f205"; } -.bi-calendar2-range-fill::before { content: "\f206"; } -.bi-calendar2-range::before { content: "\f207"; } -.bi-calendar2-week-fill::before { content: "\f208"; } -.bi-calendar2-week::before { content: "\f209"; } -.bi-calendar2-x-fill::before { content: "\f20a"; } -.bi-calendar2-x::before { content: "\f20b"; } -.bi-calendar2::before { content: "\f20c"; } -.bi-calendar3-event-fill::before { content: "\f20d"; } -.bi-calendar3-event::before { content: "\f20e"; } -.bi-calendar3-fill::before { content: "\f20f"; } -.bi-calendar3-range-fill::before { content: "\f210"; } -.bi-calendar3-range::before { content: "\f211"; } -.bi-calendar3-week-fill::before { content: "\f212"; } -.bi-calendar3-week::before { content: "\f213"; } -.bi-calendar3::before { content: "\f214"; } -.bi-calendar4-event::before { content: "\f215"; } -.bi-calendar4-range::before { content: "\f216"; } -.bi-calendar4-week::before { content: "\f217"; } -.bi-calendar4::before { content: "\f218"; } -.bi-camera-fill::before { content: "\f219"; } -.bi-camera-reels-fill::before { content: "\f21a"; } -.bi-camera-reels::before { content: "\f21b"; } -.bi-camera-video-fill::before { content: "\f21c"; } -.bi-camera-video-off-fill::before { content: "\f21d"; } -.bi-camera-video-off::before { content: "\f21e"; } -.bi-camera-video::before { content: "\f21f"; } -.bi-camera::before { content: "\f220"; } -.bi-camera2::before { content: "\f221"; } -.bi-capslock-fill::before { content: "\f222"; } -.bi-capslock::before { content: "\f223"; } -.bi-card-checklist::before { content: "\f224"; } -.bi-card-heading::before { content: "\f225"; } -.bi-card-image::before { content: "\f226"; } -.bi-card-list::before { content: "\f227"; } -.bi-card-text::before { content: "\f228"; } -.bi-caret-down-fill::before { content: "\f229"; } -.bi-caret-down-square-fill::before { content: "\f22a"; } -.bi-caret-down-square::before { content: "\f22b"; } -.bi-caret-down::before { content: "\f22c"; } -.bi-caret-left-fill::before { content: "\f22d"; } -.bi-caret-left-square-fill::before { content: "\f22e"; } -.bi-caret-left-square::before { content: "\f22f"; } -.bi-caret-left::before { content: "\f230"; } -.bi-caret-right-fill::before { content: "\f231"; } -.bi-caret-right-square-fill::before { content: "\f232"; } -.bi-caret-right-square::before { content: "\f233"; } -.bi-caret-right::before { content: "\f234"; } -.bi-caret-up-fill::before { content: "\f235"; } -.bi-caret-up-square-fill::before { content: "\f236"; } -.bi-caret-up-square::before { content: "\f237"; } -.bi-caret-up::before { content: "\f238"; } -.bi-cart-check-fill::before { content: "\f239"; } -.bi-cart-check::before { content: "\f23a"; } -.bi-cart-dash-fill::before { content: "\f23b"; } -.bi-cart-dash::before { content: "\f23c"; } -.bi-cart-fill::before { content: "\f23d"; } -.bi-cart-plus-fill::before { content: "\f23e"; } -.bi-cart-plus::before { content: "\f23f"; } -.bi-cart-x-fill::before { content: "\f240"; } -.bi-cart-x::before { content: "\f241"; } -.bi-cart::before { content: "\f242"; } -.bi-cart2::before { content: "\f243"; } -.bi-cart3::before { content: "\f244"; } -.bi-cart4::before { content: "\f245"; } -.bi-cash-stack::before { content: "\f246"; } -.bi-cash::before { content: "\f247"; } -.bi-cast::before { content: "\f248"; } -.bi-chat-dots-fill::before { content: "\f249"; } -.bi-chat-dots::before { content: "\f24a"; } -.bi-chat-fill::before { content: "\f24b"; } -.bi-chat-left-dots-fill::before { content: "\f24c"; } -.bi-chat-left-dots::before { content: "\f24d"; } -.bi-chat-left-fill::before { content: "\f24e"; } -.bi-chat-left-quote-fill::before { content: "\f24f"; } -.bi-chat-left-quote::before { content: "\f250"; } -.bi-chat-left-text-fill::before { content: "\f251"; } -.bi-chat-left-text::before { content: "\f252"; } -.bi-chat-left::before { content: "\f253"; } -.bi-chat-quote-fill::before { content: "\f254"; } -.bi-chat-quote::before { content: "\f255"; } -.bi-chat-right-dots-fill::before { content: "\f256"; } -.bi-chat-right-dots::before { content: "\f257"; } -.bi-chat-right-fill::before { content: "\f258"; } -.bi-chat-right-quote-fill::before { content: "\f259"; } -.bi-chat-right-quote::before { content: "\f25a"; } -.bi-chat-right-text-fill::before { content: "\f25b"; } -.bi-chat-right-text::before { content: "\f25c"; } -.bi-chat-right::before { content: "\f25d"; } -.bi-chat-square-dots-fill::before { content: "\f25e"; } -.bi-chat-square-dots::before { content: "\f25f"; } -.bi-chat-square-fill::before { content: "\f260"; } -.bi-chat-square-quote-fill::before { content: "\f261"; } -.bi-chat-square-quote::before { content: "\f262"; } -.bi-chat-square-text-fill::before { content: "\f263"; } -.bi-chat-square-text::before { content: "\f264"; } -.bi-chat-square::before { content: "\f265"; } -.bi-chat-text-fill::before { content: "\f266"; } -.bi-chat-text::before { content: "\f267"; } -.bi-chat::before { content: "\f268"; } -.bi-check-all::before { content: "\f269"; } -.bi-check-circle-fill::before { content: "\f26a"; } -.bi-check-circle::before { content: "\f26b"; } -.bi-check-square-fill::before { content: "\f26c"; } -.bi-check-square::before { content: "\f26d"; } -.bi-check::before { content: "\f26e"; } -.bi-check2-all::before { content: "\f26f"; } -.bi-check2-circle::before { content: "\f270"; } -.bi-check2-square::before { content: "\f271"; } -.bi-check2::before { content: "\f272"; } -.bi-chevron-bar-contract::before { content: "\f273"; } -.bi-chevron-bar-down::before { content: "\f274"; } -.bi-chevron-bar-expand::before { content: "\f275"; } -.bi-chevron-bar-left::before { content: "\f276"; } -.bi-chevron-bar-right::before { content: "\f277"; } -.bi-chevron-bar-up::before { content: "\f278"; } -.bi-chevron-compact-down::before { content: "\f279"; } -.bi-chevron-compact-left::before { content: "\f27a"; } -.bi-chevron-compact-right::before { content: "\f27b"; } -.bi-chevron-compact-up::before { content: "\f27c"; } -.bi-chevron-contract::before { content: "\f27d"; } -.bi-chevron-double-down::before { content: "\f27e"; } -.bi-chevron-double-left::before { content: "\f27f"; } -.bi-chevron-double-right::before { content: "\f280"; } -.bi-chevron-double-up::before { content: "\f281"; } -.bi-chevron-down::before { content: "\f282"; } -.bi-chevron-expand::before { content: "\f283"; } -.bi-chevron-left::before { content: "\f284"; } -.bi-chevron-right::before { content: "\f285"; } -.bi-chevron-up::before { content: "\f286"; } -.bi-circle-fill::before { content: "\f287"; } -.bi-circle-half::before { content: "\f288"; } -.bi-circle-square::before { content: "\f289"; } -.bi-circle::before { content: "\f28a"; } -.bi-clipboard-check::before { content: "\f28b"; } -.bi-clipboard-data::before { content: "\f28c"; } -.bi-clipboard-minus::before { content: "\f28d"; } -.bi-clipboard-plus::before { content: "\f28e"; } -.bi-clipboard-x::before { content: "\f28f"; } -.bi-clipboard::before { content: "\f290"; } -.bi-clock-fill::before { content: "\f291"; } -.bi-clock-history::before { content: "\f292"; } -.bi-clock::before { content: "\f293"; } -.bi-cloud-arrow-down-fill::before { content: "\f294"; } -.bi-cloud-arrow-down::before { content: "\f295"; } -.bi-cloud-arrow-up-fill::before { content: "\f296"; } -.bi-cloud-arrow-up::before { content: "\f297"; } -.bi-cloud-check-fill::before { content: "\f298"; } -.bi-cloud-check::before { content: "\f299"; } -.bi-cloud-download-fill::before { content: "\f29a"; } -.bi-cloud-download::before { content: "\f29b"; } -.bi-cloud-drizzle-fill::before { content: "\f29c"; } -.bi-cloud-drizzle::before { content: "\f29d"; } -.bi-cloud-fill::before { content: "\f29e"; } -.bi-cloud-fog-fill::before { content: "\f29f"; } -.bi-cloud-fog::before { content: "\f2a0"; } -.bi-cloud-fog2-fill::before { content: "\f2a1"; } -.bi-cloud-fog2::before { content: "\f2a2"; } -.bi-cloud-hail-fill::before { content: "\f2a3"; } -.bi-cloud-hail::before { content: "\f2a4"; } -.bi-cloud-haze-fill::before { content: "\f2a6"; } -.bi-cloud-haze::before { content: "\f2a7"; } -.bi-cloud-haze2-fill::before { content: "\f2a8"; } -.bi-cloud-lightning-fill::before { content: "\f2a9"; } -.bi-cloud-lightning-rain-fill::before { content: "\f2aa"; } -.bi-cloud-lightning-rain::before { content: "\f2ab"; } -.bi-cloud-lightning::before { content: "\f2ac"; } -.bi-cloud-minus-fill::before { content: "\f2ad"; } -.bi-cloud-minus::before { content: "\f2ae"; } -.bi-cloud-moon-fill::before { content: "\f2af"; } -.bi-cloud-moon::before { content: "\f2b0"; } -.bi-cloud-plus-fill::before { content: "\f2b1"; } -.bi-cloud-plus::before { content: "\f2b2"; } -.bi-cloud-rain-fill::before { content: "\f2b3"; } -.bi-cloud-rain-heavy-fill::before { content: "\f2b4"; } -.bi-cloud-rain-heavy::before { content: "\f2b5"; } -.bi-cloud-rain::before { content: "\f2b6"; } -.bi-cloud-slash-fill::before { content: "\f2b7"; } -.bi-cloud-slash::before { content: "\f2b8"; } -.bi-cloud-sleet-fill::before { content: "\f2b9"; } -.bi-cloud-sleet::before { content: "\f2ba"; } -.bi-cloud-snow-fill::before { content: "\f2bb"; } -.bi-cloud-snow::before { content: "\f2bc"; } -.bi-cloud-sun-fill::before { content: "\f2bd"; } -.bi-cloud-sun::before { content: "\f2be"; } -.bi-cloud-upload-fill::before { content: "\f2bf"; } -.bi-cloud-upload::before { content: "\f2c0"; } -.bi-cloud::before { content: "\f2c1"; } -.bi-clouds-fill::before { content: "\f2c2"; } -.bi-clouds::before { content: "\f2c3"; } -.bi-cloudy-fill::before { content: "\f2c4"; } -.bi-cloudy::before { content: "\f2c5"; } -.bi-code-slash::before { content: "\f2c6"; } -.bi-code-square::before { content: "\f2c7"; } -.bi-code::before { content: "\f2c8"; } -.bi-collection-fill::before { content: "\f2c9"; } -.bi-collection-play-fill::before { content: "\f2ca"; } -.bi-collection-play::before { content: "\f2cb"; } -.bi-collection::before { content: "\f2cc"; } -.bi-columns-gap::before { content: "\f2cd"; } -.bi-columns::before { content: "\f2ce"; } -.bi-command::before { content: "\f2cf"; } -.bi-compass-fill::before { content: "\f2d0"; } -.bi-compass::before { content: "\f2d1"; } -.bi-cone-striped::before { content: "\f2d2"; } -.bi-cone::before { content: "\f2d3"; } -.bi-controller::before { content: "\f2d4"; } -.bi-cpu-fill::before { content: "\f2d5"; } -.bi-cpu::before { content: "\f2d6"; } -.bi-credit-card-2-back-fill::before { content: "\f2d7"; } -.bi-credit-card-2-back::before { content: "\f2d8"; } -.bi-credit-card-2-front-fill::before { content: "\f2d9"; } -.bi-credit-card-2-front::before { content: "\f2da"; } -.bi-credit-card-fill::before { content: "\f2db"; } -.bi-credit-card::before { content: "\f2dc"; } -.bi-crop::before { content: "\f2dd"; } -.bi-cup-fill::before { content: "\f2de"; } -.bi-cup-straw::before { content: "\f2df"; } -.bi-cup::before { content: "\f2e0"; } -.bi-cursor-fill::before { content: "\f2e1"; } -.bi-cursor-text::before { content: "\f2e2"; } -.bi-cursor::before { content: "\f2e3"; } -.bi-dash-circle-dotted::before { content: "\f2e4"; } -.bi-dash-circle-fill::before { content: "\f2e5"; } -.bi-dash-circle::before { content: "\f2e6"; } -.bi-dash-square-dotted::before { content: "\f2e7"; } -.bi-dash-square-fill::before { content: "\f2e8"; } -.bi-dash-square::before { content: "\f2e9"; } -.bi-dash::before { content: "\f2ea"; } -.bi-diagram-2-fill::before { content: "\f2eb"; } -.bi-diagram-2::before { content: "\f2ec"; } -.bi-diagram-3-fill::before { content: "\f2ed"; } -.bi-diagram-3::before { content: "\f2ee"; } -.bi-diamond-fill::before { content: "\f2ef"; } -.bi-diamond-half::before { content: "\f2f0"; } -.bi-diamond::before { content: "\f2f1"; } -.bi-dice-1-fill::before { content: "\f2f2"; } -.bi-dice-1::before { content: "\f2f3"; } -.bi-dice-2-fill::before { content: "\f2f4"; } -.bi-dice-2::before { content: "\f2f5"; } -.bi-dice-3-fill::before { content: "\f2f6"; } -.bi-dice-3::before { content: "\f2f7"; } -.bi-dice-4-fill::before { content: "\f2f8"; } -.bi-dice-4::before { content: "\f2f9"; } -.bi-dice-5-fill::before { content: "\f2fa"; } -.bi-dice-5::before { content: "\f2fb"; } -.bi-dice-6-fill::before { content: "\f2fc"; } -.bi-dice-6::before { content: "\f2fd"; } -.bi-disc-fill::before { content: "\f2fe"; } -.bi-disc::before { content: "\f2ff"; } -.bi-discord::before { content: "\f300"; } -.bi-display-fill::before { content: "\f301"; } -.bi-display::before { content: "\f302"; } -.bi-distribute-horizontal::before { content: "\f303"; } -.bi-distribute-vertical::before { content: "\f304"; } -.bi-door-closed-fill::before { content: "\f305"; } -.bi-door-closed::before { content: "\f306"; } -.bi-door-open-fill::before { content: "\f307"; } -.bi-door-open::before { content: "\f308"; } -.bi-dot::before { content: "\f309"; } -.bi-download::before { content: "\f30a"; } -.bi-droplet-fill::before { content: "\f30b"; } -.bi-droplet-half::before { content: "\f30c"; } -.bi-droplet::before { content: "\f30d"; } -.bi-earbuds::before { content: "\f30e"; } -.bi-easel-fill::before { content: "\f30f"; } -.bi-easel::before { content: "\f310"; } -.bi-egg-fill::before { content: "\f311"; } -.bi-egg-fried::before { content: "\f312"; } -.bi-egg::before { content: "\f313"; } -.bi-eject-fill::before { content: "\f314"; } -.bi-eject::before { content: "\f315"; } -.bi-emoji-angry-fill::before { content: "\f316"; } -.bi-emoji-angry::before { content: "\f317"; } -.bi-emoji-dizzy-fill::before { content: "\f318"; } -.bi-emoji-dizzy::before { content: "\f319"; } -.bi-emoji-expressionless-fill::before { content: "\f31a"; } -.bi-emoji-expressionless::before { content: "\f31b"; } -.bi-emoji-frown-fill::before { content: "\f31c"; } -.bi-emoji-frown::before { content: "\f31d"; } -.bi-emoji-heart-eyes-fill::before { content: "\f31e"; } -.bi-emoji-heart-eyes::before { content: "\f31f"; } -.bi-emoji-laughing-fill::before { content: "\f320"; } -.bi-emoji-laughing::before { content: "\f321"; } -.bi-emoji-neutral-fill::before { content: "\f322"; } -.bi-emoji-neutral::before { content: "\f323"; } -.bi-emoji-smile-fill::before { content: "\f324"; } -.bi-emoji-smile-upside-down-fill::before { content: "\f325"; } -.bi-emoji-smile-upside-down::before { content: "\f326"; } -.bi-emoji-smile::before { content: "\f327"; } -.bi-emoji-sunglasses-fill::before { content: "\f328"; } -.bi-emoji-sunglasses::before { content: "\f329"; } -.bi-emoji-wink-fill::before { content: "\f32a"; } -.bi-emoji-wink::before { content: "\f32b"; } -.bi-envelope-fill::before { content: "\f32c"; } -.bi-envelope-open-fill::before { content: "\f32d"; } -.bi-envelope-open::before { content: "\f32e"; } -.bi-envelope::before { content: "\f32f"; } -.bi-eraser-fill::before { content: "\f330"; } -.bi-eraser::before { content: "\f331"; } -.bi-exclamation-circle-fill::before { content: "\f332"; } -.bi-exclamation-circle::before { content: "\f333"; } -.bi-exclamation-diamond-fill::before { content: "\f334"; } -.bi-exclamation-diamond::before { content: "\f335"; } -.bi-exclamation-octagon-fill::before { content: "\f336"; } -.bi-exclamation-octagon::before { content: "\f337"; } -.bi-exclamation-square-fill::before { content: "\f338"; } -.bi-exclamation-square::before { content: "\f339"; } -.bi-exclamation-triangle-fill::before { content: "\f33a"; } -.bi-exclamation-triangle::before { content: "\f33b"; } -.bi-exclamation::before { content: "\f33c"; } -.bi-exclude::before { content: "\f33d"; } -.bi-eye-fill::before { content: "\f33e"; } -.bi-eye-slash-fill::before { content: "\f33f"; } -.bi-eye-slash::before { content: "\f340"; } -.bi-eye::before { content: "\f341"; } -.bi-eyedropper::before { content: "\f342"; } -.bi-eyeglasses::before { content: "\f343"; } -.bi-facebook::before { content: "\f344"; } -.bi-file-arrow-down-fill::before { content: "\f345"; } -.bi-file-arrow-down::before { content: "\f346"; } -.bi-file-arrow-up-fill::before { content: "\f347"; } -.bi-file-arrow-up::before { content: "\f348"; } -.bi-file-bar-graph-fill::before { content: "\f349"; } -.bi-file-bar-graph::before { content: "\f34a"; } -.bi-file-binary-fill::before { content: "\f34b"; } -.bi-file-binary::before { content: "\f34c"; } -.bi-file-break-fill::before { content: "\f34d"; } -.bi-file-break::before { content: "\f34e"; } -.bi-file-check-fill::before { content: "\f34f"; } -.bi-file-check::before { content: "\f350"; } -.bi-file-code-fill::before { content: "\f351"; } -.bi-file-code::before { content: "\f352"; } -.bi-file-diff-fill::before { content: "\f353"; } -.bi-file-diff::before { content: "\f354"; } -.bi-file-earmark-arrow-down-fill::before { content: "\f355"; } -.bi-file-earmark-arrow-down::before { content: "\f356"; } -.bi-file-earmark-arrow-up-fill::before { content: "\f357"; } -.bi-file-earmark-arrow-up::before { content: "\f358"; } -.bi-file-earmark-bar-graph-fill::before { content: "\f359"; } -.bi-file-earmark-bar-graph::before { content: "\f35a"; } -.bi-file-earmark-binary-fill::before { content: "\f35b"; } -.bi-file-earmark-binary::before { content: "\f35c"; } -.bi-file-earmark-break-fill::before { content: "\f35d"; } -.bi-file-earmark-break::before { content: "\f35e"; } -.bi-file-earmark-check-fill::before { content: "\f35f"; } -.bi-file-earmark-check::before { content: "\f360"; } -.bi-file-earmark-code-fill::before { content: "\f361"; } -.bi-file-earmark-code::before { content: "\f362"; } -.bi-file-earmark-diff-fill::before { content: "\f363"; } -.bi-file-earmark-diff::before { content: "\f364"; } -.bi-file-earmark-easel-fill::before { content: "\f365"; } -.bi-file-earmark-easel::before { content: "\f366"; } -.bi-file-earmark-excel-fill::before { content: "\f367"; } -.bi-file-earmark-excel::before { content: "\f368"; } -.bi-file-earmark-fill::before { content: "\f369"; } -.bi-file-earmark-font-fill::before { content: "\f36a"; } -.bi-file-earmark-font::before { content: "\f36b"; } -.bi-file-earmark-image-fill::before { content: "\f36c"; } -.bi-file-earmark-image::before { content: "\f36d"; } -.bi-file-earmark-lock-fill::before { content: "\f36e"; } -.bi-file-earmark-lock::before { content: "\f36f"; } -.bi-file-earmark-lock2-fill::before { content: "\f370"; } -.bi-file-earmark-lock2::before { content: "\f371"; } -.bi-file-earmark-medical-fill::before { content: "\f372"; } -.bi-file-earmark-medical::before { content: "\f373"; } -.bi-file-earmark-minus-fill::before { content: "\f374"; } -.bi-file-earmark-minus::before { content: "\f375"; } -.bi-file-earmark-music-fill::before { content: "\f376"; } -.bi-file-earmark-music::before { content: "\f377"; } -.bi-file-earmark-person-fill::before { content: "\f378"; } -.bi-file-earmark-person::before { content: "\f379"; } -.bi-file-earmark-play-fill::before { content: "\f37a"; } -.bi-file-earmark-play::before { content: "\f37b"; } -.bi-file-earmark-plus-fill::before { content: "\f37c"; } -.bi-file-earmark-plus::before { content: "\f37d"; } -.bi-file-earmark-post-fill::before { content: "\f37e"; } -.bi-file-earmark-post::before { content: "\f37f"; } -.bi-file-earmark-ppt-fill::before { content: "\f380"; } -.bi-file-earmark-ppt::before { content: "\f381"; } -.bi-file-earmark-richtext-fill::before { content: "\f382"; } -.bi-file-earmark-richtext::before { content: "\f383"; } -.bi-file-earmark-ruled-fill::before { content: "\f384"; } -.bi-file-earmark-ruled::before { content: "\f385"; } -.bi-file-earmark-slides-fill::before { content: "\f386"; } -.bi-file-earmark-slides::before { content: "\f387"; } -.bi-file-earmark-spreadsheet-fill::before { content: "\f388"; } -.bi-file-earmark-spreadsheet::before { content: "\f389"; } -.bi-file-earmark-text-fill::before { content: "\f38a"; } -.bi-file-earmark-text::before { content: "\f38b"; } -.bi-file-earmark-word-fill::before { content: "\f38c"; } -.bi-file-earmark-word::before { content: "\f38d"; } -.bi-file-earmark-x-fill::before { content: "\f38e"; } -.bi-file-earmark-x::before { content: "\f38f"; } -.bi-file-earmark-zip-fill::before { content: "\f390"; } -.bi-file-earmark-zip::before { content: "\f391"; } -.bi-file-earmark::before { content: "\f392"; } -.bi-file-easel-fill::before { content: "\f393"; } -.bi-file-easel::before { content: "\f394"; } -.bi-file-excel-fill::before { content: "\f395"; } -.bi-file-excel::before { content: "\f396"; } -.bi-file-fill::before { content: "\f397"; } -.bi-file-font-fill::before { content: "\f398"; } -.bi-file-font::before { content: "\f399"; } -.bi-file-image-fill::before { content: "\f39a"; } -.bi-file-image::before { content: "\f39b"; } -.bi-file-lock-fill::before { content: "\f39c"; } -.bi-file-lock::before { content: "\f39d"; } -.bi-file-lock2-fill::before { content: "\f39e"; } -.bi-file-lock2::before { content: "\f39f"; } -.bi-file-medical-fill::before { content: "\f3a0"; } -.bi-file-medical::before { content: "\f3a1"; } -.bi-file-minus-fill::before { content: "\f3a2"; } -.bi-file-minus::before { content: "\f3a3"; } -.bi-file-music-fill::before { content: "\f3a4"; } -.bi-file-music::before { content: "\f3a5"; } -.bi-file-person-fill::before { content: "\f3a6"; } -.bi-file-person::before { content: "\f3a7"; } -.bi-file-play-fill::before { content: "\f3a8"; } -.bi-file-play::before { content: "\f3a9"; } -.bi-file-plus-fill::before { content: "\f3aa"; } -.bi-file-plus::before { content: "\f3ab"; } -.bi-file-post-fill::before { content: "\f3ac"; } -.bi-file-post::before { content: "\f3ad"; } -.bi-file-ppt-fill::before { content: "\f3ae"; } -.bi-file-ppt::before { content: "\f3af"; } -.bi-file-richtext-fill::before { content: "\f3b0"; } -.bi-file-richtext::before { content: "\f3b1"; } -.bi-file-ruled-fill::before { content: "\f3b2"; } -.bi-file-ruled::before { content: "\f3b3"; } -.bi-file-slides-fill::before { content: "\f3b4"; } -.bi-file-slides::before { content: "\f3b5"; } -.bi-file-spreadsheet-fill::before { content: "\f3b6"; } -.bi-file-spreadsheet::before { content: "\f3b7"; } -.bi-file-text-fill::before { content: "\f3b8"; } -.bi-file-text::before { content: "\f3b9"; } -.bi-file-word-fill::before { content: "\f3ba"; } -.bi-file-word::before { content: "\f3bb"; } -.bi-file-x-fill::before { content: "\f3bc"; } -.bi-file-x::before { content: "\f3bd"; } -.bi-file-zip-fill::before { content: "\f3be"; } -.bi-file-zip::before { content: "\f3bf"; } -.bi-file::before { content: "\f3c0"; } -.bi-files-alt::before { content: "\f3c1"; } -.bi-files::before { content: "\f3c2"; } -.bi-film::before { content: "\f3c3"; } -.bi-filter-circle-fill::before { content: "\f3c4"; } -.bi-filter-circle::before { content: "\f3c5"; } -.bi-filter-left::before { content: "\f3c6"; } -.bi-filter-right::before { content: "\f3c7"; } -.bi-filter-square-fill::before { content: "\f3c8"; } -.bi-filter-square::before { content: "\f3c9"; } -.bi-filter::before { content: "\f3ca"; } -.bi-flag-fill::before { content: "\f3cb"; } -.bi-flag::before { content: "\f3cc"; } -.bi-flower1::before { content: "\f3cd"; } -.bi-flower2::before { content: "\f3ce"; } -.bi-flower3::before { content: "\f3cf"; } -.bi-folder-check::before { content: "\f3d0"; } -.bi-folder-fill::before { content: "\f3d1"; } -.bi-folder-minus::before { content: "\f3d2"; } -.bi-folder-plus::before { content: "\f3d3"; } -.bi-folder-symlink-fill::before { content: "\f3d4"; } -.bi-folder-symlink::before { content: "\f3d5"; } -.bi-folder-x::before { content: "\f3d6"; } -.bi-folder::before { content: "\f3d7"; } -.bi-folder2-open::before { content: "\f3d8"; } -.bi-folder2::before { content: "\f3d9"; } -.bi-fonts::before { content: "\f3da"; } -.bi-forward-fill::before { content: "\f3db"; } -.bi-forward::before { content: "\f3dc"; } -.bi-front::before { content: "\f3dd"; } -.bi-fullscreen-exit::before { content: "\f3de"; } -.bi-fullscreen::before { content: "\f3df"; } -.bi-funnel-fill::before { content: "\f3e0"; } -.bi-funnel::before { content: "\f3e1"; } -.bi-gear-fill::before { content: "\f3e2"; } -.bi-gear-wide-connected::before { content: "\f3e3"; } -.bi-gear-wide::before { content: "\f3e4"; } -.bi-gear::before { content: "\f3e5"; } -.bi-gem::before { content: "\f3e6"; } -.bi-geo-alt-fill::before { content: "\f3e7"; } -.bi-geo-alt::before { content: "\f3e8"; } -.bi-geo-fill::before { content: "\f3e9"; } -.bi-geo::before { content: "\f3ea"; } -.bi-gift-fill::before { content: "\f3eb"; } -.bi-gift::before { content: "\f3ec"; } -.bi-github::before { content: "\f3ed"; } -.bi-globe::before { content: "\f3ee"; } -.bi-globe2::before { content: "\f3ef"; } -.bi-google::before { content: "\f3f0"; } -.bi-graph-down::before { content: "\f3f1"; } -.bi-graph-up::before { content: "\f3f2"; } -.bi-grid-1x2-fill::before { content: "\f3f3"; } -.bi-grid-1x2::before { content: "\f3f4"; } -.bi-grid-3x2-gap-fill::before { content: "\f3f5"; } -.bi-grid-3x2-gap::before { content: "\f3f6"; } -.bi-grid-3x2::before { content: "\f3f7"; } -.bi-grid-3x3-gap-fill::before { content: "\f3f8"; } -.bi-grid-3x3-gap::before { content: "\f3f9"; } -.bi-grid-3x3::before { content: "\f3fa"; } -.bi-grid-fill::before { content: "\f3fb"; } -.bi-grid::before { content: "\f3fc"; } -.bi-grip-horizontal::before { content: "\f3fd"; } -.bi-grip-vertical::before { content: "\f3fe"; } -.bi-hammer::before { content: "\f3ff"; } -.bi-hand-index-fill::before { content: "\f400"; } -.bi-hand-index-thumb-fill::before { content: "\f401"; } -.bi-hand-index-thumb::before { content: "\f402"; } -.bi-hand-index::before { content: "\f403"; } -.bi-hand-thumbs-down-fill::before { content: "\f404"; } -.bi-hand-thumbs-down::before { content: "\f405"; } -.bi-hand-thumbs-up-fill::before { content: "\f406"; } -.bi-hand-thumbs-up::before { content: "\f407"; } -.bi-handbag-fill::before { content: "\f408"; } -.bi-handbag::before { content: "\f409"; } -.bi-hash::before { content: "\f40a"; } -.bi-hdd-fill::before { content: "\f40b"; } -.bi-hdd-network-fill::before { content: "\f40c"; } -.bi-hdd-network::before { content: "\f40d"; } -.bi-hdd-rack-fill::before { content: "\f40e"; } -.bi-hdd-rack::before { content: "\f40f"; } -.bi-hdd-stack-fill::before { content: "\f410"; } -.bi-hdd-stack::before { content: "\f411"; } -.bi-hdd::before { content: "\f412"; } -.bi-headphones::before { content: "\f413"; } -.bi-headset::before { content: "\f414"; } -.bi-heart-fill::before { content: "\f415"; } -.bi-heart-half::before { content: "\f416"; } -.bi-heart::before { content: "\f417"; } -.bi-heptagon-fill::before { content: "\f418"; } -.bi-heptagon-half::before { content: "\f419"; } -.bi-heptagon::before { content: "\f41a"; } -.bi-hexagon-fill::before { content: "\f41b"; } -.bi-hexagon-half::before { content: "\f41c"; } -.bi-hexagon::before { content: "\f41d"; } -.bi-hourglass-bottom::before { content: "\f41e"; } -.bi-hourglass-split::before { content: "\f41f"; } -.bi-hourglass-top::before { content: "\f420"; } -.bi-hourglass::before { content: "\f421"; } -.bi-house-door-fill::before { content: "\f422"; } -.bi-house-door::before { content: "\f423"; } -.bi-house-fill::before { content: "\f424"; } -.bi-house::before { content: "\f425"; } -.bi-hr::before { content: "\f426"; } -.bi-hurricane::before { content: "\f427"; } -.bi-image-alt::before { content: "\f428"; } -.bi-image-fill::before { content: "\f429"; } -.bi-image::before { content: "\f42a"; } -.bi-images::before { content: "\f42b"; } -.bi-inbox-fill::before { content: "\f42c"; } -.bi-inbox::before { content: "\f42d"; } -.bi-inboxes-fill::before { content: "\f42e"; } -.bi-inboxes::before { content: "\f42f"; } -.bi-info-circle-fill::before { content: "\f430"; } -.bi-info-circle::before { content: "\f431"; } -.bi-info-square-fill::before { content: "\f432"; } -.bi-info-square::before { content: "\f433"; } -.bi-info::before { content: "\f434"; } -.bi-input-cursor-text::before { content: "\f435"; } -.bi-input-cursor::before { content: "\f436"; } -.bi-instagram::before { content: "\f437"; } -.bi-intersect::before { content: "\f438"; } -.bi-journal-album::before { content: "\f439"; } -.bi-journal-arrow-down::before { content: "\f43a"; } -.bi-journal-arrow-up::before { content: "\f43b"; } -.bi-journal-bookmark-fill::before { content: "\f43c"; } -.bi-journal-bookmark::before { content: "\f43d"; } -.bi-journal-check::before { content: "\f43e"; } -.bi-journal-code::before { content: "\f43f"; } -.bi-journal-medical::before { content: "\f440"; } -.bi-journal-minus::before { content: "\f441"; } -.bi-journal-plus::before { content: "\f442"; } -.bi-journal-richtext::before { content: "\f443"; } -.bi-journal-text::before { content: "\f444"; } -.bi-journal-x::before { content: "\f445"; } -.bi-journal::before { content: "\f446"; } -.bi-journals::before { content: "\f447"; } -.bi-joystick::before { content: "\f448"; } -.bi-justify-left::before { content: "\f449"; } -.bi-justify-right::before { content: "\f44a"; } -.bi-justify::before { content: "\f44b"; } -.bi-kanban-fill::before { content: "\f44c"; } -.bi-kanban::before { content: "\f44d"; } -.bi-key-fill::before { content: "\f44e"; } -.bi-key::before { content: "\f44f"; } -.bi-keyboard-fill::before { content: "\f450"; } -.bi-keyboard::before { content: "\f451"; } -.bi-ladder::before { content: "\f452"; } -.bi-lamp-fill::before { content: "\f453"; } -.bi-lamp::before { content: "\f454"; } -.bi-laptop-fill::before { content: "\f455"; } -.bi-laptop::before { content: "\f456"; } -.bi-layer-backward::before { content: "\f457"; } -.bi-layer-forward::before { content: "\f458"; } -.bi-layers-fill::before { content: "\f459"; } -.bi-layers-half::before { content: "\f45a"; } -.bi-layers::before { content: "\f45b"; } -.bi-layout-sidebar-inset-reverse::before { content: "\f45c"; } -.bi-layout-sidebar-inset::before { content: "\f45d"; } -.bi-layout-sidebar-reverse::before { content: "\f45e"; } -.bi-layout-sidebar::before { content: "\f45f"; } -.bi-layout-split::before { content: "\f460"; } -.bi-layout-text-sidebar-reverse::before { content: "\f461"; } -.bi-layout-text-sidebar::before { content: "\f462"; } -.bi-layout-text-window-reverse::before { content: "\f463"; } -.bi-layout-text-window::before { content: "\f464"; } -.bi-layout-three-columns::before { content: "\f465"; } -.bi-layout-wtf::before { content: "\f466"; } -.bi-life-preserver::before { content: "\f467"; } -.bi-lightbulb-fill::before { content: "\f468"; } -.bi-lightbulb-off-fill::before { content: "\f469"; } -.bi-lightbulb-off::before { content: "\f46a"; } -.bi-lightbulb::before { content: "\f46b"; } -.bi-lightning-charge-fill::before { content: "\f46c"; } -.bi-lightning-charge::before { content: "\f46d"; } -.bi-lightning-fill::before { content: "\f46e"; } -.bi-lightning::before { content: "\f46f"; } -.bi-link-45deg::before { content: "\f470"; } -.bi-link::before { content: "\f471"; } -.bi-linkedin::before { content: "\f472"; } -.bi-list-check::before { content: "\f473"; } -.bi-list-nested::before { content: "\f474"; } -.bi-list-ol::before { content: "\f475"; } -.bi-list-stars::before { content: "\f476"; } -.bi-list-task::before { content: "\f477"; } -.bi-list-ul::before { content: "\f478"; } -.bi-list::before { content: "\f479"; } -.bi-lock-fill::before { content: "\f47a"; } -.bi-lock::before { content: "\f47b"; } -.bi-mailbox::before { content: "\f47c"; } -.bi-mailbox2::before { content: "\f47d"; } -.bi-map-fill::before { content: "\f47e"; } -.bi-map::before { content: "\f47f"; } -.bi-markdown-fill::before { content: "\f480"; } -.bi-markdown::before { content: "\f481"; } -.bi-mask::before { content: "\f482"; } -.bi-megaphone-fill::before { content: "\f483"; } -.bi-megaphone::before { content: "\f484"; } -.bi-menu-app-fill::before { content: "\f485"; } -.bi-menu-app::before { content: "\f486"; } -.bi-menu-button-fill::before { content: "\f487"; } -.bi-menu-button-wide-fill::before { content: "\f488"; } -.bi-menu-button-wide::before { content: "\f489"; } -.bi-menu-button::before { content: "\f48a"; } -.bi-menu-down::before { content: "\f48b"; } -.bi-menu-up::before { content: "\f48c"; } -.bi-mic-fill::before { content: "\f48d"; } -.bi-mic-mute-fill::before { content: "\f48e"; } -.bi-mic-mute::before { content: "\f48f"; } -.bi-mic::before { content: "\f490"; } -.bi-minecart-loaded::before { content: "\f491"; } -.bi-minecart::before { content: "\f492"; } -.bi-moisture::before { content: "\f493"; } -.bi-moon-fill::before { content: "\f494"; } -.bi-moon-stars-fill::before { content: "\f495"; } -.bi-moon-stars::before { content: "\f496"; } -.bi-moon::before { content: "\f497"; } -.bi-mouse-fill::before { content: "\f498"; } -.bi-mouse::before { content: "\f499"; } -.bi-mouse2-fill::before { content: "\f49a"; } -.bi-mouse2::before { content: "\f49b"; } -.bi-mouse3-fill::before { content: "\f49c"; } -.bi-mouse3::before { content: "\f49d"; } -.bi-music-note-beamed::before { content: "\f49e"; } -.bi-music-note-list::before { content: "\f49f"; } -.bi-music-note::before { content: "\f4a0"; } -.bi-music-player-fill::before { content: "\f4a1"; } -.bi-music-player::before { content: "\f4a2"; } -.bi-newspaper::before { content: "\f4a3"; } -.bi-node-minus-fill::before { content: "\f4a4"; } -.bi-node-minus::before { content: "\f4a5"; } -.bi-node-plus-fill::before { content: "\f4a6"; } -.bi-node-plus::before { content: "\f4a7"; } -.bi-nut-fill::before { content: "\f4a8"; } -.bi-nut::before { content: "\f4a9"; } -.bi-octagon-fill::before { content: "\f4aa"; } -.bi-octagon-half::before { content: "\f4ab"; } -.bi-octagon::before { content: "\f4ac"; } -.bi-option::before { content: "\f4ad"; } -.bi-outlet::before { content: "\f4ae"; } -.bi-paint-bucket::before { content: "\f4af"; } -.bi-palette-fill::before { content: "\f4b0"; } -.bi-palette::before { content: "\f4b1"; } -.bi-palette2::before { content: "\f4b2"; } -.bi-paperclip::before { content: "\f4b3"; } -.bi-paragraph::before { content: "\f4b4"; } -.bi-patch-check-fill::before { content: "\f4b5"; } -.bi-patch-check::before { content: "\f4b6"; } -.bi-patch-exclamation-fill::before { content: "\f4b7"; } -.bi-patch-exclamation::before { content: "\f4b8"; } -.bi-patch-minus-fill::before { content: "\f4b9"; } -.bi-patch-minus::before { content: "\f4ba"; } -.bi-patch-plus-fill::before { content: "\f4bb"; } -.bi-patch-plus::before { content: "\f4bc"; } -.bi-patch-question-fill::before { content: "\f4bd"; } -.bi-patch-question::before { content: "\f4be"; } -.bi-pause-btn-fill::before { content: "\f4bf"; } -.bi-pause-btn::before { content: "\f4c0"; } -.bi-pause-circle-fill::before { content: "\f4c1"; } -.bi-pause-circle::before { content: "\f4c2"; } -.bi-pause-fill::before { content: "\f4c3"; } -.bi-pause::before { content: "\f4c4"; } -.bi-peace-fill::before { content: "\f4c5"; } -.bi-peace::before { content: "\f4c6"; } -.bi-pen-fill::before { content: "\f4c7"; } -.bi-pen::before { content: "\f4c8"; } -.bi-pencil-fill::before { content: "\f4c9"; } -.bi-pencil-square::before { content: "\f4ca"; } -.bi-pencil::before { content: "\f4cb"; } -.bi-pentagon-fill::before { content: "\f4cc"; } -.bi-pentagon-half::before { content: "\f4cd"; } -.bi-pentagon::before { content: "\f4ce"; } -.bi-people-fill::before { content: "\f4cf"; } -.bi-people::before { content: "\f4d0"; } -.bi-percent::before { content: "\f4d1"; } -.bi-person-badge-fill::before { content: "\f4d2"; } -.bi-person-badge::before { content: "\f4d3"; } -.bi-person-bounding-box::before { content: "\f4d4"; } -.bi-person-check-fill::before { content: "\f4d5"; } -.bi-person-check::before { content: "\f4d6"; } -.bi-person-circle::before { content: "\f4d7"; } -.bi-person-dash-fill::before { content: "\f4d8"; } -.bi-person-dash::before { content: "\f4d9"; } -.bi-person-fill::before { content: "\f4da"; } -.bi-person-lines-fill::before { content: "\f4db"; } -.bi-person-plus-fill::before { content: "\f4dc"; } -.bi-person-plus::before { content: "\f4dd"; } -.bi-person-square::before { content: "\f4de"; } -.bi-person-x-fill::before { content: "\f4df"; } -.bi-person-x::before { content: "\f4e0"; } -.bi-person::before { content: "\f4e1"; } -.bi-phone-fill::before { content: "\f4e2"; } -.bi-phone-landscape-fill::before { content: "\f4e3"; } -.bi-phone-landscape::before { content: "\f4e4"; } -.bi-phone-vibrate-fill::before { content: "\f4e5"; } -.bi-phone-vibrate::before { content: "\f4e6"; } -.bi-phone::before { content: "\f4e7"; } -.bi-pie-chart-fill::before { content: "\f4e8"; } -.bi-pie-chart::before { content: "\f4e9"; } -.bi-pin-angle-fill::before { content: "\f4ea"; } -.bi-pin-angle::before { content: "\f4eb"; } -.bi-pin-fill::before { content: "\f4ec"; } -.bi-pin::before { content: "\f4ed"; } -.bi-pip-fill::before { content: "\f4ee"; } -.bi-pip::before { content: "\f4ef"; } -.bi-play-btn-fill::before { content: "\f4f0"; } -.bi-play-btn::before { content: "\f4f1"; } -.bi-play-circle-fill::before { content: "\f4f2"; } -.bi-play-circle::before { content: "\f4f3"; } -.bi-play-fill::before { content: "\f4f4"; } -.bi-play::before { content: "\f4f5"; } -.bi-plug-fill::before { content: "\f4f6"; } -.bi-plug::before { content: "\f4f7"; } -.bi-plus-circle-dotted::before { content: "\f4f8"; } -.bi-plus-circle-fill::before { content: "\f4f9"; } -.bi-plus-circle::before { content: "\f4fa"; } -.bi-plus-square-dotted::before { content: "\f4fb"; } -.bi-plus-square-fill::before { content: "\f4fc"; } -.bi-plus-square::before { content: "\f4fd"; } -.bi-plus::before { content: "\f4fe"; } -.bi-power::before { content: "\f4ff"; } -.bi-printer-fill::before { content: "\f500"; } -.bi-printer::before { content: "\f501"; } -.bi-puzzle-fill::before { content: "\f502"; } -.bi-puzzle::before { content: "\f503"; } -.bi-question-circle-fill::before { content: "\f504"; } -.bi-question-circle::before { content: "\f505"; } -.bi-question-diamond-fill::before { content: "\f506"; } -.bi-question-diamond::before { content: "\f507"; } -.bi-question-octagon-fill::before { content: "\f508"; } -.bi-question-octagon::before { content: "\f509"; } -.bi-question-square-fill::before { content: "\f50a"; } -.bi-question-square::before { content: "\f50b"; } -.bi-question::before { content: "\f50c"; } -.bi-rainbow::before { content: "\f50d"; } -.bi-receipt-cutoff::before { content: "\f50e"; } -.bi-receipt::before { content: "\f50f"; } -.bi-reception-0::before { content: "\f510"; } -.bi-reception-1::before { content: "\f511"; } -.bi-reception-2::before { content: "\f512"; } -.bi-reception-3::before { content: "\f513"; } -.bi-reception-4::before { content: "\f514"; } -.bi-record-btn-fill::before { content: "\f515"; } -.bi-record-btn::before { content: "\f516"; } -.bi-record-circle-fill::before { content: "\f517"; } -.bi-record-circle::before { content: "\f518"; } -.bi-record-fill::before { content: "\f519"; } -.bi-record::before { content: "\f51a"; } -.bi-record2-fill::before { content: "\f51b"; } -.bi-record2::before { content: "\f51c"; } -.bi-reply-all-fill::before { content: "\f51d"; } -.bi-reply-all::before { content: "\f51e"; } -.bi-reply-fill::before { content: "\f51f"; } -.bi-reply::before { content: "\f520"; } -.bi-rss-fill::before { content: "\f521"; } -.bi-rss::before { content: "\f522"; } -.bi-rulers::before { content: "\f523"; } -.bi-save-fill::before { content: "\f524"; } -.bi-save::before { content: "\f525"; } -.bi-save2-fill::before { content: "\f526"; } -.bi-save2::before { content: "\f527"; } -.bi-scissors::before { content: "\f528"; } -.bi-screwdriver::before { content: "\f529"; } -.bi-search::before { content: "\f52a"; } -.bi-segmented-nav::before { content: "\f52b"; } -.bi-server::before { content: "\f52c"; } -.bi-share-fill::before { content: "\f52d"; } -.bi-share::before { content: "\f52e"; } -.bi-shield-check::before { content: "\f52f"; } -.bi-shield-exclamation::before { content: "\f530"; } -.bi-shield-fill-check::before { content: "\f531"; } -.bi-shield-fill-exclamation::before { content: "\f532"; } -.bi-shield-fill-minus::before { content: "\f533"; } -.bi-shield-fill-plus::before { content: "\f534"; } -.bi-shield-fill-x::before { content: "\f535"; } -.bi-shield-fill::before { content: "\f536"; } -.bi-shield-lock-fill::before { content: "\f537"; } -.bi-shield-lock::before { content: "\f538"; } -.bi-shield-minus::before { content: "\f539"; } -.bi-shield-plus::before { content: "\f53a"; } -.bi-shield-shaded::before { content: "\f53b"; } -.bi-shield-slash-fill::before { content: "\f53c"; } -.bi-shield-slash::before { content: "\f53d"; } -.bi-shield-x::before { content: "\f53e"; } -.bi-shield::before { content: "\f53f"; } -.bi-shift-fill::before { content: "\f540"; } -.bi-shift::before { content: "\f541"; } -.bi-shop-window::before { content: "\f542"; } -.bi-shop::before { content: "\f543"; } -.bi-shuffle::before { content: "\f544"; } -.bi-signpost-2-fill::before { content: "\f545"; } -.bi-signpost-2::before { content: "\f546"; } -.bi-signpost-fill::before { content: "\f547"; } -.bi-signpost-split-fill::before { content: "\f548"; } -.bi-signpost-split::before { content: "\f549"; } -.bi-signpost::before { content: "\f54a"; } -.bi-sim-fill::before { content: "\f54b"; } -.bi-sim::before { content: "\f54c"; } -.bi-skip-backward-btn-fill::before { content: "\f54d"; } -.bi-skip-backward-btn::before { content: "\f54e"; } -.bi-skip-backward-circle-fill::before { content: "\f54f"; } -.bi-skip-backward-circle::before { content: "\f550"; } -.bi-skip-backward-fill::before { content: "\f551"; } -.bi-skip-backward::before { content: "\f552"; } -.bi-skip-end-btn-fill::before { content: "\f553"; } -.bi-skip-end-btn::before { content: "\f554"; } -.bi-skip-end-circle-fill::before { content: "\f555"; } -.bi-skip-end-circle::before { content: "\f556"; } -.bi-skip-end-fill::before { content: "\f557"; } -.bi-skip-end::before { content: "\f558"; } -.bi-skip-forward-btn-fill::before { content: "\f559"; } -.bi-skip-forward-btn::before { content: "\f55a"; } -.bi-skip-forward-circle-fill::before { content: "\f55b"; } -.bi-skip-forward-circle::before { content: "\f55c"; } -.bi-skip-forward-fill::before { content: "\f55d"; } -.bi-skip-forward::before { content: "\f55e"; } -.bi-skip-start-btn-fill::before { content: "\f55f"; } -.bi-skip-start-btn::before { content: "\f560"; } -.bi-skip-start-circle-fill::before { content: "\f561"; } -.bi-skip-start-circle::before { content: "\f562"; } -.bi-skip-start-fill::before { content: "\f563"; } -.bi-skip-start::before { content: "\f564"; } -.bi-slack::before { content: "\f565"; } -.bi-slash-circle-fill::before { content: "\f566"; } -.bi-slash-circle::before { content: "\f567"; } -.bi-slash-square-fill::before { content: "\f568"; } -.bi-slash-square::before { content: "\f569"; } -.bi-slash::before { content: "\f56a"; } -.bi-sliders::before { content: "\f56b"; } -.bi-smartwatch::before { content: "\f56c"; } -.bi-snow::before { content: "\f56d"; } -.bi-snow2::before { content: "\f56e"; } -.bi-snow3::before { content: "\f56f"; } -.bi-sort-alpha-down-alt::before { content: "\f570"; } -.bi-sort-alpha-down::before { content: "\f571"; } -.bi-sort-alpha-up-alt::before { content: "\f572"; } -.bi-sort-alpha-up::before { content: "\f573"; } -.bi-sort-down-alt::before { content: "\f574"; } -.bi-sort-down::before { content: "\f575"; } -.bi-sort-numeric-down-alt::before { content: "\f576"; } -.bi-sort-numeric-down::before { content: "\f577"; } -.bi-sort-numeric-up-alt::before { content: "\f578"; } -.bi-sort-numeric-up::before { content: "\f579"; } -.bi-sort-up-alt::before { content: "\f57a"; } -.bi-sort-up::before { content: "\f57b"; } -.bi-soundwave::before { content: "\f57c"; } -.bi-speaker-fill::before { content: "\f57d"; } -.bi-speaker::before { content: "\f57e"; } -.bi-speedometer::before { content: "\f57f"; } -.bi-speedometer2::before { content: "\f580"; } -.bi-spellcheck::before { content: "\f581"; } -.bi-square-fill::before { content: "\f582"; } -.bi-square-half::before { content: "\f583"; } -.bi-square::before { content: "\f584"; } -.bi-stack::before { content: "\f585"; } -.bi-star-fill::before { content: "\f586"; } -.bi-star-half::before { content: "\f587"; } -.bi-star::before { content: "\f588"; } -.bi-stars::before { content: "\f589"; } -.bi-stickies-fill::before { content: "\f58a"; } -.bi-stickies::before { content: "\f58b"; } -.bi-sticky-fill::before { content: "\f58c"; } -.bi-sticky::before { content: "\f58d"; } -.bi-stop-btn-fill::before { content: "\f58e"; } -.bi-stop-btn::before { content: "\f58f"; } -.bi-stop-circle-fill::before { content: "\f590"; } -.bi-stop-circle::before { content: "\f591"; } -.bi-stop-fill::before { content: "\f592"; } -.bi-stop::before { content: "\f593"; } -.bi-stoplights-fill::before { content: "\f594"; } -.bi-stoplights::before { content: "\f595"; } -.bi-stopwatch-fill::before { content: "\f596"; } -.bi-stopwatch::before { content: "\f597"; } -.bi-subtract::before { content: "\f598"; } -.bi-suit-club-fill::before { content: "\f599"; } -.bi-suit-club::before { content: "\f59a"; } -.bi-suit-diamond-fill::before { content: "\f59b"; } -.bi-suit-diamond::before { content: "\f59c"; } -.bi-suit-heart-fill::before { content: "\f59d"; } -.bi-suit-heart::before { content: "\f59e"; } -.bi-suit-spade-fill::before { content: "\f59f"; } -.bi-suit-spade::before { content: "\f5a0"; } -.bi-sun-fill::before { content: "\f5a1"; } -.bi-sun::before { content: "\f5a2"; } -.bi-sunglasses::before { content: "\f5a3"; } -.bi-sunrise-fill::before { content: "\f5a4"; } -.bi-sunrise::before { content: "\f5a5"; } -.bi-sunset-fill::before { content: "\f5a6"; } -.bi-sunset::before { content: "\f5a7"; } -.bi-symmetry-horizontal::before { content: "\f5a8"; } -.bi-symmetry-vertical::before { content: "\f5a9"; } -.bi-table::before { content: "\f5aa"; } -.bi-tablet-fill::before { content: "\f5ab"; } -.bi-tablet-landscape-fill::before { content: "\f5ac"; } -.bi-tablet-landscape::before { content: "\f5ad"; } -.bi-tablet::before { content: "\f5ae"; } -.bi-tag-fill::before { content: "\f5af"; } -.bi-tag::before { content: "\f5b0"; } -.bi-tags-fill::before { content: "\f5b1"; } -.bi-tags::before { content: "\f5b2"; } -.bi-telegram::before { content: "\f5b3"; } -.bi-telephone-fill::before { content: "\f5b4"; } -.bi-telephone-forward-fill::before { content: "\f5b5"; } -.bi-telephone-forward::before { content: "\f5b6"; } -.bi-telephone-inbound-fill::before { content: "\f5b7"; } -.bi-telephone-inbound::before { content: "\f5b8"; } -.bi-telephone-minus-fill::before { content: "\f5b9"; } -.bi-telephone-minus::before { content: "\f5ba"; } -.bi-telephone-outbound-fill::before { content: "\f5bb"; } -.bi-telephone-outbound::before { content: "\f5bc"; } -.bi-telephone-plus-fill::before { content: "\f5bd"; } -.bi-telephone-plus::before { content: "\f5be"; } -.bi-telephone-x-fill::before { content: "\f5bf"; } -.bi-telephone-x::before { content: "\f5c0"; } -.bi-telephone::before { content: "\f5c1"; } -.bi-terminal-fill::before { content: "\f5c2"; } -.bi-terminal::before { content: "\f5c3"; } -.bi-text-center::before { content: "\f5c4"; } -.bi-text-indent-left::before { content: "\f5c5"; } -.bi-text-indent-right::before { content: "\f5c6"; } -.bi-text-left::before { content: "\f5c7"; } -.bi-text-paragraph::before { content: "\f5c8"; } -.bi-text-right::before { content: "\f5c9"; } -.bi-textarea-resize::before { content: "\f5ca"; } -.bi-textarea-t::before { content: "\f5cb"; } -.bi-textarea::before { content: "\f5cc"; } -.bi-thermometer-half::before { content: "\f5cd"; } -.bi-thermometer-high::before { content: "\f5ce"; } -.bi-thermometer-low::before { content: "\f5cf"; } -.bi-thermometer-snow::before { content: "\f5d0"; } -.bi-thermometer-sun::before { content: "\f5d1"; } -.bi-thermometer::before { content: "\f5d2"; } -.bi-three-dots-vertical::before { content: "\f5d3"; } -.bi-three-dots::before { content: "\f5d4"; } -.bi-toggle-off::before { content: "\f5d5"; } -.bi-toggle-on::before { content: "\f5d6"; } -.bi-toggle2-off::before { content: "\f5d7"; } -.bi-toggle2-on::before { content: "\f5d8"; } -.bi-toggles::before { content: "\f5d9"; } -.bi-toggles2::before { content: "\f5da"; } -.bi-tools::before { content: "\f5db"; } -.bi-tornado::before { content: "\f5dc"; } -.bi-trash-fill::before { content: "\f5dd"; } -.bi-trash::before { content: "\f5de"; } -.bi-trash2-fill::before { content: "\f5df"; } -.bi-trash2::before { content: "\f5e0"; } -.bi-tree-fill::before { content: "\f5e1"; } -.bi-tree::before { content: "\f5e2"; } -.bi-triangle-fill::before { content: "\f5e3"; } -.bi-triangle-half::before { content: "\f5e4"; } -.bi-triangle::before { content: "\f5e5"; } -.bi-trophy-fill::before { content: "\f5e6"; } -.bi-trophy::before { content: "\f5e7"; } -.bi-tropical-storm::before { content: "\f5e8"; } -.bi-truck-flatbed::before { content: "\f5e9"; } -.bi-truck::before { content: "\f5ea"; } -.bi-tsunami::before { content: "\f5eb"; } -.bi-tv-fill::before { content: "\f5ec"; } -.bi-tv::before { content: "\f5ed"; } -.bi-twitch::before { content: "\f5ee"; } -.bi-twitter::before { content: "\f5ef"; } -.bi-type-bold::before { content: "\f5f0"; } -.bi-type-h1::before { content: "\f5f1"; } -.bi-type-h2::before { content: "\f5f2"; } -.bi-type-h3::before { content: "\f5f3"; } -.bi-type-italic::before { content: "\f5f4"; } -.bi-type-strikethrough::before { content: "\f5f5"; } -.bi-type-underline::before { content: "\f5f6"; } -.bi-type::before { content: "\f5f7"; } -.bi-ui-checks-grid::before { content: "\f5f8"; } -.bi-ui-checks::before { content: "\f5f9"; } -.bi-ui-radios-grid::before { content: "\f5fa"; } -.bi-ui-radios::before { content: "\f5fb"; } -.bi-umbrella-fill::before { content: "\f5fc"; } -.bi-umbrella::before { content: "\f5fd"; } -.bi-union::before { content: "\f5fe"; } -.bi-unlock-fill::before { content: "\f5ff"; } -.bi-unlock::before { content: "\f600"; } -.bi-upc-scan::before { content: "\f601"; } -.bi-upc::before { content: "\f602"; } -.bi-upload::before { content: "\f603"; } -.bi-vector-pen::before { content: "\f604"; } -.bi-view-list::before { content: "\f605"; } -.bi-view-stacked::before { content: "\f606"; } -.bi-vinyl-fill::before { content: "\f607"; } -.bi-vinyl::before { content: "\f608"; } -.bi-voicemail::before { content: "\f609"; } -.bi-volume-down-fill::before { content: "\f60a"; } -.bi-volume-down::before { content: "\f60b"; } -.bi-volume-mute-fill::before { content: "\f60c"; } -.bi-volume-mute::before { content: "\f60d"; } -.bi-volume-off-fill::before { content: "\f60e"; } -.bi-volume-off::before { content: "\f60f"; } -.bi-volume-up-fill::before { content: "\f610"; } -.bi-volume-up::before { content: "\f611"; } -.bi-vr::before { content: "\f612"; } -.bi-wallet-fill::before { content: "\f613"; } -.bi-wallet::before { content: "\f614"; } -.bi-wallet2::before { content: "\f615"; } -.bi-watch::before { content: "\f616"; } -.bi-water::before { content: "\f617"; } -.bi-whatsapp::before { content: "\f618"; } -.bi-wifi-1::before { content: "\f619"; } -.bi-wifi-2::before { content: "\f61a"; } -.bi-wifi-off::before { content: "\f61b"; } -.bi-wifi::before { content: "\f61c"; } -.bi-wind::before { content: "\f61d"; } -.bi-window-dock::before { content: "\f61e"; } -.bi-window-sidebar::before { content: "\f61f"; } -.bi-window::before { content: "\f620"; } -.bi-wrench::before { content: "\f621"; } -.bi-x-circle-fill::before { content: "\f622"; } -.bi-x-circle::before { content: "\f623"; } -.bi-x-diamond-fill::before { content: "\f624"; } -.bi-x-diamond::before { content: "\f625"; } -.bi-x-octagon-fill::before { content: "\f626"; } -.bi-x-octagon::before { content: "\f627"; } -.bi-x-square-fill::before { content: "\f628"; } -.bi-x-square::before { content: "\f629"; } -.bi-x::before { content: "\f62a"; } -.bi-youtube::before { content: "\f62b"; } -.bi-zoom-in::before { content: "\f62c"; } -.bi-zoom-out::before { content: "\f62d"; } -.bi-bank::before { content: "\f62e"; } -.bi-bank2::before { content: "\f62f"; } -.bi-bell-slash-fill::before { content: "\f630"; } -.bi-bell-slash::before { content: "\f631"; } -.bi-cash-coin::before { content: "\f632"; } -.bi-check-lg::before { content: "\f633"; } -.bi-coin::before { content: "\f634"; } -.bi-currency-bitcoin::before { content: "\f635"; } -.bi-currency-dollar::before { content: "\f636"; } -.bi-currency-euro::before { content: "\f637"; } -.bi-currency-exchange::before { content: "\f638"; } -.bi-currency-pound::before { content: "\f639"; } -.bi-currency-yen::before { content: "\f63a"; } -.bi-dash-lg::before { content: "\f63b"; } -.bi-exclamation-lg::before { content: "\f63c"; } -.bi-file-earmark-pdf-fill::before { content: "\f63d"; } -.bi-file-earmark-pdf::before { content: "\f63e"; } -.bi-file-pdf-fill::before { content: "\f63f"; } -.bi-file-pdf::before { content: "\f640"; } -.bi-gender-ambiguous::before { content: "\f641"; } -.bi-gender-female::before { content: "\f642"; } -.bi-gender-male::before { content: "\f643"; } -.bi-gender-trans::before { content: "\f644"; } -.bi-headset-vr::before { content: "\f645"; } -.bi-info-lg::before { content: "\f646"; } -.bi-mastodon::before { content: "\f647"; } -.bi-messenger::before { content: "\f648"; } -.bi-piggy-bank-fill::before { content: "\f649"; } -.bi-piggy-bank::before { content: "\f64a"; } -.bi-pin-map-fill::before { content: "\f64b"; } -.bi-pin-map::before { content: "\f64c"; } -.bi-plus-lg::before { content: "\f64d"; } -.bi-question-lg::before { content: "\f64e"; } -.bi-recycle::before { content: "\f64f"; } -.bi-reddit::before { content: "\f650"; } -.bi-safe-fill::before { content: "\f651"; } -.bi-safe2-fill::before { content: "\f652"; } -.bi-safe2::before { content: "\f653"; } -.bi-sd-card-fill::before { content: "\f654"; } -.bi-sd-card::before { content: "\f655"; } -.bi-skype::before { content: "\f656"; } -.bi-slash-lg::before { content: "\f657"; } -.bi-translate::before { content: "\f658"; } -.bi-x-lg::before { content: "\f659"; } -.bi-safe::before { content: "\f65a"; } -.bi-apple::before { content: "\f65b"; } -.bi-microsoft::before { content: "\f65d"; } -.bi-windows::before { content: "\f65e"; } -.bi-behance::before { content: "\f65c"; } -.bi-dribbble::before { content: "\f65f"; } -.bi-line::before { content: "\f660"; } -.bi-medium::before { content: "\f661"; } -.bi-paypal::before { content: "\f662"; } -.bi-pinterest::before { content: "\f663"; } -.bi-signal::before { content: "\f664"; } -.bi-snapchat::before { content: "\f665"; } -.bi-spotify::before { content: "\f666"; } -.bi-stack-overflow::before { content: "\f667"; } -.bi-strava::before { content: "\f668"; } -.bi-wordpress::before { content: "\f669"; } -.bi-vimeo::before { content: "\f66a"; } -.bi-activity::before { content: "\f66b"; } -.bi-easel2-fill::before { content: "\f66c"; } -.bi-easel2::before { content: "\f66d"; } -.bi-easel3-fill::before { content: "\f66e"; } -.bi-easel3::before { content: "\f66f"; } -.bi-fan::before { content: "\f670"; } -.bi-fingerprint::before { content: "\f671"; } -.bi-graph-down-arrow::before { content: "\f672"; } -.bi-graph-up-arrow::before { content: "\f673"; } -.bi-hypnotize::before { content: "\f674"; } -.bi-magic::before { content: "\f675"; } -.bi-person-rolodex::before { content: "\f676"; } -.bi-person-video::before { content: "\f677"; } -.bi-person-video2::before { content: "\f678"; } -.bi-person-video3::before { content: "\f679"; } -.bi-person-workspace::before { content: "\f67a"; } -.bi-radioactive::before { content: "\f67b"; } -.bi-webcam-fill::before { content: "\f67c"; } -.bi-webcam::before { content: "\f67d"; } -.bi-yin-yang::before { content: "\f67e"; } -.bi-bandaid-fill::before { content: "\f680"; } -.bi-bandaid::before { content: "\f681"; } -.bi-bluetooth::before { content: "\f682"; } -.bi-body-text::before { content: "\f683"; } -.bi-boombox::before { content: "\f684"; } -.bi-boxes::before { content: "\f685"; } -.bi-dpad-fill::before { content: "\f686"; } -.bi-dpad::before { content: "\f687"; } -.bi-ear-fill::before { content: "\f688"; } -.bi-ear::before { content: "\f689"; } -.bi-envelope-check-fill::before { content: "\f68b"; } -.bi-envelope-check::before { content: "\f68c"; } -.bi-envelope-dash-fill::before { content: "\f68e"; } -.bi-envelope-dash::before { content: "\f68f"; } -.bi-envelope-exclamation-fill::before { content: "\f691"; } -.bi-envelope-exclamation::before { content: "\f692"; } -.bi-envelope-plus-fill::before { content: "\f693"; } -.bi-envelope-plus::before { content: "\f694"; } -.bi-envelope-slash-fill::before { content: "\f696"; } -.bi-envelope-slash::before { content: "\f697"; } -.bi-envelope-x-fill::before { content: "\f699"; } -.bi-envelope-x::before { content: "\f69a"; } -.bi-explicit-fill::before { content: "\f69b"; } -.bi-explicit::before { content: "\f69c"; } -.bi-git::before { content: "\f69d"; } -.bi-infinity::before { content: "\f69e"; } -.bi-list-columns-reverse::before { content: "\f69f"; } -.bi-list-columns::before { content: "\f6a0"; } -.bi-meta::before { content: "\f6a1"; } -.bi-nintendo-switch::before { content: "\f6a4"; } -.bi-pc-display-horizontal::before { content: "\f6a5"; } -.bi-pc-display::before { content: "\f6a6"; } -.bi-pc-horizontal::before { content: "\f6a7"; } -.bi-pc::before { content: "\f6a8"; } -.bi-playstation::before { content: "\f6a9"; } -.bi-plus-slash-minus::before { content: "\f6aa"; } -.bi-projector-fill::before { content: "\f6ab"; } -.bi-projector::before { content: "\f6ac"; } -.bi-qr-code-scan::before { content: "\f6ad"; } -.bi-qr-code::before { content: "\f6ae"; } -.bi-quora::before { content: "\f6af"; } -.bi-quote::before { content: "\f6b0"; } -.bi-robot::before { content: "\f6b1"; } -.bi-send-check-fill::before { content: "\f6b2"; } -.bi-send-check::before { content: "\f6b3"; } -.bi-send-dash-fill::before { content: "\f6b4"; } -.bi-send-dash::before { content: "\f6b5"; } -.bi-send-exclamation-fill::before { content: "\f6b7"; } -.bi-send-exclamation::before { content: "\f6b8"; } -.bi-send-fill::before { content: "\f6b9"; } -.bi-send-plus-fill::before { content: "\f6ba"; } -.bi-send-plus::before { content: "\f6bb"; } -.bi-send-slash-fill::before { content: "\f6bc"; } -.bi-send-slash::before { content: "\f6bd"; } -.bi-send-x-fill::before { content: "\f6be"; } -.bi-send-x::before { content: "\f6bf"; } -.bi-send::before { content: "\f6c0"; } -.bi-steam::before { content: "\f6c1"; } -.bi-terminal-dash::before { content: "\f6c3"; } -.bi-terminal-plus::before { content: "\f6c4"; } -.bi-terminal-split::before { content: "\f6c5"; } -.bi-ticket-detailed-fill::before { content: "\f6c6"; } -.bi-ticket-detailed::before { content: "\f6c7"; } -.bi-ticket-fill::before { content: "\f6c8"; } -.bi-ticket-perforated-fill::before { content: "\f6c9"; } -.bi-ticket-perforated::before { content: "\f6ca"; } -.bi-ticket::before { content: "\f6cb"; } -.bi-tiktok::before { content: "\f6cc"; } -.bi-window-dash::before { content: "\f6cd"; } -.bi-window-desktop::before { content: "\f6ce"; } -.bi-window-fullscreen::before { content: "\f6cf"; } -.bi-window-plus::before { content: "\f6d0"; } -.bi-window-split::before { content: "\f6d1"; } -.bi-window-stack::before { content: "\f6d2"; } -.bi-window-x::before { content: "\f6d3"; } -.bi-xbox::before { content: "\f6d4"; } -.bi-ethernet::before { content: "\f6d5"; } -.bi-hdmi-fill::before { content: "\f6d6"; } -.bi-hdmi::before { content: "\f6d7"; } -.bi-usb-c-fill::before { content: "\f6d8"; } -.bi-usb-c::before { content: "\f6d9"; } -.bi-usb-fill::before { content: "\f6da"; } -.bi-usb-plug-fill::before { content: "\f6db"; } -.bi-usb-plug::before { content: "\f6dc"; } -.bi-usb-symbol::before { content: "\f6dd"; } -.bi-usb::before { content: "\f6de"; } -.bi-boombox-fill::before { content: "\f6df"; } -.bi-displayport::before { content: "\f6e1"; } -.bi-gpu-card::before { content: "\f6e2"; } -.bi-memory::before { content: "\f6e3"; } -.bi-modem-fill::before { content: "\f6e4"; } -.bi-modem::before { content: "\f6e5"; } -.bi-motherboard-fill::before { content: "\f6e6"; } -.bi-motherboard::before { content: "\f6e7"; } -.bi-optical-audio-fill::before { content: "\f6e8"; } -.bi-optical-audio::before { content: "\f6e9"; } -.bi-pci-card::before { content: "\f6ea"; } -.bi-router-fill::before { content: "\f6eb"; } -.bi-router::before { content: "\f6ec"; } -.bi-thunderbolt-fill::before { content: "\f6ef"; } -.bi-thunderbolt::before { content: "\f6f0"; } -.bi-usb-drive-fill::before { content: "\f6f1"; } -.bi-usb-drive::before { content: "\f6f2"; } -.bi-usb-micro-fill::before { content: "\f6f3"; } -.bi-usb-micro::before { content: "\f6f4"; } -.bi-usb-mini-fill::before { content: "\f6f5"; } -.bi-usb-mini::before { content: "\f6f6"; } -.bi-cloud-haze2::before { content: "\f6f7"; } -.bi-device-hdd-fill::before { content: "\f6f8"; } -.bi-device-hdd::before { content: "\f6f9"; } -.bi-device-ssd-fill::before { content: "\f6fa"; } -.bi-device-ssd::before { content: "\f6fb"; } -.bi-displayport-fill::before { content: "\f6fc"; } -.bi-mortarboard-fill::before { content: "\f6fd"; } -.bi-mortarboard::before { content: "\f6fe"; } -.bi-terminal-x::before { content: "\f6ff"; } -.bi-arrow-through-heart-fill::before { content: "\f700"; } -.bi-arrow-through-heart::before { content: "\f701"; } -.bi-badge-sd-fill::before { content: "\f702"; } -.bi-badge-sd::before { content: "\f703"; } -.bi-bag-heart-fill::before { content: "\f704"; } -.bi-bag-heart::before { content: "\f705"; } -.bi-balloon-fill::before { content: "\f706"; } -.bi-balloon-heart-fill::before { content: "\f707"; } -.bi-balloon-heart::before { content: "\f708"; } -.bi-balloon::before { content: "\f709"; } -.bi-box2-fill::before { content: "\f70a"; } -.bi-box2-heart-fill::before { content: "\f70b"; } -.bi-box2-heart::before { content: "\f70c"; } -.bi-box2::before { content: "\f70d"; } -.bi-braces-asterisk::before { content: "\f70e"; } -.bi-calendar-heart-fill::before { content: "\f70f"; } -.bi-calendar-heart::before { content: "\f710"; } -.bi-calendar2-heart-fill::before { content: "\f711"; } -.bi-calendar2-heart::before { content: "\f712"; } -.bi-chat-heart-fill::before { content: "\f713"; } -.bi-chat-heart::before { content: "\f714"; } -.bi-chat-left-heart-fill::before { content: "\f715"; } -.bi-chat-left-heart::before { content: "\f716"; } -.bi-chat-right-heart-fill::before { content: "\f717"; } -.bi-chat-right-heart::before { content: "\f718"; } -.bi-chat-square-heart-fill::before { content: "\f719"; } -.bi-chat-square-heart::before { content: "\f71a"; } -.bi-clipboard-check-fill::before { content: "\f71b"; } -.bi-clipboard-data-fill::before { content: "\f71c"; } -.bi-clipboard-fill::before { content: "\f71d"; } -.bi-clipboard-heart-fill::before { content: "\f71e"; } -.bi-clipboard-heart::before { content: "\f71f"; } -.bi-clipboard-minus-fill::before { content: "\f720"; } -.bi-clipboard-plus-fill::before { content: "\f721"; } -.bi-clipboard-pulse::before { content: "\f722"; } -.bi-clipboard-x-fill::before { content: "\f723"; } -.bi-clipboard2-check-fill::before { content: "\f724"; } -.bi-clipboard2-check::before { content: "\f725"; } -.bi-clipboard2-data-fill::before { content: "\f726"; } -.bi-clipboard2-data::before { content: "\f727"; } -.bi-clipboard2-fill::before { content: "\f728"; } -.bi-clipboard2-heart-fill::before { content: "\f729"; } -.bi-clipboard2-heart::before { content: "\f72a"; } -.bi-clipboard2-minus-fill::before { content: "\f72b"; } -.bi-clipboard2-minus::before { content: "\f72c"; } -.bi-clipboard2-plus-fill::before { content: "\f72d"; } -.bi-clipboard2-plus::before { content: "\f72e"; } -.bi-clipboard2-pulse-fill::before { content: "\f72f"; } -.bi-clipboard2-pulse::before { content: "\f730"; } -.bi-clipboard2-x-fill::before { content: "\f731"; } -.bi-clipboard2-x::before { content: "\f732"; } -.bi-clipboard2::before { content: "\f733"; } -.bi-emoji-kiss-fill::before { content: "\f734"; } -.bi-emoji-kiss::before { content: "\f735"; } -.bi-envelope-heart-fill::before { content: "\f736"; } -.bi-envelope-heart::before { content: "\f737"; } -.bi-envelope-open-heart-fill::before { content: "\f738"; } -.bi-envelope-open-heart::before { content: "\f739"; } -.bi-envelope-paper-fill::before { content: "\f73a"; } -.bi-envelope-paper-heart-fill::before { content: "\f73b"; } -.bi-envelope-paper-heart::before { content: "\f73c"; } -.bi-envelope-paper::before { content: "\f73d"; } -.bi-filetype-aac::before { content: "\f73e"; } -.bi-filetype-ai::before { content: "\f73f"; } -.bi-filetype-bmp::before { content: "\f740"; } -.bi-filetype-cs::before { content: "\f741"; } -.bi-filetype-css::before { content: "\f742"; } -.bi-filetype-csv::before { content: "\f743"; } -.bi-filetype-doc::before { content: "\f744"; } -.bi-filetype-docx::before { content: "\f745"; } -.bi-filetype-exe::before { content: "\f746"; } -.bi-filetype-gif::before { content: "\f747"; } -.bi-filetype-heic::before { content: "\f748"; } -.bi-filetype-html::before { content: "\f749"; } -.bi-filetype-java::before { content: "\f74a"; } -.bi-filetype-jpg::before { content: "\f74b"; } -.bi-filetype-js::before { content: "\f74c"; } -.bi-filetype-jsx::before { content: "\f74d"; } -.bi-filetype-key::before { content: "\f74e"; } -.bi-filetype-m4p::before { content: "\f74f"; } -.bi-filetype-md::before { content: "\f750"; } -.bi-filetype-mdx::before { content: "\f751"; } -.bi-filetype-mov::before { content: "\f752"; } -.bi-filetype-mp3::before { content: "\f753"; } -.bi-filetype-mp4::before { content: "\f754"; } -.bi-filetype-otf::before { content: "\f755"; } -.bi-filetype-pdf::before { content: "\f756"; } -.bi-filetype-php::before { content: "\f757"; } -.bi-filetype-png::before { content: "\f758"; } -.bi-filetype-ppt::before { content: "\f75a"; } -.bi-filetype-psd::before { content: "\f75b"; } -.bi-filetype-py::before { content: "\f75c"; } -.bi-filetype-raw::before { content: "\f75d"; } -.bi-filetype-rb::before { content: "\f75e"; } -.bi-filetype-sass::before { content: "\f75f"; } -.bi-filetype-scss::before { content: "\f760"; } -.bi-filetype-sh::before { content: "\f761"; } -.bi-filetype-svg::before { content: "\f762"; } -.bi-filetype-tiff::before { content: "\f763"; } -.bi-filetype-tsx::before { content: "\f764"; } -.bi-filetype-ttf::before { content: "\f765"; } -.bi-filetype-txt::before { content: "\f766"; } -.bi-filetype-wav::before { content: "\f767"; } -.bi-filetype-woff::before { content: "\f768"; } -.bi-filetype-xls::before { content: "\f76a"; } -.bi-filetype-xml::before { content: "\f76b"; } -.bi-filetype-yml::before { content: "\f76c"; } -.bi-heart-arrow::before { content: "\f76d"; } -.bi-heart-pulse-fill::before { content: "\f76e"; } -.bi-heart-pulse::before { content: "\f76f"; } -.bi-heartbreak-fill::before { content: "\f770"; } -.bi-heartbreak::before { content: "\f771"; } -.bi-hearts::before { content: "\f772"; } -.bi-hospital-fill::before { content: "\f773"; } -.bi-hospital::before { content: "\f774"; } -.bi-house-heart-fill::before { content: "\f775"; } -.bi-house-heart::before { content: "\f776"; } -.bi-incognito::before { content: "\f777"; } -.bi-magnet-fill::before { content: "\f778"; } -.bi-magnet::before { content: "\f779"; } -.bi-person-heart::before { content: "\f77a"; } -.bi-person-hearts::before { content: "\f77b"; } -.bi-phone-flip::before { content: "\f77c"; } -.bi-plugin::before { content: "\f77d"; } -.bi-postage-fill::before { content: "\f77e"; } -.bi-postage-heart-fill::before { content: "\f77f"; } -.bi-postage-heart::before { content: "\f780"; } -.bi-postage::before { content: "\f781"; } -.bi-postcard-fill::before { content: "\f782"; } -.bi-postcard-heart-fill::before { content: "\f783"; } -.bi-postcard-heart::before { content: "\f784"; } -.bi-postcard::before { content: "\f785"; } -.bi-search-heart-fill::before { content: "\f786"; } -.bi-search-heart::before { content: "\f787"; } -.bi-sliders2-vertical::before { content: "\f788"; } -.bi-sliders2::before { content: "\f789"; } -.bi-trash3-fill::before { content: "\f78a"; } -.bi-trash3::before { content: "\f78b"; } -.bi-valentine::before { content: "\f78c"; } -.bi-valentine2::before { content: "\f78d"; } -.bi-wrench-adjustable-circle-fill::before { content: "\f78e"; } -.bi-wrench-adjustable-circle::before { content: "\f78f"; } -.bi-wrench-adjustable::before { content: "\f790"; } -.bi-filetype-json::before { content: "\f791"; } -.bi-filetype-pptx::before { content: "\f792"; } -.bi-filetype-xlsx::before { content: "\f793"; } -.bi-1-circle-fill::before { content: "\f796"; } -.bi-1-circle::before { content: "\f797"; } -.bi-1-square-fill::before { content: "\f798"; } -.bi-1-square::before { content: "\f799"; } -.bi-2-circle-fill::before { content: "\f79c"; } -.bi-2-circle::before { content: "\f79d"; } -.bi-2-square-fill::before { content: "\f79e"; } -.bi-2-square::before { content: "\f79f"; } -.bi-3-circle-fill::before { content: "\f7a2"; } -.bi-3-circle::before { content: "\f7a3"; } -.bi-3-square-fill::before { content: "\f7a4"; } -.bi-3-square::before { content: "\f7a5"; } -.bi-4-circle-fill::before { content: "\f7a8"; } -.bi-4-circle::before { content: "\f7a9"; } -.bi-4-square-fill::before { content: "\f7aa"; } -.bi-4-square::before { content: "\f7ab"; } -.bi-5-circle-fill::before { content: "\f7ae"; } -.bi-5-circle::before { content: "\f7af"; } -.bi-5-square-fill::before { content: "\f7b0"; } -.bi-5-square::before { content: "\f7b1"; } -.bi-6-circle-fill::before { content: "\f7b4"; } -.bi-6-circle::before { content: "\f7b5"; } -.bi-6-square-fill::before { content: "\f7b6"; } -.bi-6-square::before { content: "\f7b7"; } -.bi-7-circle-fill::before { content: "\f7ba"; } -.bi-7-circle::before { content: "\f7bb"; } -.bi-7-square-fill::before { content: "\f7bc"; } -.bi-7-square::before { content: "\f7bd"; } -.bi-8-circle-fill::before { content: "\f7c0"; } -.bi-8-circle::before { content: "\f7c1"; } -.bi-8-square-fill::before { content: "\f7c2"; } -.bi-8-square::before { content: "\f7c3"; } -.bi-9-circle-fill::before { content: "\f7c6"; } -.bi-9-circle::before { content: "\f7c7"; } -.bi-9-square-fill::before { content: "\f7c8"; } -.bi-9-square::before { content: "\f7c9"; } -.bi-airplane-engines-fill::before { content: "\f7ca"; } -.bi-airplane-engines::before { content: "\f7cb"; } -.bi-airplane-fill::before { content: "\f7cc"; } -.bi-airplane::before { content: "\f7cd"; } -.bi-alexa::before { content: "\f7ce"; } -.bi-alipay::before { content: "\f7cf"; } -.bi-android::before { content: "\f7d0"; } -.bi-android2::before { content: "\f7d1"; } -.bi-box-fill::before { content: "\f7d2"; } -.bi-box-seam-fill::before { content: "\f7d3"; } -.bi-browser-chrome::before { content: "\f7d4"; } -.bi-browser-edge::before { content: "\f7d5"; } -.bi-browser-firefox::before { content: "\f7d6"; } -.bi-browser-safari::before { content: "\f7d7"; } -.bi-c-circle-fill::before { content: "\f7da"; } -.bi-c-circle::before { content: "\f7db"; } -.bi-c-square-fill::before { content: "\f7dc"; } -.bi-c-square::before { content: "\f7dd"; } -.bi-capsule-pill::before { content: "\f7de"; } -.bi-capsule::before { content: "\f7df"; } -.bi-car-front-fill::before { content: "\f7e0"; } -.bi-car-front::before { content: "\f7e1"; } -.bi-cassette-fill::before { content: "\f7e2"; } -.bi-cassette::before { content: "\f7e3"; } -.bi-cc-circle-fill::before { content: "\f7e6"; } -.bi-cc-circle::before { content: "\f7e7"; } -.bi-cc-square-fill::before { content: "\f7e8"; } -.bi-cc-square::before { content: "\f7e9"; } -.bi-cup-hot-fill::before { content: "\f7ea"; } -.bi-cup-hot::before { content: "\f7eb"; } -.bi-currency-rupee::before { content: "\f7ec"; } -.bi-dropbox::before { content: "\f7ed"; } -.bi-escape::before { content: "\f7ee"; } -.bi-fast-forward-btn-fill::before { content: "\f7ef"; } -.bi-fast-forward-btn::before { content: "\f7f0"; } -.bi-fast-forward-circle-fill::before { content: "\f7f1"; } -.bi-fast-forward-circle::before { content: "\f7f2"; } -.bi-fast-forward-fill::before { content: "\f7f3"; } -.bi-fast-forward::before { content: "\f7f4"; } -.bi-filetype-sql::before { content: "\f7f5"; } -.bi-fire::before { content: "\f7f6"; } -.bi-google-play::before { content: "\f7f7"; } -.bi-h-circle-fill::before { content: "\f7fa"; } -.bi-h-circle::before { content: "\f7fb"; } -.bi-h-square-fill::before { content: "\f7fc"; } -.bi-h-square::before { content: "\f7fd"; } -.bi-indent::before { content: "\f7fe"; } -.bi-lungs-fill::before { content: "\f7ff"; } -.bi-lungs::before { content: "\f800"; } -.bi-microsoft-teams::before { content: "\f801"; } -.bi-p-circle-fill::before { content: "\f804"; } -.bi-p-circle::before { content: "\f805"; } -.bi-p-square-fill::before { content: "\f806"; } -.bi-p-square::before { content: "\f807"; } -.bi-pass-fill::before { content: "\f808"; } -.bi-pass::before { content: "\f809"; } -.bi-prescription::before { content: "\f80a"; } -.bi-prescription2::before { content: "\f80b"; } -.bi-r-circle-fill::before { content: "\f80e"; } -.bi-r-circle::before { content: "\f80f"; } -.bi-r-square-fill::before { content: "\f810"; } -.bi-r-square::before { content: "\f811"; } -.bi-repeat-1::before { content: "\f812"; } -.bi-repeat::before { content: "\f813"; } -.bi-rewind-btn-fill::before { content: "\f814"; } -.bi-rewind-btn::before { content: "\f815"; } -.bi-rewind-circle-fill::before { content: "\f816"; } -.bi-rewind-circle::before { content: "\f817"; } -.bi-rewind-fill::before { content: "\f818"; } -.bi-rewind::before { content: "\f819"; } -.bi-train-freight-front-fill::before { content: "\f81a"; } -.bi-train-freight-front::before { content: "\f81b"; } -.bi-train-front-fill::before { content: "\f81c"; } -.bi-train-front::before { content: "\f81d"; } -.bi-train-lightrail-front-fill::before { content: "\f81e"; } -.bi-train-lightrail-front::before { content: "\f81f"; } -.bi-truck-front-fill::before { content: "\f820"; } -.bi-truck-front::before { content: "\f821"; } -.bi-ubuntu::before { content: "\f822"; } -.bi-unindent::before { content: "\f823"; } -.bi-unity::before { content: "\f824"; } -.bi-universal-access-circle::before { content: "\f825"; } -.bi-universal-access::before { content: "\f826"; } -.bi-virus::before { content: "\f827"; } -.bi-virus2::before { content: "\f828"; } -.bi-wechat::before { content: "\f829"; } -.bi-yelp::before { content: "\f82a"; } -.bi-sign-stop-fill::before { content: "\f82b"; } -.bi-sign-stop-lights-fill::before { content: "\f82c"; } -.bi-sign-stop-lights::before { content: "\f82d"; } -.bi-sign-stop::before { content: "\f82e"; } -.bi-sign-turn-left-fill::before { content: "\f82f"; } -.bi-sign-turn-left::before { content: "\f830"; } -.bi-sign-turn-right-fill::before { content: "\f831"; } -.bi-sign-turn-right::before { content: "\f832"; } -.bi-sign-turn-slight-left-fill::before { content: "\f833"; } -.bi-sign-turn-slight-left::before { content: "\f834"; } -.bi-sign-turn-slight-right-fill::before { content: "\f835"; } -.bi-sign-turn-slight-right::before { content: "\f836"; } -.bi-sign-yield-fill::before { content: "\f837"; } -.bi-sign-yield::before { content: "\f838"; } -.bi-ev-station-fill::before { content: "\f839"; } -.bi-ev-station::before { content: "\f83a"; } -.bi-fuel-pump-diesel-fill::before { content: "\f83b"; } -.bi-fuel-pump-diesel::before { content: "\f83c"; } -.bi-fuel-pump-fill::before { content: "\f83d"; } -.bi-fuel-pump::before { content: "\f83e"; } -.bi-0-circle-fill::before { content: "\f83f"; } -.bi-0-circle::before { content: "\f840"; } -.bi-0-square-fill::before { content: "\f841"; } -.bi-0-square::before { content: "\f842"; } -.bi-rocket-fill::before { content: "\f843"; } -.bi-rocket-takeoff-fill::before { content: "\f844"; } -.bi-rocket-takeoff::before { content: "\f845"; } -.bi-rocket::before { content: "\f846"; } -.bi-stripe::before { content: "\f847"; } -.bi-subscript::before { content: "\f848"; } -.bi-superscript::before { content: "\f849"; } -.bi-trello::before { content: "\f84a"; } -.bi-envelope-at-fill::before { content: "\f84b"; } -.bi-envelope-at::before { content: "\f84c"; } -.bi-regex::before { content: "\f84d"; } -.bi-text-wrap::before { content: "\f84e"; } -.bi-sign-dead-end-fill::before { content: "\f84f"; } -.bi-sign-dead-end::before { content: "\f850"; } -.bi-sign-do-not-enter-fill::before { content: "\f851"; } -.bi-sign-do-not-enter::before { content: "\f852"; } -.bi-sign-intersection-fill::before { content: "\f853"; } -.bi-sign-intersection-side-fill::before { content: "\f854"; } -.bi-sign-intersection-side::before { content: "\f855"; } -.bi-sign-intersection-t-fill::before { content: "\f856"; } -.bi-sign-intersection-t::before { content: "\f857"; } -.bi-sign-intersection-y-fill::before { content: "\f858"; } -.bi-sign-intersection-y::before { content: "\f859"; } -.bi-sign-intersection::before { content: "\f85a"; } -.bi-sign-merge-left-fill::before { content: "\f85b"; } -.bi-sign-merge-left::before { content: "\f85c"; } -.bi-sign-merge-right-fill::before { content: "\f85d"; } -.bi-sign-merge-right::before { content: "\f85e"; } -.bi-sign-no-left-turn-fill::before { content: "\f85f"; } -.bi-sign-no-left-turn::before { content: "\f860"; } -.bi-sign-no-parking-fill::before { content: "\f861"; } -.bi-sign-no-parking::before { content: "\f862"; } -.bi-sign-no-right-turn-fill::before { content: "\f863"; } -.bi-sign-no-right-turn::before { content: "\f864"; } -.bi-sign-railroad-fill::before { content: "\f865"; } -.bi-sign-railroad::before { content: "\f866"; } -.bi-building-add::before { content: "\f867"; } -.bi-building-check::before { content: "\f868"; } -.bi-building-dash::before { content: "\f869"; } -.bi-building-down::before { content: "\f86a"; } -.bi-building-exclamation::before { content: "\f86b"; } -.bi-building-fill-add::before { content: "\f86c"; } -.bi-building-fill-check::before { content: "\f86d"; } -.bi-building-fill-dash::before { content: "\f86e"; } -.bi-building-fill-down::before { content: "\f86f"; } -.bi-building-fill-exclamation::before { content: "\f870"; } -.bi-building-fill-gear::before { content: "\f871"; } -.bi-building-fill-lock::before { content: "\f872"; } -.bi-building-fill-slash::before { content: "\f873"; } -.bi-building-fill-up::before { content: "\f874"; } -.bi-building-fill-x::before { content: "\f875"; } -.bi-building-fill::before { content: "\f876"; } -.bi-building-gear::before { content: "\f877"; } -.bi-building-lock::before { content: "\f878"; } -.bi-building-slash::before { content: "\f879"; } -.bi-building-up::before { content: "\f87a"; } -.bi-building-x::before { content: "\f87b"; } -.bi-buildings-fill::before { content: "\f87c"; } -.bi-buildings::before { content: "\f87d"; } -.bi-bus-front-fill::before { content: "\f87e"; } -.bi-bus-front::before { content: "\f87f"; } -.bi-ev-front-fill::before { content: "\f880"; } -.bi-ev-front::before { content: "\f881"; } -.bi-globe-americas::before { content: "\f882"; } -.bi-globe-asia-australia::before { content: "\f883"; } -.bi-globe-central-south-asia::before { content: "\f884"; } -.bi-globe-europe-africa::before { content: "\f885"; } -.bi-house-add-fill::before { content: "\f886"; } -.bi-house-add::before { content: "\f887"; } -.bi-house-check-fill::before { content: "\f888"; } -.bi-house-check::before { content: "\f889"; } -.bi-house-dash-fill::before { content: "\f88a"; } -.bi-house-dash::before { content: "\f88b"; } -.bi-house-down-fill::before { content: "\f88c"; } -.bi-house-down::before { content: "\f88d"; } -.bi-house-exclamation-fill::before { content: "\f88e"; } -.bi-house-exclamation::before { content: "\f88f"; } -.bi-house-gear-fill::before { content: "\f890"; } -.bi-house-gear::before { content: "\f891"; } -.bi-house-lock-fill::before { content: "\f892"; } -.bi-house-lock::before { content: "\f893"; } -.bi-house-slash-fill::before { content: "\f894"; } -.bi-house-slash::before { content: "\f895"; } -.bi-house-up-fill::before { content: "\f896"; } -.bi-house-up::before { content: "\f897"; } -.bi-house-x-fill::before { content: "\f898"; } -.bi-house-x::before { content: "\f899"; } -.bi-person-add::before { content: "\f89a"; } -.bi-person-down::before { content: "\f89b"; } -.bi-person-exclamation::before { content: "\f89c"; } -.bi-person-fill-add::before { content: "\f89d"; } -.bi-person-fill-check::before { content: "\f89e"; } -.bi-person-fill-dash::before { content: "\f89f"; } -.bi-person-fill-down::before { content: "\f8a0"; } -.bi-person-fill-exclamation::before { content: "\f8a1"; } -.bi-person-fill-gear::before { content: "\f8a2"; } -.bi-person-fill-lock::before { content: "\f8a3"; } -.bi-person-fill-slash::before { content: "\f8a4"; } -.bi-person-fill-up::before { content: "\f8a5"; } -.bi-person-fill-x::before { content: "\f8a6"; } -.bi-person-gear::before { content: "\f8a7"; } -.bi-person-lock::before { content: "\f8a8"; } -.bi-person-slash::before { content: "\f8a9"; } -.bi-person-up::before { content: "\f8aa"; } -.bi-scooter::before { content: "\f8ab"; } -.bi-taxi-front-fill::before { content: "\f8ac"; } -.bi-taxi-front::before { content: "\f8ad"; } -.bi-amd::before { content: "\f8ae"; } -.bi-database-add::before { content: "\f8af"; } -.bi-database-check::before { content: "\f8b0"; } -.bi-database-dash::before { content: "\f8b1"; } -.bi-database-down::before { content: "\f8b2"; } -.bi-database-exclamation::before { content: "\f8b3"; } -.bi-database-fill-add::before { content: "\f8b4"; } -.bi-database-fill-check::before { content: "\f8b5"; } -.bi-database-fill-dash::before { content: "\f8b6"; } -.bi-database-fill-down::before { content: "\f8b7"; } -.bi-database-fill-exclamation::before { content: "\f8b8"; } -.bi-database-fill-gear::before { content: "\f8b9"; } -.bi-database-fill-lock::before { content: "\f8ba"; } -.bi-database-fill-slash::before { content: "\f8bb"; } -.bi-database-fill-up::before { content: "\f8bc"; } -.bi-database-fill-x::before { content: "\f8bd"; } -.bi-database-fill::before { content: "\f8be"; } -.bi-database-gear::before { content: "\f8bf"; } -.bi-database-lock::before { content: "\f8c0"; } -.bi-database-slash::before { content: "\f8c1"; } -.bi-database-up::before { content: "\f8c2"; } -.bi-database-x::before { content: "\f8c3"; } -.bi-database::before { content: "\f8c4"; } -.bi-houses-fill::before { content: "\f8c5"; } -.bi-houses::before { content: "\f8c6"; } -.bi-nvidia::before { content: "\f8c7"; } -.bi-person-vcard-fill::before { content: "\f8c8"; } -.bi-person-vcard::before { content: "\f8c9"; } -.bi-sina-weibo::before { content: "\f8ca"; } -.bi-tencent-qq::before { content: "\f8cb"; } -.bi-wikipedia::before { content: "\f8cc"; } -.bi-alphabet-uppercase::before { content: "\f2a5"; } -.bi-alphabet::before { content: "\f68a"; } -.bi-amazon::before { content: "\f68d"; } -.bi-arrows-collapse-vertical::before { content: "\f690"; } -.bi-arrows-expand-vertical::before { content: "\f695"; } -.bi-arrows-vertical::before { content: "\f698"; } -.bi-arrows::before { content: "\f6a2"; } -.bi-ban-fill::before { content: "\f6a3"; } -.bi-ban::before { content: "\f6b6"; } -.bi-bing::before { content: "\f6c2"; } -.bi-cake::before { content: "\f6e0"; } -.bi-cake2::before { content: "\f6ed"; } -.bi-cookie::before { content: "\f6ee"; } -.bi-copy::before { content: "\f759"; } -.bi-crosshair::before { content: "\f769"; } -.bi-crosshair2::before { content: "\f794"; } -.bi-emoji-astonished-fill::before { content: "\f795"; } -.bi-emoji-astonished::before { content: "\f79a"; } -.bi-emoji-grimace-fill::before { content: "\f79b"; } -.bi-emoji-grimace::before { content: "\f7a0"; } -.bi-emoji-grin-fill::before { content: "\f7a1"; } -.bi-emoji-grin::before { content: "\f7a6"; } -.bi-emoji-surprise-fill::before { content: "\f7a7"; } -.bi-emoji-surprise::before { content: "\f7ac"; } -.bi-emoji-tear-fill::before { content: "\f7ad"; } -.bi-emoji-tear::before { content: "\f7b2"; } -.bi-envelope-arrow-down-fill::before { content: "\f7b3"; } -.bi-envelope-arrow-down::before { content: "\f7b8"; } -.bi-envelope-arrow-up-fill::before { content: "\f7b9"; } -.bi-envelope-arrow-up::before { content: "\f7be"; } -.bi-feather::before { content: "\f7bf"; } -.bi-feather2::before { content: "\f7c4"; } -.bi-floppy-fill::before { content: "\f7c5"; } -.bi-floppy::before { content: "\f7d8"; } -.bi-floppy2-fill::before { content: "\f7d9"; } -.bi-floppy2::before { content: "\f7e4"; } -.bi-gitlab::before { content: "\f7e5"; } -.bi-highlighter::before { content: "\f7f8"; } -.bi-marker-tip::before { content: "\f802"; } -.bi-nvme-fill::before { content: "\f803"; } -.bi-nvme::before { content: "\f80c"; } -.bi-opencollective::before { content: "\f80d"; } -.bi-pci-card-network::before { content: "\f8cd"; } -.bi-pci-card-sound::before { content: "\f8ce"; } -.bi-radar::before { content: "\f8cf"; } -.bi-send-arrow-down-fill::before { content: "\f8d0"; } -.bi-send-arrow-down::before { content: "\f8d1"; } -.bi-send-arrow-up-fill::before { content: "\f8d2"; } -.bi-send-arrow-up::before { content: "\f8d3"; } -.bi-sim-slash-fill::before { content: "\f8d4"; } -.bi-sim-slash::before { content: "\f8d5"; } -.bi-sourceforge::before { content: "\f8d6"; } -.bi-substack::before { content: "\f8d7"; } -.bi-threads-fill::before { content: "\f8d8"; } -.bi-threads::before { content: "\f8d9"; } -.bi-transparency::before { content: "\f8da"; } -.bi-twitter-x::before { content: "\f8db"; } -.bi-type-h4::before { content: "\f8dc"; } -.bi-type-h5::before { content: "\f8dd"; } -.bi-type-h6::before { content: "\f8de"; } -.bi-backpack-fill::before { content: "\f8df"; } -.bi-backpack::before { content: "\f8e0"; } -.bi-backpack2-fill::before { content: "\f8e1"; } -.bi-backpack2::before { content: "\f8e2"; } -.bi-backpack3-fill::before { content: "\f8e3"; } -.bi-backpack3::before { content: "\f8e4"; } -.bi-backpack4-fill::before { content: "\f8e5"; } -.bi-backpack4::before { content: "\f8e6"; } -.bi-brilliance::before { content: "\f8e7"; } -.bi-cake-fill::before { content: "\f8e8"; } -.bi-cake2-fill::before { content: "\f8e9"; } -.bi-duffle-fill::before { content: "\f8ea"; } -.bi-duffle::before { content: "\f8eb"; } -.bi-exposure::before { content: "\f8ec"; } -.bi-gender-neuter::before { content: "\f8ed"; } -.bi-highlights::before { content: "\f8ee"; } -.bi-luggage-fill::before { content: "\f8ef"; } -.bi-luggage::before { content: "\f8f0"; } -.bi-mailbox-flag::before { content: "\f8f1"; } -.bi-mailbox2-flag::before { content: "\f8f2"; } -.bi-noise-reduction::before { content: "\f8f3"; } -.bi-passport-fill::before { content: "\f8f4"; } -.bi-passport::before { content: "\f8f5"; } -.bi-person-arms-up::before { content: "\f8f6"; } -.bi-person-raised-hand::before { content: "\f8f7"; } -.bi-person-standing-dress::before { content: "\f8f8"; } -.bi-person-standing::before { content: "\f8f9"; } -.bi-person-walking::before { content: "\f8fa"; } -.bi-person-wheelchair::before { content: "\f8fb"; } -.bi-shadows::before { content: "\f8fc"; } -.bi-suitcase-fill::before { content: "\f8fd"; } -.bi-suitcase-lg-fill::before { content: "\f8fe"; } -.bi-suitcase-lg::before { content: "\f8ff"; } -.bi-suitcase::before { content: "\f900"; } -.bi-suitcase2-fill::before { content: "\f901"; } -.bi-suitcase2::before { content: "\f902"; } -.bi-vignette::before { content: "\f903"; } -.bi-bluesky::before { content: "\f7f9"; } -.bi-tux::before { content: "\f904"; } -.bi-beaker-fill::before { content: "\f905"; } -.bi-beaker::before { content: "\f906"; } -.bi-flask-fill::before { content: "\f907"; } -.bi-flask-florence-fill::before { content: "\f908"; } -.bi-flask-florence::before { content: "\f909"; } -.bi-flask::before { content: "\f90a"; } -.bi-leaf-fill::before { content: "\f90b"; } -.bi-leaf::before { content: "\f90c"; } -.bi-measuring-cup-fill::before { content: "\f90d"; } -.bi-measuring-cup::before { content: "\f90e"; } -.bi-unlock2-fill::before { content: "\f90f"; } -.bi-unlock2::before { content: "\f910"; } -.bi-battery-low::before { content: "\f911"; } -.bi-anthropic::before { content: "\f912"; } -.bi-apple-music::before { content: "\f913"; } -.bi-claude::before { content: "\f914"; } -.bi-openai::before { content: "\f915"; } -.bi-perplexity::before { content: "\f916"; } -.bi-css::before { content: "\f917"; } -.bi-javascript::before { content: "\f918"; } -.bi-typescript::before { content: "\f919"; } -.bi-fork-knife::before { content: "\f91a"; } -.bi-globe-americas-fill::before { content: "\f91b"; } -.bi-globe-asia-australia-fill::before { content: "\f91c"; } -.bi-globe-central-south-asia-fill::before { content: "\f91d"; } -.bi-globe-europe-africa-fill::before { content: "\f91e"; } diff --git a/extensions/pagetop-bootsier/assets/bootstrap-icons-1.13.1/bootstrap-icons.json b/extensions/pagetop-bootsier/assets/bootstrap-icons-1.13.1/bootstrap-icons.json deleted file mode 100644 index 9d8873b1..00000000 --- a/extensions/pagetop-bootsier/assets/bootstrap-icons-1.13.1/bootstrap-icons.json +++ /dev/null @@ -1,2080 +0,0 @@ -{ - "123": 63103, - "alarm-fill": 61697, - "alarm": 61698, - "align-bottom": 61699, - "align-center": 61700, - "align-end": 61701, - "align-middle": 61702, - "align-start": 61703, - "align-top": 61704, - "alt": 61705, - "app-indicator": 61706, - "app": 61707, - "archive-fill": 61708, - "archive": 61709, - "arrow-90deg-down": 61710, - "arrow-90deg-left": 61711, - "arrow-90deg-right": 61712, - "arrow-90deg-up": 61713, - "arrow-bar-down": 61714, - "arrow-bar-left": 61715, - "arrow-bar-right": 61716, - "arrow-bar-up": 61717, - "arrow-clockwise": 61718, - "arrow-counterclockwise": 61719, - "arrow-down-circle-fill": 61720, - "arrow-down-circle": 61721, - "arrow-down-left-circle-fill": 61722, - "arrow-down-left-circle": 61723, - "arrow-down-left-square-fill": 61724, - "arrow-down-left-square": 61725, - "arrow-down-left": 61726, - "arrow-down-right-circle-fill": 61727, - "arrow-down-right-circle": 61728, - "arrow-down-right-square-fill": 61729, - "arrow-down-right-square": 61730, - "arrow-down-right": 61731, - "arrow-down-short": 61732, - "arrow-down-square-fill": 61733, - "arrow-down-square": 61734, - "arrow-down-up": 61735, - "arrow-down": 61736, - "arrow-left-circle-fill": 61737, - "arrow-left-circle": 61738, - "arrow-left-right": 61739, - "arrow-left-short": 61740, - "arrow-left-square-fill": 61741, - "arrow-left-square": 61742, - "arrow-left": 61743, - "arrow-repeat": 61744, - "arrow-return-left": 61745, - "arrow-return-right": 61746, - "arrow-right-circle-fill": 61747, - "arrow-right-circle": 61748, - "arrow-right-short": 61749, - "arrow-right-square-fill": 61750, - "arrow-right-square": 61751, - "arrow-right": 61752, - "arrow-up-circle-fill": 61753, - "arrow-up-circle": 61754, - "arrow-up-left-circle-fill": 61755, - "arrow-up-left-circle": 61756, - "arrow-up-left-square-fill": 61757, - "arrow-up-left-square": 61758, - "arrow-up-left": 61759, - "arrow-up-right-circle-fill": 61760, - "arrow-up-right-circle": 61761, - "arrow-up-right-square-fill": 61762, - "arrow-up-right-square": 61763, - "arrow-up-right": 61764, - "arrow-up-short": 61765, - "arrow-up-square-fill": 61766, - "arrow-up-square": 61767, - "arrow-up": 61768, - "arrows-angle-contract": 61769, - "arrows-angle-expand": 61770, - "arrows-collapse": 61771, - "arrows-expand": 61772, - "arrows-fullscreen": 61773, - "arrows-move": 61774, - "aspect-ratio-fill": 61775, - "aspect-ratio": 61776, - "asterisk": 61777, - "at": 61778, - "award-fill": 61779, - "award": 61780, - "back": 61781, - "backspace-fill": 61782, - "backspace-reverse-fill": 61783, - "backspace-reverse": 61784, - "backspace": 61785, - "badge-3d-fill": 61786, - "badge-3d": 61787, - "badge-4k-fill": 61788, - "badge-4k": 61789, - "badge-8k-fill": 61790, - "badge-8k": 61791, - "badge-ad-fill": 61792, - "badge-ad": 61793, - "badge-ar-fill": 61794, - "badge-ar": 61795, - "badge-cc-fill": 61796, - "badge-cc": 61797, - "badge-hd-fill": 61798, - "badge-hd": 61799, - "badge-tm-fill": 61800, - "badge-tm": 61801, - "badge-vo-fill": 61802, - "badge-vo": 61803, - "badge-vr-fill": 61804, - "badge-vr": 61805, - "badge-wc-fill": 61806, - "badge-wc": 61807, - "bag-check-fill": 61808, - "bag-check": 61809, - "bag-dash-fill": 61810, - "bag-dash": 61811, - "bag-fill": 61812, - "bag-plus-fill": 61813, - "bag-plus": 61814, - "bag-x-fill": 61815, - "bag-x": 61816, - "bag": 61817, - "bar-chart-fill": 61818, - "bar-chart-line-fill": 61819, - "bar-chart-line": 61820, - "bar-chart-steps": 61821, - "bar-chart": 61822, - "basket-fill": 61823, - "basket": 61824, - "basket2-fill": 61825, - "basket2": 61826, - "basket3-fill": 61827, - "basket3": 61828, - "battery-charging": 61829, - "battery-full": 61830, - "battery-half": 61831, - "battery": 61832, - "bell-fill": 61833, - "bell": 61834, - "bezier": 61835, - "bezier2": 61836, - "bicycle": 61837, - "binoculars-fill": 61838, - "binoculars": 61839, - "blockquote-left": 61840, - "blockquote-right": 61841, - "book-fill": 61842, - "book-half": 61843, - "book": 61844, - "bookmark-check-fill": 61845, - "bookmark-check": 61846, - "bookmark-dash-fill": 61847, - "bookmark-dash": 61848, - "bookmark-fill": 61849, - "bookmark-heart-fill": 61850, - "bookmark-heart": 61851, - "bookmark-plus-fill": 61852, - "bookmark-plus": 61853, - "bookmark-star-fill": 61854, - "bookmark-star": 61855, - "bookmark-x-fill": 61856, - "bookmark-x": 61857, - "bookmark": 61858, - "bookmarks-fill": 61859, - "bookmarks": 61860, - "bookshelf": 61861, - "bootstrap-fill": 61862, - "bootstrap-reboot": 61863, - "bootstrap": 61864, - "border-all": 61865, - "border-bottom": 61866, - "border-center": 61867, - "border-inner": 61868, - "border-left": 61869, - "border-middle": 61870, - "border-outer": 61871, - "border-right": 61872, - "border-style": 61873, - "border-top": 61874, - "border-width": 61875, - "border": 61876, - "bounding-box-circles": 61877, - "bounding-box": 61878, - "box-arrow-down-left": 61879, - "box-arrow-down-right": 61880, - "box-arrow-down": 61881, - "box-arrow-in-down-left": 61882, - "box-arrow-in-down-right": 61883, - "box-arrow-in-down": 61884, - "box-arrow-in-left": 61885, - "box-arrow-in-right": 61886, - "box-arrow-in-up-left": 61887, - "box-arrow-in-up-right": 61888, - "box-arrow-in-up": 61889, - "box-arrow-left": 61890, - "box-arrow-right": 61891, - "box-arrow-up-left": 61892, - "box-arrow-up-right": 61893, - "box-arrow-up": 61894, - "box-seam": 61895, - "box": 61896, - "braces": 61897, - "bricks": 61898, - "briefcase-fill": 61899, - "briefcase": 61900, - "brightness-alt-high-fill": 61901, - "brightness-alt-high": 61902, - "brightness-alt-low-fill": 61903, - "brightness-alt-low": 61904, - "brightness-high-fill": 61905, - "brightness-high": 61906, - "brightness-low-fill": 61907, - "brightness-low": 61908, - "broadcast-pin": 61909, - "broadcast": 61910, - "brush-fill": 61911, - "brush": 61912, - "bucket-fill": 61913, - "bucket": 61914, - "bug-fill": 61915, - "bug": 61916, - "building": 61917, - "bullseye": 61918, - "calculator-fill": 61919, - "calculator": 61920, - "calendar-check-fill": 61921, - "calendar-check": 61922, - "calendar-date-fill": 61923, - "calendar-date": 61924, - "calendar-day-fill": 61925, - "calendar-day": 61926, - "calendar-event-fill": 61927, - "calendar-event": 61928, - "calendar-fill": 61929, - "calendar-minus-fill": 61930, - "calendar-minus": 61931, - "calendar-month-fill": 61932, - "calendar-month": 61933, - "calendar-plus-fill": 61934, - "calendar-plus": 61935, - "calendar-range-fill": 61936, - "calendar-range": 61937, - "calendar-week-fill": 61938, - "calendar-week": 61939, - "calendar-x-fill": 61940, - "calendar-x": 61941, - "calendar": 61942, - "calendar2-check-fill": 61943, - "calendar2-check": 61944, - "calendar2-date-fill": 61945, - "calendar2-date": 61946, - "calendar2-day-fill": 61947, - "calendar2-day": 61948, - "calendar2-event-fill": 61949, - "calendar2-event": 61950, - "calendar2-fill": 61951, - "calendar2-minus-fill": 61952, - "calendar2-minus": 61953, - "calendar2-month-fill": 61954, - "calendar2-month": 61955, - "calendar2-plus-fill": 61956, - "calendar2-plus": 61957, - "calendar2-range-fill": 61958, - "calendar2-range": 61959, - "calendar2-week-fill": 61960, - "calendar2-week": 61961, - "calendar2-x-fill": 61962, - "calendar2-x": 61963, - "calendar2": 61964, - "calendar3-event-fill": 61965, - "calendar3-event": 61966, - "calendar3-fill": 61967, - "calendar3-range-fill": 61968, - "calendar3-range": 61969, - "calendar3-week-fill": 61970, - "calendar3-week": 61971, - "calendar3": 61972, - "calendar4-event": 61973, - "calendar4-range": 61974, - "calendar4-week": 61975, - "calendar4": 61976, - "camera-fill": 61977, - "camera-reels-fill": 61978, - "camera-reels": 61979, - "camera-video-fill": 61980, - "camera-video-off-fill": 61981, - "camera-video-off": 61982, - "camera-video": 61983, - "camera": 61984, - "camera2": 61985, - "capslock-fill": 61986, - "capslock": 61987, - "card-checklist": 61988, - "card-heading": 61989, - "card-image": 61990, - "card-list": 61991, - "card-text": 61992, - "caret-down-fill": 61993, - "caret-down-square-fill": 61994, - "caret-down-square": 61995, - "caret-down": 61996, - "caret-left-fill": 61997, - "caret-left-square-fill": 61998, - "caret-left-square": 61999, - "caret-left": 62000, - "caret-right-fill": 62001, - "caret-right-square-fill": 62002, - "caret-right-square": 62003, - "caret-right": 62004, - "caret-up-fill": 62005, - "caret-up-square-fill": 62006, - "caret-up-square": 62007, - "caret-up": 62008, - "cart-check-fill": 62009, - "cart-check": 62010, - "cart-dash-fill": 62011, - "cart-dash": 62012, - "cart-fill": 62013, - "cart-plus-fill": 62014, - "cart-plus": 62015, - "cart-x-fill": 62016, - "cart-x": 62017, - "cart": 62018, - "cart2": 62019, - "cart3": 62020, - "cart4": 62021, - "cash-stack": 62022, - "cash": 62023, - "cast": 62024, - "chat-dots-fill": 62025, - "chat-dots": 62026, - "chat-fill": 62027, - "chat-left-dots-fill": 62028, - "chat-left-dots": 62029, - "chat-left-fill": 62030, - "chat-left-quote-fill": 62031, - "chat-left-quote": 62032, - "chat-left-text-fill": 62033, - "chat-left-text": 62034, - "chat-left": 62035, - "chat-quote-fill": 62036, - "chat-quote": 62037, - "chat-right-dots-fill": 62038, - "chat-right-dots": 62039, - "chat-right-fill": 62040, - "chat-right-quote-fill": 62041, - "chat-right-quote": 62042, - "chat-right-text-fill": 62043, - "chat-right-text": 62044, - "chat-right": 62045, - "chat-square-dots-fill": 62046, - "chat-square-dots": 62047, - "chat-square-fill": 62048, - "chat-square-quote-fill": 62049, - "chat-square-quote": 62050, - "chat-square-text-fill": 62051, - "chat-square-text": 62052, - "chat-square": 62053, - "chat-text-fill": 62054, - "chat-text": 62055, - "chat": 62056, - "check-all": 62057, - "check-circle-fill": 62058, - "check-circle": 62059, - "check-square-fill": 62060, - "check-square": 62061, - "check": 62062, - "check2-all": 62063, - "check2-circle": 62064, - "check2-square": 62065, - "check2": 62066, - "chevron-bar-contract": 62067, - "chevron-bar-down": 62068, - "chevron-bar-expand": 62069, - "chevron-bar-left": 62070, - "chevron-bar-right": 62071, - "chevron-bar-up": 62072, - "chevron-compact-down": 62073, - "chevron-compact-left": 62074, - "chevron-compact-right": 62075, - "chevron-compact-up": 62076, - "chevron-contract": 62077, - "chevron-double-down": 62078, - "chevron-double-left": 62079, - "chevron-double-right": 62080, - "chevron-double-up": 62081, - "chevron-down": 62082, - "chevron-expand": 62083, - "chevron-left": 62084, - "chevron-right": 62085, - "chevron-up": 62086, - "circle-fill": 62087, - "circle-half": 62088, - "circle-square": 62089, - "circle": 62090, - "clipboard-check": 62091, - "clipboard-data": 62092, - "clipboard-minus": 62093, - "clipboard-plus": 62094, - "clipboard-x": 62095, - "clipboard": 62096, - "clock-fill": 62097, - "clock-history": 62098, - "clock": 62099, - "cloud-arrow-down-fill": 62100, - "cloud-arrow-down": 62101, - "cloud-arrow-up-fill": 62102, - "cloud-arrow-up": 62103, - "cloud-check-fill": 62104, - "cloud-check": 62105, - "cloud-download-fill": 62106, - "cloud-download": 62107, - "cloud-drizzle-fill": 62108, - "cloud-drizzle": 62109, - "cloud-fill": 62110, - "cloud-fog-fill": 62111, - "cloud-fog": 62112, - "cloud-fog2-fill": 62113, - "cloud-fog2": 62114, - "cloud-hail-fill": 62115, - "cloud-hail": 62116, - "cloud-haze-fill": 62118, - "cloud-haze": 62119, - "cloud-haze2-fill": 62120, - "cloud-lightning-fill": 62121, - "cloud-lightning-rain-fill": 62122, - "cloud-lightning-rain": 62123, - "cloud-lightning": 62124, - "cloud-minus-fill": 62125, - "cloud-minus": 62126, - "cloud-moon-fill": 62127, - "cloud-moon": 62128, - "cloud-plus-fill": 62129, - "cloud-plus": 62130, - "cloud-rain-fill": 62131, - "cloud-rain-heavy-fill": 62132, - "cloud-rain-heavy": 62133, - "cloud-rain": 62134, - "cloud-slash-fill": 62135, - "cloud-slash": 62136, - "cloud-sleet-fill": 62137, - "cloud-sleet": 62138, - "cloud-snow-fill": 62139, - "cloud-snow": 62140, - "cloud-sun-fill": 62141, - "cloud-sun": 62142, - "cloud-upload-fill": 62143, - "cloud-upload": 62144, - "cloud": 62145, - "clouds-fill": 62146, - "clouds": 62147, - "cloudy-fill": 62148, - "cloudy": 62149, - "code-slash": 62150, - "code-square": 62151, - "code": 62152, - "collection-fill": 62153, - "collection-play-fill": 62154, - "collection-play": 62155, - "collection": 62156, - "columns-gap": 62157, - "columns": 62158, - "command": 62159, - "compass-fill": 62160, - "compass": 62161, - "cone-striped": 62162, - "cone": 62163, - "controller": 62164, - "cpu-fill": 62165, - "cpu": 62166, - "credit-card-2-back-fill": 62167, - "credit-card-2-back": 62168, - "credit-card-2-front-fill": 62169, - "credit-card-2-front": 62170, - "credit-card-fill": 62171, - "credit-card": 62172, - "crop": 62173, - "cup-fill": 62174, - "cup-straw": 62175, - "cup": 62176, - "cursor-fill": 62177, - "cursor-text": 62178, - "cursor": 62179, - "dash-circle-dotted": 62180, - "dash-circle-fill": 62181, - "dash-circle": 62182, - "dash-square-dotted": 62183, - "dash-square-fill": 62184, - "dash-square": 62185, - "dash": 62186, - "diagram-2-fill": 62187, - "diagram-2": 62188, - "diagram-3-fill": 62189, - "diagram-3": 62190, - "diamond-fill": 62191, - "diamond-half": 62192, - "diamond": 62193, - "dice-1-fill": 62194, - "dice-1": 62195, - "dice-2-fill": 62196, - "dice-2": 62197, - "dice-3-fill": 62198, - "dice-3": 62199, - "dice-4-fill": 62200, - "dice-4": 62201, - "dice-5-fill": 62202, - "dice-5": 62203, - "dice-6-fill": 62204, - "dice-6": 62205, - "disc-fill": 62206, - "disc": 62207, - "discord": 62208, - "display-fill": 62209, - "display": 62210, - "distribute-horizontal": 62211, - "distribute-vertical": 62212, - "door-closed-fill": 62213, - "door-closed": 62214, - "door-open-fill": 62215, - "door-open": 62216, - "dot": 62217, - "download": 62218, - "droplet-fill": 62219, - "droplet-half": 62220, - "droplet": 62221, - "earbuds": 62222, - "easel-fill": 62223, - "easel": 62224, - "egg-fill": 62225, - "egg-fried": 62226, - "egg": 62227, - "eject-fill": 62228, - "eject": 62229, - "emoji-angry-fill": 62230, - "emoji-angry": 62231, - "emoji-dizzy-fill": 62232, - "emoji-dizzy": 62233, - "emoji-expressionless-fill": 62234, - "emoji-expressionless": 62235, - "emoji-frown-fill": 62236, - "emoji-frown": 62237, - "emoji-heart-eyes-fill": 62238, - "emoji-heart-eyes": 62239, - "emoji-laughing-fill": 62240, - "emoji-laughing": 62241, - "emoji-neutral-fill": 62242, - "emoji-neutral": 62243, - "emoji-smile-fill": 62244, - "emoji-smile-upside-down-fill": 62245, - "emoji-smile-upside-down": 62246, - "emoji-smile": 62247, - "emoji-sunglasses-fill": 62248, - "emoji-sunglasses": 62249, - "emoji-wink-fill": 62250, - "emoji-wink": 62251, - "envelope-fill": 62252, - "envelope-open-fill": 62253, - "envelope-open": 62254, - "envelope": 62255, - "eraser-fill": 62256, - "eraser": 62257, - "exclamation-circle-fill": 62258, - "exclamation-circle": 62259, - "exclamation-diamond-fill": 62260, - "exclamation-diamond": 62261, - "exclamation-octagon-fill": 62262, - "exclamation-octagon": 62263, - "exclamation-square-fill": 62264, - "exclamation-square": 62265, - "exclamation-triangle-fill": 62266, - "exclamation-triangle": 62267, - "exclamation": 62268, - "exclude": 62269, - "eye-fill": 62270, - "eye-slash-fill": 62271, - "eye-slash": 62272, - "eye": 62273, - "eyedropper": 62274, - "eyeglasses": 62275, - "facebook": 62276, - "file-arrow-down-fill": 62277, - "file-arrow-down": 62278, - "file-arrow-up-fill": 62279, - "file-arrow-up": 62280, - "file-bar-graph-fill": 62281, - "file-bar-graph": 62282, - "file-binary-fill": 62283, - "file-binary": 62284, - "file-break-fill": 62285, - "file-break": 62286, - "file-check-fill": 62287, - "file-check": 62288, - "file-code-fill": 62289, - "file-code": 62290, - "file-diff-fill": 62291, - "file-diff": 62292, - "file-earmark-arrow-down-fill": 62293, - "file-earmark-arrow-down": 62294, - "file-earmark-arrow-up-fill": 62295, - "file-earmark-arrow-up": 62296, - "file-earmark-bar-graph-fill": 62297, - "file-earmark-bar-graph": 62298, - "file-earmark-binary-fill": 62299, - "file-earmark-binary": 62300, - "file-earmark-break-fill": 62301, - "file-earmark-break": 62302, - "file-earmark-check-fill": 62303, - "file-earmark-check": 62304, - "file-earmark-code-fill": 62305, - "file-earmark-code": 62306, - "file-earmark-diff-fill": 62307, - "file-earmark-diff": 62308, - "file-earmark-easel-fill": 62309, - "file-earmark-easel": 62310, - "file-earmark-excel-fill": 62311, - "file-earmark-excel": 62312, - "file-earmark-fill": 62313, - "file-earmark-font-fill": 62314, - "file-earmark-font": 62315, - "file-earmark-image-fill": 62316, - "file-earmark-image": 62317, - "file-earmark-lock-fill": 62318, - "file-earmark-lock": 62319, - "file-earmark-lock2-fill": 62320, - "file-earmark-lock2": 62321, - "file-earmark-medical-fill": 62322, - "file-earmark-medical": 62323, - "file-earmark-minus-fill": 62324, - "file-earmark-minus": 62325, - "file-earmark-music-fill": 62326, - "file-earmark-music": 62327, - "file-earmark-person-fill": 62328, - "file-earmark-person": 62329, - "file-earmark-play-fill": 62330, - "file-earmark-play": 62331, - "file-earmark-plus-fill": 62332, - "file-earmark-plus": 62333, - "file-earmark-post-fill": 62334, - "file-earmark-post": 62335, - "file-earmark-ppt-fill": 62336, - "file-earmark-ppt": 62337, - "file-earmark-richtext-fill": 62338, - "file-earmark-richtext": 62339, - "file-earmark-ruled-fill": 62340, - "file-earmark-ruled": 62341, - "file-earmark-slides-fill": 62342, - "file-earmark-slides": 62343, - "file-earmark-spreadsheet-fill": 62344, - "file-earmark-spreadsheet": 62345, - "file-earmark-text-fill": 62346, - "file-earmark-text": 62347, - "file-earmark-word-fill": 62348, - "file-earmark-word": 62349, - "file-earmark-x-fill": 62350, - "file-earmark-x": 62351, - "file-earmark-zip-fill": 62352, - "file-earmark-zip": 62353, - "file-earmark": 62354, - "file-easel-fill": 62355, - "file-easel": 62356, - "file-excel-fill": 62357, - "file-excel": 62358, - "file-fill": 62359, - "file-font-fill": 62360, - "file-font": 62361, - "file-image-fill": 62362, - "file-image": 62363, - "file-lock-fill": 62364, - "file-lock": 62365, - "file-lock2-fill": 62366, - "file-lock2": 62367, - "file-medical-fill": 62368, - "file-medical": 62369, - "file-minus-fill": 62370, - "file-minus": 62371, - "file-music-fill": 62372, - "file-music": 62373, - "file-person-fill": 62374, - "file-person": 62375, - "file-play-fill": 62376, - "file-play": 62377, - "file-plus-fill": 62378, - "file-plus": 62379, - "file-post-fill": 62380, - "file-post": 62381, - "file-ppt-fill": 62382, - "file-ppt": 62383, - "file-richtext-fill": 62384, - "file-richtext": 62385, - "file-ruled-fill": 62386, - "file-ruled": 62387, - "file-slides-fill": 62388, - "file-slides": 62389, - "file-spreadsheet-fill": 62390, - "file-spreadsheet": 62391, - "file-text-fill": 62392, - "file-text": 62393, - "file-word-fill": 62394, - "file-word": 62395, - "file-x-fill": 62396, - "file-x": 62397, - "file-zip-fill": 62398, - "file-zip": 62399, - "file": 62400, - "files-alt": 62401, - "files": 62402, - "film": 62403, - "filter-circle-fill": 62404, - "filter-circle": 62405, - "filter-left": 62406, - "filter-right": 62407, - "filter-square-fill": 62408, - "filter-square": 62409, - "filter": 62410, - "flag-fill": 62411, - "flag": 62412, - "flower1": 62413, - "flower2": 62414, - "flower3": 62415, - "folder-check": 62416, - "folder-fill": 62417, - "folder-minus": 62418, - "folder-plus": 62419, - "folder-symlink-fill": 62420, - "folder-symlink": 62421, - "folder-x": 62422, - "folder": 62423, - "folder2-open": 62424, - "folder2": 62425, - "fonts": 62426, - "forward-fill": 62427, - "forward": 62428, - "front": 62429, - "fullscreen-exit": 62430, - "fullscreen": 62431, - "funnel-fill": 62432, - "funnel": 62433, - "gear-fill": 62434, - "gear-wide-connected": 62435, - "gear-wide": 62436, - "gear": 62437, - "gem": 62438, - "geo-alt-fill": 62439, - "geo-alt": 62440, - "geo-fill": 62441, - "geo": 62442, - "gift-fill": 62443, - "gift": 62444, - "github": 62445, - "globe": 62446, - "globe2": 62447, - "google": 62448, - "graph-down": 62449, - "graph-up": 62450, - "grid-1x2-fill": 62451, - "grid-1x2": 62452, - "grid-3x2-gap-fill": 62453, - "grid-3x2-gap": 62454, - "grid-3x2": 62455, - "grid-3x3-gap-fill": 62456, - "grid-3x3-gap": 62457, - "grid-3x3": 62458, - "grid-fill": 62459, - "grid": 62460, - "grip-horizontal": 62461, - "grip-vertical": 62462, - "hammer": 62463, - "hand-index-fill": 62464, - "hand-index-thumb-fill": 62465, - "hand-index-thumb": 62466, - "hand-index": 62467, - "hand-thumbs-down-fill": 62468, - "hand-thumbs-down": 62469, - "hand-thumbs-up-fill": 62470, - "hand-thumbs-up": 62471, - "handbag-fill": 62472, - "handbag": 62473, - "hash": 62474, - "hdd-fill": 62475, - "hdd-network-fill": 62476, - "hdd-network": 62477, - "hdd-rack-fill": 62478, - "hdd-rack": 62479, - "hdd-stack-fill": 62480, - "hdd-stack": 62481, - "hdd": 62482, - "headphones": 62483, - "headset": 62484, - "heart-fill": 62485, - "heart-half": 62486, - "heart": 62487, - "heptagon-fill": 62488, - "heptagon-half": 62489, - "heptagon": 62490, - "hexagon-fill": 62491, - "hexagon-half": 62492, - "hexagon": 62493, - "hourglass-bottom": 62494, - "hourglass-split": 62495, - "hourglass-top": 62496, - "hourglass": 62497, - "house-door-fill": 62498, - "house-door": 62499, - "house-fill": 62500, - "house": 62501, - "hr": 62502, - "hurricane": 62503, - "image-alt": 62504, - "image-fill": 62505, - "image": 62506, - "images": 62507, - "inbox-fill": 62508, - "inbox": 62509, - "inboxes-fill": 62510, - "inboxes": 62511, - "info-circle-fill": 62512, - "info-circle": 62513, - "info-square-fill": 62514, - "info-square": 62515, - "info": 62516, - "input-cursor-text": 62517, - "input-cursor": 62518, - "instagram": 62519, - "intersect": 62520, - "journal-album": 62521, - "journal-arrow-down": 62522, - "journal-arrow-up": 62523, - "journal-bookmark-fill": 62524, - "journal-bookmark": 62525, - "journal-check": 62526, - "journal-code": 62527, - "journal-medical": 62528, - "journal-minus": 62529, - "journal-plus": 62530, - "journal-richtext": 62531, - "journal-text": 62532, - "journal-x": 62533, - "journal": 62534, - "journals": 62535, - "joystick": 62536, - "justify-left": 62537, - "justify-right": 62538, - "justify": 62539, - "kanban-fill": 62540, - "kanban": 62541, - "key-fill": 62542, - "key": 62543, - "keyboard-fill": 62544, - "keyboard": 62545, - "ladder": 62546, - "lamp-fill": 62547, - "lamp": 62548, - "laptop-fill": 62549, - "laptop": 62550, - "layer-backward": 62551, - "layer-forward": 62552, - "layers-fill": 62553, - "layers-half": 62554, - "layers": 62555, - "layout-sidebar-inset-reverse": 62556, - "layout-sidebar-inset": 62557, - "layout-sidebar-reverse": 62558, - "layout-sidebar": 62559, - "layout-split": 62560, - "layout-text-sidebar-reverse": 62561, - "layout-text-sidebar": 62562, - "layout-text-window-reverse": 62563, - "layout-text-window": 62564, - "layout-three-columns": 62565, - "layout-wtf": 62566, - "life-preserver": 62567, - "lightbulb-fill": 62568, - "lightbulb-off-fill": 62569, - "lightbulb-off": 62570, - "lightbulb": 62571, - "lightning-charge-fill": 62572, - "lightning-charge": 62573, - "lightning-fill": 62574, - "lightning": 62575, - "link-45deg": 62576, - "link": 62577, - "linkedin": 62578, - "list-check": 62579, - "list-nested": 62580, - "list-ol": 62581, - "list-stars": 62582, - "list-task": 62583, - "list-ul": 62584, - "list": 62585, - "lock-fill": 62586, - "lock": 62587, - "mailbox": 62588, - "mailbox2": 62589, - "map-fill": 62590, - "map": 62591, - "markdown-fill": 62592, - "markdown": 62593, - "mask": 62594, - "megaphone-fill": 62595, - "megaphone": 62596, - "menu-app-fill": 62597, - "menu-app": 62598, - "menu-button-fill": 62599, - "menu-button-wide-fill": 62600, - "menu-button-wide": 62601, - "menu-button": 62602, - "menu-down": 62603, - "menu-up": 62604, - "mic-fill": 62605, - "mic-mute-fill": 62606, - "mic-mute": 62607, - "mic": 62608, - "minecart-loaded": 62609, - "minecart": 62610, - "moisture": 62611, - "moon-fill": 62612, - "moon-stars-fill": 62613, - "moon-stars": 62614, - "moon": 62615, - "mouse-fill": 62616, - "mouse": 62617, - "mouse2-fill": 62618, - "mouse2": 62619, - "mouse3-fill": 62620, - "mouse3": 62621, - "music-note-beamed": 62622, - "music-note-list": 62623, - "music-note": 62624, - "music-player-fill": 62625, - "music-player": 62626, - "newspaper": 62627, - "node-minus-fill": 62628, - "node-minus": 62629, - "node-plus-fill": 62630, - "node-plus": 62631, - "nut-fill": 62632, - "nut": 62633, - "octagon-fill": 62634, - "octagon-half": 62635, - "octagon": 62636, - "option": 62637, - "outlet": 62638, - "paint-bucket": 62639, - "palette-fill": 62640, - "palette": 62641, - "palette2": 62642, - "paperclip": 62643, - "paragraph": 62644, - "patch-check-fill": 62645, - "patch-check": 62646, - "patch-exclamation-fill": 62647, - "patch-exclamation": 62648, - "patch-minus-fill": 62649, - "patch-minus": 62650, - "patch-plus-fill": 62651, - "patch-plus": 62652, - "patch-question-fill": 62653, - "patch-question": 62654, - "pause-btn-fill": 62655, - "pause-btn": 62656, - "pause-circle-fill": 62657, - "pause-circle": 62658, - "pause-fill": 62659, - "pause": 62660, - "peace-fill": 62661, - "peace": 62662, - "pen-fill": 62663, - "pen": 62664, - "pencil-fill": 62665, - "pencil-square": 62666, - "pencil": 62667, - "pentagon-fill": 62668, - "pentagon-half": 62669, - "pentagon": 62670, - "people-fill": 62671, - "people": 62672, - "percent": 62673, - "person-badge-fill": 62674, - "person-badge": 62675, - "person-bounding-box": 62676, - "person-check-fill": 62677, - "person-check": 62678, - "person-circle": 62679, - "person-dash-fill": 62680, - "person-dash": 62681, - "person-fill": 62682, - "person-lines-fill": 62683, - "person-plus-fill": 62684, - "person-plus": 62685, - "person-square": 62686, - "person-x-fill": 62687, - "person-x": 62688, - "person": 62689, - "phone-fill": 62690, - "phone-landscape-fill": 62691, - "phone-landscape": 62692, - "phone-vibrate-fill": 62693, - "phone-vibrate": 62694, - "phone": 62695, - "pie-chart-fill": 62696, - "pie-chart": 62697, - "pin-angle-fill": 62698, - "pin-angle": 62699, - "pin-fill": 62700, - "pin": 62701, - "pip-fill": 62702, - "pip": 62703, - "play-btn-fill": 62704, - "play-btn": 62705, - "play-circle-fill": 62706, - "play-circle": 62707, - "play-fill": 62708, - "play": 62709, - "plug-fill": 62710, - "plug": 62711, - "plus-circle-dotted": 62712, - "plus-circle-fill": 62713, - "plus-circle": 62714, - "plus-square-dotted": 62715, - "plus-square-fill": 62716, - "plus-square": 62717, - "plus": 62718, - "power": 62719, - "printer-fill": 62720, - "printer": 62721, - "puzzle-fill": 62722, - "puzzle": 62723, - "question-circle-fill": 62724, - "question-circle": 62725, - "question-diamond-fill": 62726, - "question-diamond": 62727, - "question-octagon-fill": 62728, - "question-octagon": 62729, - "question-square-fill": 62730, - "question-square": 62731, - "question": 62732, - "rainbow": 62733, - "receipt-cutoff": 62734, - "receipt": 62735, - "reception-0": 62736, - "reception-1": 62737, - "reception-2": 62738, - "reception-3": 62739, - "reception-4": 62740, - "record-btn-fill": 62741, - "record-btn": 62742, - "record-circle-fill": 62743, - "record-circle": 62744, - "record-fill": 62745, - "record": 62746, - "record2-fill": 62747, - "record2": 62748, - "reply-all-fill": 62749, - "reply-all": 62750, - "reply-fill": 62751, - "reply": 62752, - "rss-fill": 62753, - "rss": 62754, - "rulers": 62755, - "save-fill": 62756, - "save": 62757, - "save2-fill": 62758, - "save2": 62759, - "scissors": 62760, - "screwdriver": 62761, - "search": 62762, - "segmented-nav": 62763, - "server": 62764, - "share-fill": 62765, - "share": 62766, - "shield-check": 62767, - "shield-exclamation": 62768, - "shield-fill-check": 62769, - "shield-fill-exclamation": 62770, - "shield-fill-minus": 62771, - "shield-fill-plus": 62772, - "shield-fill-x": 62773, - "shield-fill": 62774, - "shield-lock-fill": 62775, - "shield-lock": 62776, - "shield-minus": 62777, - "shield-plus": 62778, - "shield-shaded": 62779, - "shield-slash-fill": 62780, - "shield-slash": 62781, - "shield-x": 62782, - "shield": 62783, - "shift-fill": 62784, - "shift": 62785, - "shop-window": 62786, - "shop": 62787, - "shuffle": 62788, - "signpost-2-fill": 62789, - "signpost-2": 62790, - "signpost-fill": 62791, - "signpost-split-fill": 62792, - "signpost-split": 62793, - "signpost": 62794, - "sim-fill": 62795, - "sim": 62796, - "skip-backward-btn-fill": 62797, - "skip-backward-btn": 62798, - "skip-backward-circle-fill": 62799, - "skip-backward-circle": 62800, - "skip-backward-fill": 62801, - "skip-backward": 62802, - "skip-end-btn-fill": 62803, - "skip-end-btn": 62804, - "skip-end-circle-fill": 62805, - "skip-end-circle": 62806, - "skip-end-fill": 62807, - "skip-end": 62808, - "skip-forward-btn-fill": 62809, - "skip-forward-btn": 62810, - "skip-forward-circle-fill": 62811, - "skip-forward-circle": 62812, - "skip-forward-fill": 62813, - "skip-forward": 62814, - "skip-start-btn-fill": 62815, - "skip-start-btn": 62816, - "skip-start-circle-fill": 62817, - "skip-start-circle": 62818, - "skip-start-fill": 62819, - "skip-start": 62820, - "slack": 62821, - "slash-circle-fill": 62822, - "slash-circle": 62823, - "slash-square-fill": 62824, - "slash-square": 62825, - "slash": 62826, - "sliders": 62827, - "smartwatch": 62828, - "snow": 62829, - "snow2": 62830, - "snow3": 62831, - "sort-alpha-down-alt": 62832, - "sort-alpha-down": 62833, - "sort-alpha-up-alt": 62834, - "sort-alpha-up": 62835, - "sort-down-alt": 62836, - "sort-down": 62837, - "sort-numeric-down-alt": 62838, - "sort-numeric-down": 62839, - "sort-numeric-up-alt": 62840, - "sort-numeric-up": 62841, - "sort-up-alt": 62842, - "sort-up": 62843, - "soundwave": 62844, - "speaker-fill": 62845, - "speaker": 62846, - "speedometer": 62847, - "speedometer2": 62848, - "spellcheck": 62849, - "square-fill": 62850, - "square-half": 62851, - "square": 62852, - "stack": 62853, - "star-fill": 62854, - "star-half": 62855, - "star": 62856, - "stars": 62857, - "stickies-fill": 62858, - "stickies": 62859, - "sticky-fill": 62860, - "sticky": 62861, - "stop-btn-fill": 62862, - "stop-btn": 62863, - "stop-circle-fill": 62864, - "stop-circle": 62865, - "stop-fill": 62866, - "stop": 62867, - "stoplights-fill": 62868, - "stoplights": 62869, - "stopwatch-fill": 62870, - "stopwatch": 62871, - "subtract": 62872, - "suit-club-fill": 62873, - "suit-club": 62874, - "suit-diamond-fill": 62875, - "suit-diamond": 62876, - "suit-heart-fill": 62877, - "suit-heart": 62878, - "suit-spade-fill": 62879, - "suit-spade": 62880, - "sun-fill": 62881, - "sun": 62882, - "sunglasses": 62883, - "sunrise-fill": 62884, - "sunrise": 62885, - "sunset-fill": 62886, - "sunset": 62887, - "symmetry-horizontal": 62888, - "symmetry-vertical": 62889, - "table": 62890, - "tablet-fill": 62891, - "tablet-landscape-fill": 62892, - "tablet-landscape": 62893, - "tablet": 62894, - "tag-fill": 62895, - "tag": 62896, - "tags-fill": 62897, - "tags": 62898, - "telegram": 62899, - "telephone-fill": 62900, - "telephone-forward-fill": 62901, - "telephone-forward": 62902, - "telephone-inbound-fill": 62903, - "telephone-inbound": 62904, - "telephone-minus-fill": 62905, - "telephone-minus": 62906, - "telephone-outbound-fill": 62907, - "telephone-outbound": 62908, - "telephone-plus-fill": 62909, - "telephone-plus": 62910, - "telephone-x-fill": 62911, - "telephone-x": 62912, - "telephone": 62913, - "terminal-fill": 62914, - "terminal": 62915, - "text-center": 62916, - "text-indent-left": 62917, - "text-indent-right": 62918, - "text-left": 62919, - "text-paragraph": 62920, - "text-right": 62921, - "textarea-resize": 62922, - "textarea-t": 62923, - "textarea": 62924, - "thermometer-half": 62925, - "thermometer-high": 62926, - "thermometer-low": 62927, - "thermometer-snow": 62928, - "thermometer-sun": 62929, - "thermometer": 62930, - "three-dots-vertical": 62931, - "three-dots": 62932, - "toggle-off": 62933, - "toggle-on": 62934, - "toggle2-off": 62935, - "toggle2-on": 62936, - "toggles": 62937, - "toggles2": 62938, - "tools": 62939, - "tornado": 62940, - "trash-fill": 62941, - "trash": 62942, - "trash2-fill": 62943, - "trash2": 62944, - "tree-fill": 62945, - "tree": 62946, - "triangle-fill": 62947, - "triangle-half": 62948, - "triangle": 62949, - "trophy-fill": 62950, - "trophy": 62951, - "tropical-storm": 62952, - "truck-flatbed": 62953, - "truck": 62954, - "tsunami": 62955, - "tv-fill": 62956, - "tv": 62957, - "twitch": 62958, - "twitter": 62959, - "type-bold": 62960, - "type-h1": 62961, - "type-h2": 62962, - "type-h3": 62963, - "type-italic": 62964, - "type-strikethrough": 62965, - "type-underline": 62966, - "type": 62967, - "ui-checks-grid": 62968, - "ui-checks": 62969, - "ui-radios-grid": 62970, - "ui-radios": 62971, - "umbrella-fill": 62972, - "umbrella": 62973, - "union": 62974, - "unlock-fill": 62975, - "unlock": 62976, - "upc-scan": 62977, - "upc": 62978, - "upload": 62979, - "vector-pen": 62980, - "view-list": 62981, - "view-stacked": 62982, - "vinyl-fill": 62983, - "vinyl": 62984, - "voicemail": 62985, - "volume-down-fill": 62986, - "volume-down": 62987, - "volume-mute-fill": 62988, - "volume-mute": 62989, - "volume-off-fill": 62990, - "volume-off": 62991, - "volume-up-fill": 62992, - "volume-up": 62993, - "vr": 62994, - "wallet-fill": 62995, - "wallet": 62996, - "wallet2": 62997, - "watch": 62998, - "water": 62999, - "whatsapp": 63000, - "wifi-1": 63001, - "wifi-2": 63002, - "wifi-off": 63003, - "wifi": 63004, - "wind": 63005, - "window-dock": 63006, - "window-sidebar": 63007, - "window": 63008, - "wrench": 63009, - "x-circle-fill": 63010, - "x-circle": 63011, - "x-diamond-fill": 63012, - "x-diamond": 63013, - "x-octagon-fill": 63014, - "x-octagon": 63015, - "x-square-fill": 63016, - "x-square": 63017, - "x": 63018, - "youtube": 63019, - "zoom-in": 63020, - "zoom-out": 63021, - "bank": 63022, - "bank2": 63023, - "bell-slash-fill": 63024, - "bell-slash": 63025, - "cash-coin": 63026, - "check-lg": 63027, - "coin": 63028, - "currency-bitcoin": 63029, - "currency-dollar": 63030, - "currency-euro": 63031, - "currency-exchange": 63032, - "currency-pound": 63033, - "currency-yen": 63034, - "dash-lg": 63035, - "exclamation-lg": 63036, - "file-earmark-pdf-fill": 63037, - "file-earmark-pdf": 63038, - "file-pdf-fill": 63039, - "file-pdf": 63040, - "gender-ambiguous": 63041, - "gender-female": 63042, - "gender-male": 63043, - "gender-trans": 63044, - "headset-vr": 63045, - "info-lg": 63046, - "mastodon": 63047, - "messenger": 63048, - "piggy-bank-fill": 63049, - "piggy-bank": 63050, - "pin-map-fill": 63051, - "pin-map": 63052, - "plus-lg": 63053, - "question-lg": 63054, - "recycle": 63055, - "reddit": 63056, - "safe-fill": 63057, - "safe2-fill": 63058, - "safe2": 63059, - "sd-card-fill": 63060, - "sd-card": 63061, - "skype": 63062, - "slash-lg": 63063, - "translate": 63064, - "x-lg": 63065, - "safe": 63066, - "apple": 63067, - "microsoft": 63069, - "windows": 63070, - "behance": 63068, - "dribbble": 63071, - "line": 63072, - "medium": 63073, - "paypal": 63074, - "pinterest": 63075, - "signal": 63076, - "snapchat": 63077, - "spotify": 63078, - "stack-overflow": 63079, - "strava": 63080, - "wordpress": 63081, - "vimeo": 63082, - "activity": 63083, - "easel2-fill": 63084, - "easel2": 63085, - "easel3-fill": 63086, - "easel3": 63087, - "fan": 63088, - "fingerprint": 63089, - "graph-down-arrow": 63090, - "graph-up-arrow": 63091, - "hypnotize": 63092, - "magic": 63093, - "person-rolodex": 63094, - "person-video": 63095, - "person-video2": 63096, - "person-video3": 63097, - "person-workspace": 63098, - "radioactive": 63099, - "webcam-fill": 63100, - "webcam": 63101, - "yin-yang": 63102, - "bandaid-fill": 63104, - "bandaid": 63105, - "bluetooth": 63106, - "body-text": 63107, - "boombox": 63108, - "boxes": 63109, - "dpad-fill": 63110, - "dpad": 63111, - "ear-fill": 63112, - "ear": 63113, - "envelope-check-fill": 63115, - "envelope-check": 63116, - "envelope-dash-fill": 63118, - "envelope-dash": 63119, - "envelope-exclamation-fill": 63121, - "envelope-exclamation": 63122, - "envelope-plus-fill": 63123, - "envelope-plus": 63124, - "envelope-slash-fill": 63126, - "envelope-slash": 63127, - "envelope-x-fill": 63129, - "envelope-x": 63130, - "explicit-fill": 63131, - "explicit": 63132, - "git": 63133, - "infinity": 63134, - "list-columns-reverse": 63135, - "list-columns": 63136, - "meta": 63137, - "nintendo-switch": 63140, - "pc-display-horizontal": 63141, - "pc-display": 63142, - "pc-horizontal": 63143, - "pc": 63144, - "playstation": 63145, - "plus-slash-minus": 63146, - "projector-fill": 63147, - "projector": 63148, - "qr-code-scan": 63149, - "qr-code": 63150, - "quora": 63151, - "quote": 63152, - "robot": 63153, - "send-check-fill": 63154, - "send-check": 63155, - "send-dash-fill": 63156, - "send-dash": 63157, - "send-exclamation-fill": 63159, - "send-exclamation": 63160, - "send-fill": 63161, - "send-plus-fill": 63162, - "send-plus": 63163, - "send-slash-fill": 63164, - "send-slash": 63165, - "send-x-fill": 63166, - "send-x": 63167, - "send": 63168, - "steam": 63169, - "terminal-dash": 63171, - "terminal-plus": 63172, - "terminal-split": 63173, - "ticket-detailed-fill": 63174, - "ticket-detailed": 63175, - "ticket-fill": 63176, - "ticket-perforated-fill": 63177, - "ticket-perforated": 63178, - "ticket": 63179, - "tiktok": 63180, - "window-dash": 63181, - "window-desktop": 63182, - "window-fullscreen": 63183, - "window-plus": 63184, - "window-split": 63185, - "window-stack": 63186, - "window-x": 63187, - "xbox": 63188, - "ethernet": 63189, - "hdmi-fill": 63190, - "hdmi": 63191, - "usb-c-fill": 63192, - "usb-c": 63193, - "usb-fill": 63194, - "usb-plug-fill": 63195, - "usb-plug": 63196, - "usb-symbol": 63197, - "usb": 63198, - "boombox-fill": 63199, - "displayport": 63201, - "gpu-card": 63202, - "memory": 63203, - "modem-fill": 63204, - "modem": 63205, - "motherboard-fill": 63206, - "motherboard": 63207, - "optical-audio-fill": 63208, - "optical-audio": 63209, - "pci-card": 63210, - "router-fill": 63211, - "router": 63212, - "thunderbolt-fill": 63215, - "thunderbolt": 63216, - "usb-drive-fill": 63217, - "usb-drive": 63218, - "usb-micro-fill": 63219, - "usb-micro": 63220, - "usb-mini-fill": 63221, - "usb-mini": 63222, - "cloud-haze2": 63223, - "device-hdd-fill": 63224, - "device-hdd": 63225, - "device-ssd-fill": 63226, - "device-ssd": 63227, - "displayport-fill": 63228, - "mortarboard-fill": 63229, - "mortarboard": 63230, - "terminal-x": 63231, - "arrow-through-heart-fill": 63232, - "arrow-through-heart": 63233, - "badge-sd-fill": 63234, - "badge-sd": 63235, - "bag-heart-fill": 63236, - "bag-heart": 63237, - "balloon-fill": 63238, - "balloon-heart-fill": 63239, - "balloon-heart": 63240, - "balloon": 63241, - "box2-fill": 63242, - "box2-heart-fill": 63243, - "box2-heart": 63244, - "box2": 63245, - "braces-asterisk": 63246, - "calendar-heart-fill": 63247, - "calendar-heart": 63248, - "calendar2-heart-fill": 63249, - "calendar2-heart": 63250, - "chat-heart-fill": 63251, - "chat-heart": 63252, - "chat-left-heart-fill": 63253, - "chat-left-heart": 63254, - "chat-right-heart-fill": 63255, - "chat-right-heart": 63256, - "chat-square-heart-fill": 63257, - "chat-square-heart": 63258, - "clipboard-check-fill": 63259, - "clipboard-data-fill": 63260, - "clipboard-fill": 63261, - "clipboard-heart-fill": 63262, - "clipboard-heart": 63263, - "clipboard-minus-fill": 63264, - "clipboard-plus-fill": 63265, - "clipboard-pulse": 63266, - "clipboard-x-fill": 63267, - "clipboard2-check-fill": 63268, - "clipboard2-check": 63269, - "clipboard2-data-fill": 63270, - "clipboard2-data": 63271, - "clipboard2-fill": 63272, - "clipboard2-heart-fill": 63273, - "clipboard2-heart": 63274, - "clipboard2-minus-fill": 63275, - "clipboard2-minus": 63276, - "clipboard2-plus-fill": 63277, - "clipboard2-plus": 63278, - "clipboard2-pulse-fill": 63279, - "clipboard2-pulse": 63280, - "clipboard2-x-fill": 63281, - "clipboard2-x": 63282, - "clipboard2": 63283, - "emoji-kiss-fill": 63284, - "emoji-kiss": 63285, - "envelope-heart-fill": 63286, - "envelope-heart": 63287, - "envelope-open-heart-fill": 63288, - "envelope-open-heart": 63289, - "envelope-paper-fill": 63290, - "envelope-paper-heart-fill": 63291, - "envelope-paper-heart": 63292, - "envelope-paper": 63293, - "filetype-aac": 63294, - "filetype-ai": 63295, - "filetype-bmp": 63296, - "filetype-cs": 63297, - "filetype-css": 63298, - "filetype-csv": 63299, - "filetype-doc": 63300, - "filetype-docx": 63301, - "filetype-exe": 63302, - "filetype-gif": 63303, - "filetype-heic": 63304, - "filetype-html": 63305, - "filetype-java": 63306, - "filetype-jpg": 63307, - "filetype-js": 63308, - "filetype-jsx": 63309, - "filetype-key": 63310, - "filetype-m4p": 63311, - "filetype-md": 63312, - "filetype-mdx": 63313, - "filetype-mov": 63314, - "filetype-mp3": 63315, - "filetype-mp4": 63316, - "filetype-otf": 63317, - "filetype-pdf": 63318, - "filetype-php": 63319, - "filetype-png": 63320, - "filetype-ppt": 63322, - "filetype-psd": 63323, - "filetype-py": 63324, - "filetype-raw": 63325, - "filetype-rb": 63326, - "filetype-sass": 63327, - "filetype-scss": 63328, - "filetype-sh": 63329, - "filetype-svg": 63330, - "filetype-tiff": 63331, - "filetype-tsx": 63332, - "filetype-ttf": 63333, - "filetype-txt": 63334, - "filetype-wav": 63335, - "filetype-woff": 63336, - "filetype-xls": 63338, - "filetype-xml": 63339, - "filetype-yml": 63340, - "heart-arrow": 63341, - "heart-pulse-fill": 63342, - "heart-pulse": 63343, - "heartbreak-fill": 63344, - "heartbreak": 63345, - "hearts": 63346, - "hospital-fill": 63347, - "hospital": 63348, - "house-heart-fill": 63349, - "house-heart": 63350, - "incognito": 63351, - "magnet-fill": 63352, - "magnet": 63353, - "person-heart": 63354, - "person-hearts": 63355, - "phone-flip": 63356, - "plugin": 63357, - "postage-fill": 63358, - "postage-heart-fill": 63359, - "postage-heart": 63360, - "postage": 63361, - "postcard-fill": 63362, - "postcard-heart-fill": 63363, - "postcard-heart": 63364, - "postcard": 63365, - "search-heart-fill": 63366, - "search-heart": 63367, - "sliders2-vertical": 63368, - "sliders2": 63369, - "trash3-fill": 63370, - "trash3": 63371, - "valentine": 63372, - "valentine2": 63373, - "wrench-adjustable-circle-fill": 63374, - "wrench-adjustable-circle": 63375, - "wrench-adjustable": 63376, - "filetype-json": 63377, - "filetype-pptx": 63378, - "filetype-xlsx": 63379, - "1-circle-fill": 63382, - "1-circle": 63383, - "1-square-fill": 63384, - "1-square": 63385, - "2-circle-fill": 63388, - "2-circle": 63389, - "2-square-fill": 63390, - "2-square": 63391, - "3-circle-fill": 63394, - "3-circle": 63395, - "3-square-fill": 63396, - "3-square": 63397, - "4-circle-fill": 63400, - "4-circle": 63401, - "4-square-fill": 63402, - "4-square": 63403, - "5-circle-fill": 63406, - "5-circle": 63407, - "5-square-fill": 63408, - "5-square": 63409, - "6-circle-fill": 63412, - "6-circle": 63413, - "6-square-fill": 63414, - "6-square": 63415, - "7-circle-fill": 63418, - "7-circle": 63419, - "7-square-fill": 63420, - "7-square": 63421, - "8-circle-fill": 63424, - "8-circle": 63425, - "8-square-fill": 63426, - "8-square": 63427, - "9-circle-fill": 63430, - "9-circle": 63431, - "9-square-fill": 63432, - "9-square": 63433, - "airplane-engines-fill": 63434, - "airplane-engines": 63435, - "airplane-fill": 63436, - "airplane": 63437, - "alexa": 63438, - "alipay": 63439, - "android": 63440, - "android2": 63441, - "box-fill": 63442, - "box-seam-fill": 63443, - "browser-chrome": 63444, - "browser-edge": 63445, - "browser-firefox": 63446, - "browser-safari": 63447, - "c-circle-fill": 63450, - "c-circle": 63451, - "c-square-fill": 63452, - "c-square": 63453, - "capsule-pill": 63454, - "capsule": 63455, - "car-front-fill": 63456, - "car-front": 63457, - "cassette-fill": 63458, - "cassette": 63459, - "cc-circle-fill": 63462, - "cc-circle": 63463, - "cc-square-fill": 63464, - "cc-square": 63465, - "cup-hot-fill": 63466, - "cup-hot": 63467, - "currency-rupee": 63468, - "dropbox": 63469, - "escape": 63470, - "fast-forward-btn-fill": 63471, - "fast-forward-btn": 63472, - "fast-forward-circle-fill": 63473, - "fast-forward-circle": 63474, - "fast-forward-fill": 63475, - "fast-forward": 63476, - "filetype-sql": 63477, - "fire": 63478, - "google-play": 63479, - "h-circle-fill": 63482, - "h-circle": 63483, - "h-square-fill": 63484, - "h-square": 63485, - "indent": 63486, - "lungs-fill": 63487, - "lungs": 63488, - "microsoft-teams": 63489, - "p-circle-fill": 63492, - "p-circle": 63493, - "p-square-fill": 63494, - "p-square": 63495, - "pass-fill": 63496, - "pass": 63497, - "prescription": 63498, - "prescription2": 63499, - "r-circle-fill": 63502, - "r-circle": 63503, - "r-square-fill": 63504, - "r-square": 63505, - "repeat-1": 63506, - "repeat": 63507, - "rewind-btn-fill": 63508, - "rewind-btn": 63509, - "rewind-circle-fill": 63510, - "rewind-circle": 63511, - "rewind-fill": 63512, - "rewind": 63513, - "train-freight-front-fill": 63514, - "train-freight-front": 63515, - "train-front-fill": 63516, - "train-front": 63517, - "train-lightrail-front-fill": 63518, - "train-lightrail-front": 63519, - "truck-front-fill": 63520, - "truck-front": 63521, - "ubuntu": 63522, - "unindent": 63523, - "unity": 63524, - "universal-access-circle": 63525, - "universal-access": 63526, - "virus": 63527, - "virus2": 63528, - "wechat": 63529, - "yelp": 63530, - "sign-stop-fill": 63531, - "sign-stop-lights-fill": 63532, - "sign-stop-lights": 63533, - "sign-stop": 63534, - "sign-turn-left-fill": 63535, - "sign-turn-left": 63536, - "sign-turn-right-fill": 63537, - "sign-turn-right": 63538, - "sign-turn-slight-left-fill": 63539, - "sign-turn-slight-left": 63540, - "sign-turn-slight-right-fill": 63541, - "sign-turn-slight-right": 63542, - "sign-yield-fill": 63543, - "sign-yield": 63544, - "ev-station-fill": 63545, - "ev-station": 63546, - "fuel-pump-diesel-fill": 63547, - "fuel-pump-diesel": 63548, - "fuel-pump-fill": 63549, - "fuel-pump": 63550, - "0-circle-fill": 63551, - "0-circle": 63552, - "0-square-fill": 63553, - "0-square": 63554, - "rocket-fill": 63555, - "rocket-takeoff-fill": 63556, - "rocket-takeoff": 63557, - "rocket": 63558, - "stripe": 63559, - "subscript": 63560, - "superscript": 63561, - "trello": 63562, - "envelope-at-fill": 63563, - "envelope-at": 63564, - "regex": 63565, - "text-wrap": 63566, - "sign-dead-end-fill": 63567, - "sign-dead-end": 63568, - "sign-do-not-enter-fill": 63569, - "sign-do-not-enter": 63570, - "sign-intersection-fill": 63571, - "sign-intersection-side-fill": 63572, - "sign-intersection-side": 63573, - "sign-intersection-t-fill": 63574, - "sign-intersection-t": 63575, - "sign-intersection-y-fill": 63576, - "sign-intersection-y": 63577, - "sign-intersection": 63578, - "sign-merge-left-fill": 63579, - "sign-merge-left": 63580, - "sign-merge-right-fill": 63581, - "sign-merge-right": 63582, - "sign-no-left-turn-fill": 63583, - "sign-no-left-turn": 63584, - "sign-no-parking-fill": 63585, - "sign-no-parking": 63586, - "sign-no-right-turn-fill": 63587, - "sign-no-right-turn": 63588, - "sign-railroad-fill": 63589, - "sign-railroad": 63590, - "building-add": 63591, - "building-check": 63592, - "building-dash": 63593, - "building-down": 63594, - "building-exclamation": 63595, - "building-fill-add": 63596, - "building-fill-check": 63597, - "building-fill-dash": 63598, - "building-fill-down": 63599, - "building-fill-exclamation": 63600, - "building-fill-gear": 63601, - "building-fill-lock": 63602, - "building-fill-slash": 63603, - "building-fill-up": 63604, - "building-fill-x": 63605, - "building-fill": 63606, - "building-gear": 63607, - "building-lock": 63608, - "building-slash": 63609, - "building-up": 63610, - "building-x": 63611, - "buildings-fill": 63612, - "buildings": 63613, - "bus-front-fill": 63614, - "bus-front": 63615, - "ev-front-fill": 63616, - "ev-front": 63617, - "globe-americas": 63618, - "globe-asia-australia": 63619, - "globe-central-south-asia": 63620, - "globe-europe-africa": 63621, - "house-add-fill": 63622, - "house-add": 63623, - "house-check-fill": 63624, - "house-check": 63625, - "house-dash-fill": 63626, - "house-dash": 63627, - "house-down-fill": 63628, - "house-down": 63629, - "house-exclamation-fill": 63630, - "house-exclamation": 63631, - "house-gear-fill": 63632, - "house-gear": 63633, - "house-lock-fill": 63634, - "house-lock": 63635, - "house-slash-fill": 63636, - "house-slash": 63637, - "house-up-fill": 63638, - "house-up": 63639, - "house-x-fill": 63640, - "house-x": 63641, - "person-add": 63642, - "person-down": 63643, - "person-exclamation": 63644, - "person-fill-add": 63645, - "person-fill-check": 63646, - "person-fill-dash": 63647, - "person-fill-down": 63648, - "person-fill-exclamation": 63649, - "person-fill-gear": 63650, - "person-fill-lock": 63651, - "person-fill-slash": 63652, - "person-fill-up": 63653, - "person-fill-x": 63654, - "person-gear": 63655, - "person-lock": 63656, - "person-slash": 63657, - "person-up": 63658, - "scooter": 63659, - "taxi-front-fill": 63660, - "taxi-front": 63661, - "amd": 63662, - "database-add": 63663, - "database-check": 63664, - "database-dash": 63665, - "database-down": 63666, - "database-exclamation": 63667, - "database-fill-add": 63668, - "database-fill-check": 63669, - "database-fill-dash": 63670, - "database-fill-down": 63671, - "database-fill-exclamation": 63672, - "database-fill-gear": 63673, - "database-fill-lock": 63674, - "database-fill-slash": 63675, - "database-fill-up": 63676, - "database-fill-x": 63677, - "database-fill": 63678, - "database-gear": 63679, - "database-lock": 63680, - "database-slash": 63681, - "database-up": 63682, - "database-x": 63683, - "database": 63684, - "houses-fill": 63685, - "houses": 63686, - "nvidia": 63687, - "person-vcard-fill": 63688, - "person-vcard": 63689, - "sina-weibo": 63690, - "tencent-qq": 63691, - "wikipedia": 63692, - "alphabet-uppercase": 62117, - "alphabet": 63114, - "amazon": 63117, - "arrows-collapse-vertical": 63120, - "arrows-expand-vertical": 63125, - "arrows-vertical": 63128, - "arrows": 63138, - "ban-fill": 63139, - "ban": 63158, - "bing": 63170, - "cake": 63200, - "cake2": 63213, - "cookie": 63214, - "copy": 63321, - "crosshair": 63337, - "crosshair2": 63380, - "emoji-astonished-fill": 63381, - "emoji-astonished": 63386, - "emoji-grimace-fill": 63387, - "emoji-grimace": 63392, - "emoji-grin-fill": 63393, - "emoji-grin": 63398, - "emoji-surprise-fill": 63399, - "emoji-surprise": 63404, - "emoji-tear-fill": 63405, - "emoji-tear": 63410, - "envelope-arrow-down-fill": 63411, - "envelope-arrow-down": 63416, - "envelope-arrow-up-fill": 63417, - "envelope-arrow-up": 63422, - "feather": 63423, - "feather2": 63428, - "floppy-fill": 63429, - "floppy": 63448, - "floppy2-fill": 63449, - "floppy2": 63460, - "gitlab": 63461, - "highlighter": 63480, - "marker-tip": 63490, - "nvme-fill": 63491, - "nvme": 63500, - "opencollective": 63501, - "pci-card-network": 63693, - "pci-card-sound": 63694, - "radar": 63695, - "send-arrow-down-fill": 63696, - "send-arrow-down": 63697, - "send-arrow-up-fill": 63698, - "send-arrow-up": 63699, - "sim-slash-fill": 63700, - "sim-slash": 63701, - "sourceforge": 63702, - "substack": 63703, - "threads-fill": 63704, - "threads": 63705, - "transparency": 63706, - "twitter-x": 63707, - "type-h4": 63708, - "type-h5": 63709, - "type-h6": 63710, - "backpack-fill": 63711, - "backpack": 63712, - "backpack2-fill": 63713, - "backpack2": 63714, - "backpack3-fill": 63715, - "backpack3": 63716, - "backpack4-fill": 63717, - "backpack4": 63718, - "brilliance": 63719, - "cake-fill": 63720, - "cake2-fill": 63721, - "duffle-fill": 63722, - "duffle": 63723, - "exposure": 63724, - "gender-neuter": 63725, - "highlights": 63726, - "luggage-fill": 63727, - "luggage": 63728, - "mailbox-flag": 63729, - "mailbox2-flag": 63730, - "noise-reduction": 63731, - "passport-fill": 63732, - "passport": 63733, - "person-arms-up": 63734, - "person-raised-hand": 63735, - "person-standing-dress": 63736, - "person-standing": 63737, - "person-walking": 63738, - "person-wheelchair": 63739, - "shadows": 63740, - "suitcase-fill": 63741, - "suitcase-lg-fill": 63742, - "suitcase-lg": 63743, - "suitcase": 63744, - "suitcase2-fill": 63745, - "suitcase2": 63746, - "vignette": 63747, - "bluesky": 63481, - "tux": 63748, - "beaker-fill": 63749, - "beaker": 63750, - "flask-fill": 63751, - "flask-florence-fill": 63752, - "flask-florence": 63753, - "flask": 63754, - "leaf-fill": 63755, - "leaf": 63756, - "measuring-cup-fill": 63757, - "measuring-cup": 63758, - "unlock2-fill": 63759, - "unlock2": 63760, - "battery-low": 63761, - "anthropic": 63762, - "apple-music": 63763, - "claude": 63764, - "openai": 63765, - "perplexity": 63766, - "css": 63767, - "javascript": 63768, - "typescript": 63769, - "fork-knife": 63770, - "globe-americas-fill": 63771, - "globe-asia-australia-fill": 63772, - "globe-central-south-asia-fill": 63773, - "globe-europe-africa-fill": 63774 -} \ No newline at end of file diff --git a/extensions/pagetop-bootsier/assets/bootstrap-icons-1.13.1/bootstrap-icons.min.css b/extensions/pagetop-bootsier/assets/bootstrap-icons-1.13.1/bootstrap-icons.min.css deleted file mode 100644 index 706a5c8b..00000000 --- a/extensions/pagetop-bootsier/assets/bootstrap-icons-1.13.1/bootstrap-icons.min.css +++ /dev/null @@ -1,5 +0,0 @@ -/*! - * Bootstrap Icons v1.13.1 (https://icons.getbootstrap.com/) - * Copyright 2019-2024 The Bootstrap Authors - * Licensed under MIT (https://github.com/twbs/icons/blob/main/LICENSE) - */@font-face{font-display:block;font-family:bootstrap-icons;src:url("fonts/bootstrap-icons.woff2?e34853135f9e39acf64315236852cd5a") format("woff2"),url("fonts/bootstrap-icons.woff?e34853135f9e39acf64315236852cd5a") format("woff")}.bi::before,[class*=" bi-"]::before,[class^=bi-]::before{display:inline-block;font-family:bootstrap-icons!important;font-style:normal;font-weight:400!important;font-variant:normal;text-transform:none;line-height:1;vertical-align:-.125em;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.bi-123::before{content:"\f67f"}.bi-alarm-fill::before{content:"\f101"}.bi-alarm::before{content:"\f102"}.bi-align-bottom::before{content:"\f103"}.bi-align-center::before{content:"\f104"}.bi-align-end::before{content:"\f105"}.bi-align-middle::before{content:"\f106"}.bi-align-start::before{content:"\f107"}.bi-align-top::before{content:"\f108"}.bi-alt::before{content:"\f109"}.bi-app-indicator::before{content:"\f10a"}.bi-app::before{content:"\f10b"}.bi-archive-fill::before{content:"\f10c"}.bi-archive::before{content:"\f10d"}.bi-arrow-90deg-down::before{content:"\f10e"}.bi-arrow-90deg-left::before{content:"\f10f"}.bi-arrow-90deg-right::before{content:"\f110"}.bi-arrow-90deg-up::before{content:"\f111"}.bi-arrow-bar-down::before{content:"\f112"}.bi-arrow-bar-left::before{content:"\f113"}.bi-arrow-bar-right::before{content:"\f114"}.bi-arrow-bar-up::before{content:"\f115"}.bi-arrow-clockwise::before{content:"\f116"}.bi-arrow-counterclockwise::before{content:"\f117"}.bi-arrow-down-circle-fill::before{content:"\f118"}.bi-arrow-down-circle::before{content:"\f119"}.bi-arrow-down-left-circle-fill::before{content:"\f11a"}.bi-arrow-down-left-circle::before{content:"\f11b"}.bi-arrow-down-left-square-fill::before{content:"\f11c"}.bi-arrow-down-left-square::before{content:"\f11d"}.bi-arrow-down-left::before{content:"\f11e"}.bi-arrow-down-right-circle-fill::before{content:"\f11f"}.bi-arrow-down-right-circle::before{content:"\f120"}.bi-arrow-down-right-square-fill::before{content:"\f121"}.bi-arrow-down-right-square::before{content:"\f122"}.bi-arrow-down-right::before{content:"\f123"}.bi-arrow-down-short::before{content:"\f124"}.bi-arrow-down-square-fill::before{content:"\f125"}.bi-arrow-down-square::before{content:"\f126"}.bi-arrow-down-up::before{content:"\f127"}.bi-arrow-down::before{content:"\f128"}.bi-arrow-left-circle-fill::before{content:"\f129"}.bi-arrow-left-circle::before{content:"\f12a"}.bi-arrow-left-right::before{content:"\f12b"}.bi-arrow-left-short::before{content:"\f12c"}.bi-arrow-left-square-fill::before{content:"\f12d"}.bi-arrow-left-square::before{content:"\f12e"}.bi-arrow-left::before{content:"\f12f"}.bi-arrow-repeat::before{content:"\f130"}.bi-arrow-return-left::before{content:"\f131"}.bi-arrow-return-right::before{content:"\f132"}.bi-arrow-right-circle-fill::before{content:"\f133"}.bi-arrow-right-circle::before{content:"\f134"}.bi-arrow-right-short::before{content:"\f135"}.bi-arrow-right-square-fill::before{content:"\f136"}.bi-arrow-right-square::before{content:"\f137"}.bi-arrow-right::before{content:"\f138"}.bi-arrow-up-circle-fill::before{content:"\f139"}.bi-arrow-up-circle::before{content:"\f13a"}.bi-arrow-up-left-circle-fill::before{content:"\f13b"}.bi-arrow-up-left-circle::before{content:"\f13c"}.bi-arrow-up-left-square-fill::before{content:"\f13d"}.bi-arrow-up-left-square::before{content:"\f13e"}.bi-arrow-up-left::before{content:"\f13f"}.bi-arrow-up-right-circle-fill::before{content:"\f140"}.bi-arrow-up-right-circle::before{content:"\f141"}.bi-arrow-up-right-square-fill::before{content:"\f142"}.bi-arrow-up-right-square::before{content:"\f143"}.bi-arrow-up-right::before{content:"\f144"}.bi-arrow-up-short::before{content:"\f145"}.bi-arrow-up-square-fill::before{content:"\f146"}.bi-arrow-up-square::before{content:"\f147"}.bi-arrow-up::before{content:"\f148"}.bi-arrows-angle-contract::before{content:"\f149"}.bi-arrows-angle-expand::before{content:"\f14a"}.bi-arrows-collapse::before{content:"\f14b"}.bi-arrows-expand::before{content:"\f14c"}.bi-arrows-fullscreen::before{content:"\f14d"}.bi-arrows-move::before{content:"\f14e"}.bi-aspect-ratio-fill::before{content:"\f14f"}.bi-aspect-ratio::before{content:"\f150"}.bi-asterisk::before{content:"\f151"}.bi-at::before{content:"\f152"}.bi-award-fill::before{content:"\f153"}.bi-award::before{content:"\f154"}.bi-back::before{content:"\f155"}.bi-backspace-fill::before{content:"\f156"}.bi-backspace-reverse-fill::before{content:"\f157"}.bi-backspace-reverse::before{content:"\f158"}.bi-backspace::before{content:"\f159"}.bi-badge-3d-fill::before{content:"\f15a"}.bi-badge-3d::before{content:"\f15b"}.bi-badge-4k-fill::before{content:"\f15c"}.bi-badge-4k::before{content:"\f15d"}.bi-badge-8k-fill::before{content:"\f15e"}.bi-badge-8k::before{content:"\f15f"}.bi-badge-ad-fill::before{content:"\f160"}.bi-badge-ad::before{content:"\f161"}.bi-badge-ar-fill::before{content:"\f162"}.bi-badge-ar::before{content:"\f163"}.bi-badge-cc-fill::before{content:"\f164"}.bi-badge-cc::before{content:"\f165"}.bi-badge-hd-fill::before{content:"\f166"}.bi-badge-hd::before{content:"\f167"}.bi-badge-tm-fill::before{content:"\f168"}.bi-badge-tm::before{content:"\f169"}.bi-badge-vo-fill::before{content:"\f16a"}.bi-badge-vo::before{content:"\f16b"}.bi-badge-vr-fill::before{content:"\f16c"}.bi-badge-vr::before{content:"\f16d"}.bi-badge-wc-fill::before{content:"\f16e"}.bi-badge-wc::before{content:"\f16f"}.bi-bag-check-fill::before{content:"\f170"}.bi-bag-check::before{content:"\f171"}.bi-bag-dash-fill::before{content:"\f172"}.bi-bag-dash::before{content:"\f173"}.bi-bag-fill::before{content:"\f174"}.bi-bag-plus-fill::before{content:"\f175"}.bi-bag-plus::before{content:"\f176"}.bi-bag-x-fill::before{content:"\f177"}.bi-bag-x::before{content:"\f178"}.bi-bag::before{content:"\f179"}.bi-bar-chart-fill::before{content:"\f17a"}.bi-bar-chart-line-fill::before{content:"\f17b"}.bi-bar-chart-line::before{content:"\f17c"}.bi-bar-chart-steps::before{content:"\f17d"}.bi-bar-chart::before{content:"\f17e"}.bi-basket-fill::before{content:"\f17f"}.bi-basket::before{content:"\f180"}.bi-basket2-fill::before{content:"\f181"}.bi-basket2::before{content:"\f182"}.bi-basket3-fill::before{content:"\f183"}.bi-basket3::before{content:"\f184"}.bi-battery-charging::before{content:"\f185"}.bi-battery-full::before{content:"\f186"}.bi-battery-half::before{content:"\f187"}.bi-battery::before{content:"\f188"}.bi-bell-fill::before{content:"\f189"}.bi-bell::before{content:"\f18a"}.bi-bezier::before{content:"\f18b"}.bi-bezier2::before{content:"\f18c"}.bi-bicycle::before{content:"\f18d"}.bi-binoculars-fill::before{content:"\f18e"}.bi-binoculars::before{content:"\f18f"}.bi-blockquote-left::before{content:"\f190"}.bi-blockquote-right::before{content:"\f191"}.bi-book-fill::before{content:"\f192"}.bi-book-half::before{content:"\f193"}.bi-book::before{content:"\f194"}.bi-bookmark-check-fill::before{content:"\f195"}.bi-bookmark-check::before{content:"\f196"}.bi-bookmark-dash-fill::before{content:"\f197"}.bi-bookmark-dash::before{content:"\f198"}.bi-bookmark-fill::before{content:"\f199"}.bi-bookmark-heart-fill::before{content:"\f19a"}.bi-bookmark-heart::before{content:"\f19b"}.bi-bookmark-plus-fill::before{content:"\f19c"}.bi-bookmark-plus::before{content:"\f19d"}.bi-bookmark-star-fill::before{content:"\f19e"}.bi-bookmark-star::before{content:"\f19f"}.bi-bookmark-x-fill::before{content:"\f1a0"}.bi-bookmark-x::before{content:"\f1a1"}.bi-bookmark::before{content:"\f1a2"}.bi-bookmarks-fill::before{content:"\f1a3"}.bi-bookmarks::before{content:"\f1a4"}.bi-bookshelf::before{content:"\f1a5"}.bi-bootstrap-fill::before{content:"\f1a6"}.bi-bootstrap-reboot::before{content:"\f1a7"}.bi-bootstrap::before{content:"\f1a8"}.bi-border-all::before{content:"\f1a9"}.bi-border-bottom::before{content:"\f1aa"}.bi-border-center::before{content:"\f1ab"}.bi-border-inner::before{content:"\f1ac"}.bi-border-left::before{content:"\f1ad"}.bi-border-middle::before{content:"\f1ae"}.bi-border-outer::before{content:"\f1af"}.bi-border-right::before{content:"\f1b0"}.bi-border-style::before{content:"\f1b1"}.bi-border-top::before{content:"\f1b2"}.bi-border-width::before{content:"\f1b3"}.bi-border::before{content:"\f1b4"}.bi-bounding-box-circles::before{content:"\f1b5"}.bi-bounding-box::before{content:"\f1b6"}.bi-box-arrow-down-left::before{content:"\f1b7"}.bi-box-arrow-down-right::before{content:"\f1b8"}.bi-box-arrow-down::before{content:"\f1b9"}.bi-box-arrow-in-down-left::before{content:"\f1ba"}.bi-box-arrow-in-down-right::before{content:"\f1bb"}.bi-box-arrow-in-down::before{content:"\f1bc"}.bi-box-arrow-in-left::before{content:"\f1bd"}.bi-box-arrow-in-right::before{content:"\f1be"}.bi-box-arrow-in-up-left::before{content:"\f1bf"}.bi-box-arrow-in-up-right::before{content:"\f1c0"}.bi-box-arrow-in-up::before{content:"\f1c1"}.bi-box-arrow-left::before{content:"\f1c2"}.bi-box-arrow-right::before{content:"\f1c3"}.bi-box-arrow-up-left::before{content:"\f1c4"}.bi-box-arrow-up-right::before{content:"\f1c5"}.bi-box-arrow-up::before{content:"\f1c6"}.bi-box-seam::before{content:"\f1c7"}.bi-box::before{content:"\f1c8"}.bi-braces::before{content:"\f1c9"}.bi-bricks::before{content:"\f1ca"}.bi-briefcase-fill::before{content:"\f1cb"}.bi-briefcase::before{content:"\f1cc"}.bi-brightness-alt-high-fill::before{content:"\f1cd"}.bi-brightness-alt-high::before{content:"\f1ce"}.bi-brightness-alt-low-fill::before{content:"\f1cf"}.bi-brightness-alt-low::before{content:"\f1d0"}.bi-brightness-high-fill::before{content:"\f1d1"}.bi-brightness-high::before{content:"\f1d2"}.bi-brightness-low-fill::before{content:"\f1d3"}.bi-brightness-low::before{content:"\f1d4"}.bi-broadcast-pin::before{content:"\f1d5"}.bi-broadcast::before{content:"\f1d6"}.bi-brush-fill::before{content:"\f1d7"}.bi-brush::before{content:"\f1d8"}.bi-bucket-fill::before{content:"\f1d9"}.bi-bucket::before{content:"\f1da"}.bi-bug-fill::before{content:"\f1db"}.bi-bug::before{content:"\f1dc"}.bi-building::before{content:"\f1dd"}.bi-bullseye::before{content:"\f1de"}.bi-calculator-fill::before{content:"\f1df"}.bi-calculator::before{content:"\f1e0"}.bi-calendar-check-fill::before{content:"\f1e1"}.bi-calendar-check::before{content:"\f1e2"}.bi-calendar-date-fill::before{content:"\f1e3"}.bi-calendar-date::before{content:"\f1e4"}.bi-calendar-day-fill::before{content:"\f1e5"}.bi-calendar-day::before{content:"\f1e6"}.bi-calendar-event-fill::before{content:"\f1e7"}.bi-calendar-event::before{content:"\f1e8"}.bi-calendar-fill::before{content:"\f1e9"}.bi-calendar-minus-fill::before{content:"\f1ea"}.bi-calendar-minus::before{content:"\f1eb"}.bi-calendar-month-fill::before{content:"\f1ec"}.bi-calendar-month::before{content:"\f1ed"}.bi-calendar-plus-fill::before{content:"\f1ee"}.bi-calendar-plus::before{content:"\f1ef"}.bi-calendar-range-fill::before{content:"\f1f0"}.bi-calendar-range::before{content:"\f1f1"}.bi-calendar-week-fill::before{content:"\f1f2"}.bi-calendar-week::before{content:"\f1f3"}.bi-calendar-x-fill::before{content:"\f1f4"}.bi-calendar-x::before{content:"\f1f5"}.bi-calendar::before{content:"\f1f6"}.bi-calendar2-check-fill::before{content:"\f1f7"}.bi-calendar2-check::before{content:"\f1f8"}.bi-calendar2-date-fill::before{content:"\f1f9"}.bi-calendar2-date::before{content:"\f1fa"}.bi-calendar2-day-fill::before{content:"\f1fb"}.bi-calendar2-day::before{content:"\f1fc"}.bi-calendar2-event-fill::before{content:"\f1fd"}.bi-calendar2-event::before{content:"\f1fe"}.bi-calendar2-fill::before{content:"\f1ff"}.bi-calendar2-minus-fill::before{content:"\f200"}.bi-calendar2-minus::before{content:"\f201"}.bi-calendar2-month-fill::before{content:"\f202"}.bi-calendar2-month::before{content:"\f203"}.bi-calendar2-plus-fill::before{content:"\f204"}.bi-calendar2-plus::before{content:"\f205"}.bi-calendar2-range-fill::before{content:"\f206"}.bi-calendar2-range::before{content:"\f207"}.bi-calendar2-week-fill::before{content:"\f208"}.bi-calendar2-week::before{content:"\f209"}.bi-calendar2-x-fill::before{content:"\f20a"}.bi-calendar2-x::before{content:"\f20b"}.bi-calendar2::before{content:"\f20c"}.bi-calendar3-event-fill::before{content:"\f20d"}.bi-calendar3-event::before{content:"\f20e"}.bi-calendar3-fill::before{content:"\f20f"}.bi-calendar3-range-fill::before{content:"\f210"}.bi-calendar3-range::before{content:"\f211"}.bi-calendar3-week-fill::before{content:"\f212"}.bi-calendar3-week::before{content:"\f213"}.bi-calendar3::before{content:"\f214"}.bi-calendar4-event::before{content:"\f215"}.bi-calendar4-range::before{content:"\f216"}.bi-calendar4-week::before{content:"\f217"}.bi-calendar4::before{content:"\f218"}.bi-camera-fill::before{content:"\f219"}.bi-camera-reels-fill::before{content:"\f21a"}.bi-camera-reels::before{content:"\f21b"}.bi-camera-video-fill::before{content:"\f21c"}.bi-camera-video-off-fill::before{content:"\f21d"}.bi-camera-video-off::before{content:"\f21e"}.bi-camera-video::before{content:"\f21f"}.bi-camera::before{content:"\f220"}.bi-camera2::before{content:"\f221"}.bi-capslock-fill::before{content:"\f222"}.bi-capslock::before{content:"\f223"}.bi-card-checklist::before{content:"\f224"}.bi-card-heading::before{content:"\f225"}.bi-card-image::before{content:"\f226"}.bi-card-list::before{content:"\f227"}.bi-card-text::before{content:"\f228"}.bi-caret-down-fill::before{content:"\f229"}.bi-caret-down-square-fill::before{content:"\f22a"}.bi-caret-down-square::before{content:"\f22b"}.bi-caret-down::before{content:"\f22c"}.bi-caret-left-fill::before{content:"\f22d"}.bi-caret-left-square-fill::before{content:"\f22e"}.bi-caret-left-square::before{content:"\f22f"}.bi-caret-left::before{content:"\f230"}.bi-caret-right-fill::before{content:"\f231"}.bi-caret-right-square-fill::before{content:"\f232"}.bi-caret-right-square::before{content:"\f233"}.bi-caret-right::before{content:"\f234"}.bi-caret-up-fill::before{content:"\f235"}.bi-caret-up-square-fill::before{content:"\f236"}.bi-caret-up-square::before{content:"\f237"}.bi-caret-up::before{content:"\f238"}.bi-cart-check-fill::before{content:"\f239"}.bi-cart-check::before{content:"\f23a"}.bi-cart-dash-fill::before{content:"\f23b"}.bi-cart-dash::before{content:"\f23c"}.bi-cart-fill::before{content:"\f23d"}.bi-cart-plus-fill::before{content:"\f23e"}.bi-cart-plus::before{content:"\f23f"}.bi-cart-x-fill::before{content:"\f240"}.bi-cart-x::before{content:"\f241"}.bi-cart::before{content:"\f242"}.bi-cart2::before{content:"\f243"}.bi-cart3::before{content:"\f244"}.bi-cart4::before{content:"\f245"}.bi-cash-stack::before{content:"\f246"}.bi-cash::before{content:"\f247"}.bi-cast::before{content:"\f248"}.bi-chat-dots-fill::before{content:"\f249"}.bi-chat-dots::before{content:"\f24a"}.bi-chat-fill::before{content:"\f24b"}.bi-chat-left-dots-fill::before{content:"\f24c"}.bi-chat-left-dots::before{content:"\f24d"}.bi-chat-left-fill::before{content:"\f24e"}.bi-chat-left-quote-fill::before{content:"\f24f"}.bi-chat-left-quote::before{content:"\f250"}.bi-chat-left-text-fill::before{content:"\f251"}.bi-chat-left-text::before{content:"\f252"}.bi-chat-left::before{content:"\f253"}.bi-chat-quote-fill::before{content:"\f254"}.bi-chat-quote::before{content:"\f255"}.bi-chat-right-dots-fill::before{content:"\f256"}.bi-chat-right-dots::before{content:"\f257"}.bi-chat-right-fill::before{content:"\f258"}.bi-chat-right-quote-fill::before{content:"\f259"}.bi-chat-right-quote::before{content:"\f25a"}.bi-chat-right-text-fill::before{content:"\f25b"}.bi-chat-right-text::before{content:"\f25c"}.bi-chat-right::before{content:"\f25d"}.bi-chat-square-dots-fill::before{content:"\f25e"}.bi-chat-square-dots::before{content:"\f25f"}.bi-chat-square-fill::before{content:"\f260"}.bi-chat-square-quote-fill::before{content:"\f261"}.bi-chat-square-quote::before{content:"\f262"}.bi-chat-square-text-fill::before{content:"\f263"}.bi-chat-square-text::before{content:"\f264"}.bi-chat-square::before{content:"\f265"}.bi-chat-text-fill::before{content:"\f266"}.bi-chat-text::before{content:"\f267"}.bi-chat::before{content:"\f268"}.bi-check-all::before{content:"\f269"}.bi-check-circle-fill::before{content:"\f26a"}.bi-check-circle::before{content:"\f26b"}.bi-check-square-fill::before{content:"\f26c"}.bi-check-square::before{content:"\f26d"}.bi-check::before{content:"\f26e"}.bi-check2-all::before{content:"\f26f"}.bi-check2-circle::before{content:"\f270"}.bi-check2-square::before{content:"\f271"}.bi-check2::before{content:"\f272"}.bi-chevron-bar-contract::before{content:"\f273"}.bi-chevron-bar-down::before{content:"\f274"}.bi-chevron-bar-expand::before{content:"\f275"}.bi-chevron-bar-left::before{content:"\f276"}.bi-chevron-bar-right::before{content:"\f277"}.bi-chevron-bar-up::before{content:"\f278"}.bi-chevron-compact-down::before{content:"\f279"}.bi-chevron-compact-left::before{content:"\f27a"}.bi-chevron-compact-right::before{content:"\f27b"}.bi-chevron-compact-up::before{content:"\f27c"}.bi-chevron-contract::before{content:"\f27d"}.bi-chevron-double-down::before{content:"\f27e"}.bi-chevron-double-left::before{content:"\f27f"}.bi-chevron-double-right::before{content:"\f280"}.bi-chevron-double-up::before{content:"\f281"}.bi-chevron-down::before{content:"\f282"}.bi-chevron-expand::before{content:"\f283"}.bi-chevron-left::before{content:"\f284"}.bi-chevron-right::before{content:"\f285"}.bi-chevron-up::before{content:"\f286"}.bi-circle-fill::before{content:"\f287"}.bi-circle-half::before{content:"\f288"}.bi-circle-square::before{content:"\f289"}.bi-circle::before{content:"\f28a"}.bi-clipboard-check::before{content:"\f28b"}.bi-clipboard-data::before{content:"\f28c"}.bi-clipboard-minus::before{content:"\f28d"}.bi-clipboard-plus::before{content:"\f28e"}.bi-clipboard-x::before{content:"\f28f"}.bi-clipboard::before{content:"\f290"}.bi-clock-fill::before{content:"\f291"}.bi-clock-history::before{content:"\f292"}.bi-clock::before{content:"\f293"}.bi-cloud-arrow-down-fill::before{content:"\f294"}.bi-cloud-arrow-down::before{content:"\f295"}.bi-cloud-arrow-up-fill::before{content:"\f296"}.bi-cloud-arrow-up::before{content:"\f297"}.bi-cloud-check-fill::before{content:"\f298"}.bi-cloud-check::before{content:"\f299"}.bi-cloud-download-fill::before{content:"\f29a"}.bi-cloud-download::before{content:"\f29b"}.bi-cloud-drizzle-fill::before{content:"\f29c"}.bi-cloud-drizzle::before{content:"\f29d"}.bi-cloud-fill::before{content:"\f29e"}.bi-cloud-fog-fill::before{content:"\f29f"}.bi-cloud-fog::before{content:"\f2a0"}.bi-cloud-fog2-fill::before{content:"\f2a1"}.bi-cloud-fog2::before{content:"\f2a2"}.bi-cloud-hail-fill::before{content:"\f2a3"}.bi-cloud-hail::before{content:"\f2a4"}.bi-cloud-haze-fill::before{content:"\f2a6"}.bi-cloud-haze::before{content:"\f2a7"}.bi-cloud-haze2-fill::before{content:"\f2a8"}.bi-cloud-lightning-fill::before{content:"\f2a9"}.bi-cloud-lightning-rain-fill::before{content:"\f2aa"}.bi-cloud-lightning-rain::before{content:"\f2ab"}.bi-cloud-lightning::before{content:"\f2ac"}.bi-cloud-minus-fill::before{content:"\f2ad"}.bi-cloud-minus::before{content:"\f2ae"}.bi-cloud-moon-fill::before{content:"\f2af"}.bi-cloud-moon::before{content:"\f2b0"}.bi-cloud-plus-fill::before{content:"\f2b1"}.bi-cloud-plus::before{content:"\f2b2"}.bi-cloud-rain-fill::before{content:"\f2b3"}.bi-cloud-rain-heavy-fill::before{content:"\f2b4"}.bi-cloud-rain-heavy::before{content:"\f2b5"}.bi-cloud-rain::before{content:"\f2b6"}.bi-cloud-slash-fill::before{content:"\f2b7"}.bi-cloud-slash::before{content:"\f2b8"}.bi-cloud-sleet-fill::before{content:"\f2b9"}.bi-cloud-sleet::before{content:"\f2ba"}.bi-cloud-snow-fill::before{content:"\f2bb"}.bi-cloud-snow::before{content:"\f2bc"}.bi-cloud-sun-fill::before{content:"\f2bd"}.bi-cloud-sun::before{content:"\f2be"}.bi-cloud-upload-fill::before{content:"\f2bf"}.bi-cloud-upload::before{content:"\f2c0"}.bi-cloud::before{content:"\f2c1"}.bi-clouds-fill::before{content:"\f2c2"}.bi-clouds::before{content:"\f2c3"}.bi-cloudy-fill::before{content:"\f2c4"}.bi-cloudy::before{content:"\f2c5"}.bi-code-slash::before{content:"\f2c6"}.bi-code-square::before{content:"\f2c7"}.bi-code::before{content:"\f2c8"}.bi-collection-fill::before{content:"\f2c9"}.bi-collection-play-fill::before{content:"\f2ca"}.bi-collection-play::before{content:"\f2cb"}.bi-collection::before{content:"\f2cc"}.bi-columns-gap::before{content:"\f2cd"}.bi-columns::before{content:"\f2ce"}.bi-command::before{content:"\f2cf"}.bi-compass-fill::before{content:"\f2d0"}.bi-compass::before{content:"\f2d1"}.bi-cone-striped::before{content:"\f2d2"}.bi-cone::before{content:"\f2d3"}.bi-controller::before{content:"\f2d4"}.bi-cpu-fill::before{content:"\f2d5"}.bi-cpu::before{content:"\f2d6"}.bi-credit-card-2-back-fill::before{content:"\f2d7"}.bi-credit-card-2-back::before{content:"\f2d8"}.bi-credit-card-2-front-fill::before{content:"\f2d9"}.bi-credit-card-2-front::before{content:"\f2da"}.bi-credit-card-fill::before{content:"\f2db"}.bi-credit-card::before{content:"\f2dc"}.bi-crop::before{content:"\f2dd"}.bi-cup-fill::before{content:"\f2de"}.bi-cup-straw::before{content:"\f2df"}.bi-cup::before{content:"\f2e0"}.bi-cursor-fill::before{content:"\f2e1"}.bi-cursor-text::before{content:"\f2e2"}.bi-cursor::before{content:"\f2e3"}.bi-dash-circle-dotted::before{content:"\f2e4"}.bi-dash-circle-fill::before{content:"\f2e5"}.bi-dash-circle::before{content:"\f2e6"}.bi-dash-square-dotted::before{content:"\f2e7"}.bi-dash-square-fill::before{content:"\f2e8"}.bi-dash-square::before{content:"\f2e9"}.bi-dash::before{content:"\f2ea"}.bi-diagram-2-fill::before{content:"\f2eb"}.bi-diagram-2::before{content:"\f2ec"}.bi-diagram-3-fill::before{content:"\f2ed"}.bi-diagram-3::before{content:"\f2ee"}.bi-diamond-fill::before{content:"\f2ef"}.bi-diamond-half::before{content:"\f2f0"}.bi-diamond::before{content:"\f2f1"}.bi-dice-1-fill::before{content:"\f2f2"}.bi-dice-1::before{content:"\f2f3"}.bi-dice-2-fill::before{content:"\f2f4"}.bi-dice-2::before{content:"\f2f5"}.bi-dice-3-fill::before{content:"\f2f6"}.bi-dice-3::before{content:"\f2f7"}.bi-dice-4-fill::before{content:"\f2f8"}.bi-dice-4::before{content:"\f2f9"}.bi-dice-5-fill::before{content:"\f2fa"}.bi-dice-5::before{content:"\f2fb"}.bi-dice-6-fill::before{content:"\f2fc"}.bi-dice-6::before{content:"\f2fd"}.bi-disc-fill::before{content:"\f2fe"}.bi-disc::before{content:"\f2ff"}.bi-discord::before{content:"\f300"}.bi-display-fill::before{content:"\f301"}.bi-display::before{content:"\f302"}.bi-distribute-horizontal::before{content:"\f303"}.bi-distribute-vertical::before{content:"\f304"}.bi-door-closed-fill::before{content:"\f305"}.bi-door-closed::before{content:"\f306"}.bi-door-open-fill::before{content:"\f307"}.bi-door-open::before{content:"\f308"}.bi-dot::before{content:"\f309"}.bi-download::before{content:"\f30a"}.bi-droplet-fill::before{content:"\f30b"}.bi-droplet-half::before{content:"\f30c"}.bi-droplet::before{content:"\f30d"}.bi-earbuds::before{content:"\f30e"}.bi-easel-fill::before{content:"\f30f"}.bi-easel::before{content:"\f310"}.bi-egg-fill::before{content:"\f311"}.bi-egg-fried::before{content:"\f312"}.bi-egg::before{content:"\f313"}.bi-eject-fill::before{content:"\f314"}.bi-eject::before{content:"\f315"}.bi-emoji-angry-fill::before{content:"\f316"}.bi-emoji-angry::before{content:"\f317"}.bi-emoji-dizzy-fill::before{content:"\f318"}.bi-emoji-dizzy::before{content:"\f319"}.bi-emoji-expressionless-fill::before{content:"\f31a"}.bi-emoji-expressionless::before{content:"\f31b"}.bi-emoji-frown-fill::before{content:"\f31c"}.bi-emoji-frown::before{content:"\f31d"}.bi-emoji-heart-eyes-fill::before{content:"\f31e"}.bi-emoji-heart-eyes::before{content:"\f31f"}.bi-emoji-laughing-fill::before{content:"\f320"}.bi-emoji-laughing::before{content:"\f321"}.bi-emoji-neutral-fill::before{content:"\f322"}.bi-emoji-neutral::before{content:"\f323"}.bi-emoji-smile-fill::before{content:"\f324"}.bi-emoji-smile-upside-down-fill::before{content:"\f325"}.bi-emoji-smile-upside-down::before{content:"\f326"}.bi-emoji-smile::before{content:"\f327"}.bi-emoji-sunglasses-fill::before{content:"\f328"}.bi-emoji-sunglasses::before{content:"\f329"}.bi-emoji-wink-fill::before{content:"\f32a"}.bi-emoji-wink::before{content:"\f32b"}.bi-envelope-fill::before{content:"\f32c"}.bi-envelope-open-fill::before{content:"\f32d"}.bi-envelope-open::before{content:"\f32e"}.bi-envelope::before{content:"\f32f"}.bi-eraser-fill::before{content:"\f330"}.bi-eraser::before{content:"\f331"}.bi-exclamation-circle-fill::before{content:"\f332"}.bi-exclamation-circle::before{content:"\f333"}.bi-exclamation-diamond-fill::before{content:"\f334"}.bi-exclamation-diamond::before{content:"\f335"}.bi-exclamation-octagon-fill::before{content:"\f336"}.bi-exclamation-octagon::before{content:"\f337"}.bi-exclamation-square-fill::before{content:"\f338"}.bi-exclamation-square::before{content:"\f339"}.bi-exclamation-triangle-fill::before{content:"\f33a"}.bi-exclamation-triangle::before{content:"\f33b"}.bi-exclamation::before{content:"\f33c"}.bi-exclude::before{content:"\f33d"}.bi-eye-fill::before{content:"\f33e"}.bi-eye-slash-fill::before{content:"\f33f"}.bi-eye-slash::before{content:"\f340"}.bi-eye::before{content:"\f341"}.bi-eyedropper::before{content:"\f342"}.bi-eyeglasses::before{content:"\f343"}.bi-facebook::before{content:"\f344"}.bi-file-arrow-down-fill::before{content:"\f345"}.bi-file-arrow-down::before{content:"\f346"}.bi-file-arrow-up-fill::before{content:"\f347"}.bi-file-arrow-up::before{content:"\f348"}.bi-file-bar-graph-fill::before{content:"\f349"}.bi-file-bar-graph::before{content:"\f34a"}.bi-file-binary-fill::before{content:"\f34b"}.bi-file-binary::before{content:"\f34c"}.bi-file-break-fill::before{content:"\f34d"}.bi-file-break::before{content:"\f34e"}.bi-file-check-fill::before{content:"\f34f"}.bi-file-check::before{content:"\f350"}.bi-file-code-fill::before{content:"\f351"}.bi-file-code::before{content:"\f352"}.bi-file-diff-fill::before{content:"\f353"}.bi-file-diff::before{content:"\f354"}.bi-file-earmark-arrow-down-fill::before{content:"\f355"}.bi-file-earmark-arrow-down::before{content:"\f356"}.bi-file-earmark-arrow-up-fill::before{content:"\f357"}.bi-file-earmark-arrow-up::before{content:"\f358"}.bi-file-earmark-bar-graph-fill::before{content:"\f359"}.bi-file-earmark-bar-graph::before{content:"\f35a"}.bi-file-earmark-binary-fill::before{content:"\f35b"}.bi-file-earmark-binary::before{content:"\f35c"}.bi-file-earmark-break-fill::before{content:"\f35d"}.bi-file-earmark-break::before{content:"\f35e"}.bi-file-earmark-check-fill::before{content:"\f35f"}.bi-file-earmark-check::before{content:"\f360"}.bi-file-earmark-code-fill::before{content:"\f361"}.bi-file-earmark-code::before{content:"\f362"}.bi-file-earmark-diff-fill::before{content:"\f363"}.bi-file-earmark-diff::before{content:"\f364"}.bi-file-earmark-easel-fill::before{content:"\f365"}.bi-file-earmark-easel::before{content:"\f366"}.bi-file-earmark-excel-fill::before{content:"\f367"}.bi-file-earmark-excel::before{content:"\f368"}.bi-file-earmark-fill::before{content:"\f369"}.bi-file-earmark-font-fill::before{content:"\f36a"}.bi-file-earmark-font::before{content:"\f36b"}.bi-file-earmark-image-fill::before{content:"\f36c"}.bi-file-earmark-image::before{content:"\f36d"}.bi-file-earmark-lock-fill::before{content:"\f36e"}.bi-file-earmark-lock::before{content:"\f36f"}.bi-file-earmark-lock2-fill::before{content:"\f370"}.bi-file-earmark-lock2::before{content:"\f371"}.bi-file-earmark-medical-fill::before{content:"\f372"}.bi-file-earmark-medical::before{content:"\f373"}.bi-file-earmark-minus-fill::before{content:"\f374"}.bi-file-earmark-minus::before{content:"\f375"}.bi-file-earmark-music-fill::before{content:"\f376"}.bi-file-earmark-music::before{content:"\f377"}.bi-file-earmark-person-fill::before{content:"\f378"}.bi-file-earmark-person::before{content:"\f379"}.bi-file-earmark-play-fill::before{content:"\f37a"}.bi-file-earmark-play::before{content:"\f37b"}.bi-file-earmark-plus-fill::before{content:"\f37c"}.bi-file-earmark-plus::before{content:"\f37d"}.bi-file-earmark-post-fill::before{content:"\f37e"}.bi-file-earmark-post::before{content:"\f37f"}.bi-file-earmark-ppt-fill::before{content:"\f380"}.bi-file-earmark-ppt::before{content:"\f381"}.bi-file-earmark-richtext-fill::before{content:"\f382"}.bi-file-earmark-richtext::before{content:"\f383"}.bi-file-earmark-ruled-fill::before{content:"\f384"}.bi-file-earmark-ruled::before{content:"\f385"}.bi-file-earmark-slides-fill::before{content:"\f386"}.bi-file-earmark-slides::before{content:"\f387"}.bi-file-earmark-spreadsheet-fill::before{content:"\f388"}.bi-file-earmark-spreadsheet::before{content:"\f389"}.bi-file-earmark-text-fill::before{content:"\f38a"}.bi-file-earmark-text::before{content:"\f38b"}.bi-file-earmark-word-fill::before{content:"\f38c"}.bi-file-earmark-word::before{content:"\f38d"}.bi-file-earmark-x-fill::before{content:"\f38e"}.bi-file-earmark-x::before{content:"\f38f"}.bi-file-earmark-zip-fill::before{content:"\f390"}.bi-file-earmark-zip::before{content:"\f391"}.bi-file-earmark::before{content:"\f392"}.bi-file-easel-fill::before{content:"\f393"}.bi-file-easel::before{content:"\f394"}.bi-file-excel-fill::before{content:"\f395"}.bi-file-excel::before{content:"\f396"}.bi-file-fill::before{content:"\f397"}.bi-file-font-fill::before{content:"\f398"}.bi-file-font::before{content:"\f399"}.bi-file-image-fill::before{content:"\f39a"}.bi-file-image::before{content:"\f39b"}.bi-file-lock-fill::before{content:"\f39c"}.bi-file-lock::before{content:"\f39d"}.bi-file-lock2-fill::before{content:"\f39e"}.bi-file-lock2::before{content:"\f39f"}.bi-file-medical-fill::before{content:"\f3a0"}.bi-file-medical::before{content:"\f3a1"}.bi-file-minus-fill::before{content:"\f3a2"}.bi-file-minus::before{content:"\f3a3"}.bi-file-music-fill::before{content:"\f3a4"}.bi-file-music::before{content:"\f3a5"}.bi-file-person-fill::before{content:"\f3a6"}.bi-file-person::before{content:"\f3a7"}.bi-file-play-fill::before{content:"\f3a8"}.bi-file-play::before{content:"\f3a9"}.bi-file-plus-fill::before{content:"\f3aa"}.bi-file-plus::before{content:"\f3ab"}.bi-file-post-fill::before{content:"\f3ac"}.bi-file-post::before{content:"\f3ad"}.bi-file-ppt-fill::before{content:"\f3ae"}.bi-file-ppt::before{content:"\f3af"}.bi-file-richtext-fill::before{content:"\f3b0"}.bi-file-richtext::before{content:"\f3b1"}.bi-file-ruled-fill::before{content:"\f3b2"}.bi-file-ruled::before{content:"\f3b3"}.bi-file-slides-fill::before{content:"\f3b4"}.bi-file-slides::before{content:"\f3b5"}.bi-file-spreadsheet-fill::before{content:"\f3b6"}.bi-file-spreadsheet::before{content:"\f3b7"}.bi-file-text-fill::before{content:"\f3b8"}.bi-file-text::before{content:"\f3b9"}.bi-file-word-fill::before{content:"\f3ba"}.bi-file-word::before{content:"\f3bb"}.bi-file-x-fill::before{content:"\f3bc"}.bi-file-x::before{content:"\f3bd"}.bi-file-zip-fill::before{content:"\f3be"}.bi-file-zip::before{content:"\f3bf"}.bi-file::before{content:"\f3c0"}.bi-files-alt::before{content:"\f3c1"}.bi-files::before{content:"\f3c2"}.bi-film::before{content:"\f3c3"}.bi-filter-circle-fill::before{content:"\f3c4"}.bi-filter-circle::before{content:"\f3c5"}.bi-filter-left::before{content:"\f3c6"}.bi-filter-right::before{content:"\f3c7"}.bi-filter-square-fill::before{content:"\f3c8"}.bi-filter-square::before{content:"\f3c9"}.bi-filter::before{content:"\f3ca"}.bi-flag-fill::before{content:"\f3cb"}.bi-flag::before{content:"\f3cc"}.bi-flower1::before{content:"\f3cd"}.bi-flower2::before{content:"\f3ce"}.bi-flower3::before{content:"\f3cf"}.bi-folder-check::before{content:"\f3d0"}.bi-folder-fill::before{content:"\f3d1"}.bi-folder-minus::before{content:"\f3d2"}.bi-folder-plus::before{content:"\f3d3"}.bi-folder-symlink-fill::before{content:"\f3d4"}.bi-folder-symlink::before{content:"\f3d5"}.bi-folder-x::before{content:"\f3d6"}.bi-folder::before{content:"\f3d7"}.bi-folder2-open::before{content:"\f3d8"}.bi-folder2::before{content:"\f3d9"}.bi-fonts::before{content:"\f3da"}.bi-forward-fill::before{content:"\f3db"}.bi-forward::before{content:"\f3dc"}.bi-front::before{content:"\f3dd"}.bi-fullscreen-exit::before{content:"\f3de"}.bi-fullscreen::before{content:"\f3df"}.bi-funnel-fill::before{content:"\f3e0"}.bi-funnel::before{content:"\f3e1"}.bi-gear-fill::before{content:"\f3e2"}.bi-gear-wide-connected::before{content:"\f3e3"}.bi-gear-wide::before{content:"\f3e4"}.bi-gear::before{content:"\f3e5"}.bi-gem::before{content:"\f3e6"}.bi-geo-alt-fill::before{content:"\f3e7"}.bi-geo-alt::before{content:"\f3e8"}.bi-geo-fill::before{content:"\f3e9"}.bi-geo::before{content:"\f3ea"}.bi-gift-fill::before{content:"\f3eb"}.bi-gift::before{content:"\f3ec"}.bi-github::before{content:"\f3ed"}.bi-globe::before{content:"\f3ee"}.bi-globe2::before{content:"\f3ef"}.bi-google::before{content:"\f3f0"}.bi-graph-down::before{content:"\f3f1"}.bi-graph-up::before{content:"\f3f2"}.bi-grid-1x2-fill::before{content:"\f3f3"}.bi-grid-1x2::before{content:"\f3f4"}.bi-grid-3x2-gap-fill::before{content:"\f3f5"}.bi-grid-3x2-gap::before{content:"\f3f6"}.bi-grid-3x2::before{content:"\f3f7"}.bi-grid-3x3-gap-fill::before{content:"\f3f8"}.bi-grid-3x3-gap::before{content:"\f3f9"}.bi-grid-3x3::before{content:"\f3fa"}.bi-grid-fill::before{content:"\f3fb"}.bi-grid::before{content:"\f3fc"}.bi-grip-horizontal::before{content:"\f3fd"}.bi-grip-vertical::before{content:"\f3fe"}.bi-hammer::before{content:"\f3ff"}.bi-hand-index-fill::before{content:"\f400"}.bi-hand-index-thumb-fill::before{content:"\f401"}.bi-hand-index-thumb::before{content:"\f402"}.bi-hand-index::before{content:"\f403"}.bi-hand-thumbs-down-fill::before{content:"\f404"}.bi-hand-thumbs-down::before{content:"\f405"}.bi-hand-thumbs-up-fill::before{content:"\f406"}.bi-hand-thumbs-up::before{content:"\f407"}.bi-handbag-fill::before{content:"\f408"}.bi-handbag::before{content:"\f409"}.bi-hash::before{content:"\f40a"}.bi-hdd-fill::before{content:"\f40b"}.bi-hdd-network-fill::before{content:"\f40c"}.bi-hdd-network::before{content:"\f40d"}.bi-hdd-rack-fill::before{content:"\f40e"}.bi-hdd-rack::before{content:"\f40f"}.bi-hdd-stack-fill::before{content:"\f410"}.bi-hdd-stack::before{content:"\f411"}.bi-hdd::before{content:"\f412"}.bi-headphones::before{content:"\f413"}.bi-headset::before{content:"\f414"}.bi-heart-fill::before{content:"\f415"}.bi-heart-half::before{content:"\f416"}.bi-heart::before{content:"\f417"}.bi-heptagon-fill::before{content:"\f418"}.bi-heptagon-half::before{content:"\f419"}.bi-heptagon::before{content:"\f41a"}.bi-hexagon-fill::before{content:"\f41b"}.bi-hexagon-half::before{content:"\f41c"}.bi-hexagon::before{content:"\f41d"}.bi-hourglass-bottom::before{content:"\f41e"}.bi-hourglass-split::before{content:"\f41f"}.bi-hourglass-top::before{content:"\f420"}.bi-hourglass::before{content:"\f421"}.bi-house-door-fill::before{content:"\f422"}.bi-house-door::before{content:"\f423"}.bi-house-fill::before{content:"\f424"}.bi-house::before{content:"\f425"}.bi-hr::before{content:"\f426"}.bi-hurricane::before{content:"\f427"}.bi-image-alt::before{content:"\f428"}.bi-image-fill::before{content:"\f429"}.bi-image::before{content:"\f42a"}.bi-images::before{content:"\f42b"}.bi-inbox-fill::before{content:"\f42c"}.bi-inbox::before{content:"\f42d"}.bi-inboxes-fill::before{content:"\f42e"}.bi-inboxes::before{content:"\f42f"}.bi-info-circle-fill::before{content:"\f430"}.bi-info-circle::before{content:"\f431"}.bi-info-square-fill::before{content:"\f432"}.bi-info-square::before{content:"\f433"}.bi-info::before{content:"\f434"}.bi-input-cursor-text::before{content:"\f435"}.bi-input-cursor::before{content:"\f436"}.bi-instagram::before{content:"\f437"}.bi-intersect::before{content:"\f438"}.bi-journal-album::before{content:"\f439"}.bi-journal-arrow-down::before{content:"\f43a"}.bi-journal-arrow-up::before{content:"\f43b"}.bi-journal-bookmark-fill::before{content:"\f43c"}.bi-journal-bookmark::before{content:"\f43d"}.bi-journal-check::before{content:"\f43e"}.bi-journal-code::before{content:"\f43f"}.bi-journal-medical::before{content:"\f440"}.bi-journal-minus::before{content:"\f441"}.bi-journal-plus::before{content:"\f442"}.bi-journal-richtext::before{content:"\f443"}.bi-journal-text::before{content:"\f444"}.bi-journal-x::before{content:"\f445"}.bi-journal::before{content:"\f446"}.bi-journals::before{content:"\f447"}.bi-joystick::before{content:"\f448"}.bi-justify-left::before{content:"\f449"}.bi-justify-right::before{content:"\f44a"}.bi-justify::before{content:"\f44b"}.bi-kanban-fill::before{content:"\f44c"}.bi-kanban::before{content:"\f44d"}.bi-key-fill::before{content:"\f44e"}.bi-key::before{content:"\f44f"}.bi-keyboard-fill::before{content:"\f450"}.bi-keyboard::before{content:"\f451"}.bi-ladder::before{content:"\f452"}.bi-lamp-fill::before{content:"\f453"}.bi-lamp::before{content:"\f454"}.bi-laptop-fill::before{content:"\f455"}.bi-laptop::before{content:"\f456"}.bi-layer-backward::before{content:"\f457"}.bi-layer-forward::before{content:"\f458"}.bi-layers-fill::before{content:"\f459"}.bi-layers-half::before{content:"\f45a"}.bi-layers::before{content:"\f45b"}.bi-layout-sidebar-inset-reverse::before{content:"\f45c"}.bi-layout-sidebar-inset::before{content:"\f45d"}.bi-layout-sidebar-reverse::before{content:"\f45e"}.bi-layout-sidebar::before{content:"\f45f"}.bi-layout-split::before{content:"\f460"}.bi-layout-text-sidebar-reverse::before{content:"\f461"}.bi-layout-text-sidebar::before{content:"\f462"}.bi-layout-text-window-reverse::before{content:"\f463"}.bi-layout-text-window::before{content:"\f464"}.bi-layout-three-columns::before{content:"\f465"}.bi-layout-wtf::before{content:"\f466"}.bi-life-preserver::before{content:"\f467"}.bi-lightbulb-fill::before{content:"\f468"}.bi-lightbulb-off-fill::before{content:"\f469"}.bi-lightbulb-off::before{content:"\f46a"}.bi-lightbulb::before{content:"\f46b"}.bi-lightning-charge-fill::before{content:"\f46c"}.bi-lightning-charge::before{content:"\f46d"}.bi-lightning-fill::before{content:"\f46e"}.bi-lightning::before{content:"\f46f"}.bi-link-45deg::before{content:"\f470"}.bi-link::before{content:"\f471"}.bi-linkedin::before{content:"\f472"}.bi-list-check::before{content:"\f473"}.bi-list-nested::before{content:"\f474"}.bi-list-ol::before{content:"\f475"}.bi-list-stars::before{content:"\f476"}.bi-list-task::before{content:"\f477"}.bi-list-ul::before{content:"\f478"}.bi-list::before{content:"\f479"}.bi-lock-fill::before{content:"\f47a"}.bi-lock::before{content:"\f47b"}.bi-mailbox::before{content:"\f47c"}.bi-mailbox2::before{content:"\f47d"}.bi-map-fill::before{content:"\f47e"}.bi-map::before{content:"\f47f"}.bi-markdown-fill::before{content:"\f480"}.bi-markdown::before{content:"\f481"}.bi-mask::before{content:"\f482"}.bi-megaphone-fill::before{content:"\f483"}.bi-megaphone::before{content:"\f484"}.bi-menu-app-fill::before{content:"\f485"}.bi-menu-app::before{content:"\f486"}.bi-menu-button-fill::before{content:"\f487"}.bi-menu-button-wide-fill::before{content:"\f488"}.bi-menu-button-wide::before{content:"\f489"}.bi-menu-button::before{content:"\f48a"}.bi-menu-down::before{content:"\f48b"}.bi-menu-up::before{content:"\f48c"}.bi-mic-fill::before{content:"\f48d"}.bi-mic-mute-fill::before{content:"\f48e"}.bi-mic-mute::before{content:"\f48f"}.bi-mic::before{content:"\f490"}.bi-minecart-loaded::before{content:"\f491"}.bi-minecart::before{content:"\f492"}.bi-moisture::before{content:"\f493"}.bi-moon-fill::before{content:"\f494"}.bi-moon-stars-fill::before{content:"\f495"}.bi-moon-stars::before{content:"\f496"}.bi-moon::before{content:"\f497"}.bi-mouse-fill::before{content:"\f498"}.bi-mouse::before{content:"\f499"}.bi-mouse2-fill::before{content:"\f49a"}.bi-mouse2::before{content:"\f49b"}.bi-mouse3-fill::before{content:"\f49c"}.bi-mouse3::before{content:"\f49d"}.bi-music-note-beamed::before{content:"\f49e"}.bi-music-note-list::before{content:"\f49f"}.bi-music-note::before{content:"\f4a0"}.bi-music-player-fill::before{content:"\f4a1"}.bi-music-player::before{content:"\f4a2"}.bi-newspaper::before{content:"\f4a3"}.bi-node-minus-fill::before{content:"\f4a4"}.bi-node-minus::before{content:"\f4a5"}.bi-node-plus-fill::before{content:"\f4a6"}.bi-node-plus::before{content:"\f4a7"}.bi-nut-fill::before{content:"\f4a8"}.bi-nut::before{content:"\f4a9"}.bi-octagon-fill::before{content:"\f4aa"}.bi-octagon-half::before{content:"\f4ab"}.bi-octagon::before{content:"\f4ac"}.bi-option::before{content:"\f4ad"}.bi-outlet::before{content:"\f4ae"}.bi-paint-bucket::before{content:"\f4af"}.bi-palette-fill::before{content:"\f4b0"}.bi-palette::before{content:"\f4b1"}.bi-palette2::before{content:"\f4b2"}.bi-paperclip::before{content:"\f4b3"}.bi-paragraph::before{content:"\f4b4"}.bi-patch-check-fill::before{content:"\f4b5"}.bi-patch-check::before{content:"\f4b6"}.bi-patch-exclamation-fill::before{content:"\f4b7"}.bi-patch-exclamation::before{content:"\f4b8"}.bi-patch-minus-fill::before{content:"\f4b9"}.bi-patch-minus::before{content:"\f4ba"}.bi-patch-plus-fill::before{content:"\f4bb"}.bi-patch-plus::before{content:"\f4bc"}.bi-patch-question-fill::before{content:"\f4bd"}.bi-patch-question::before{content:"\f4be"}.bi-pause-btn-fill::before{content:"\f4bf"}.bi-pause-btn::before{content:"\f4c0"}.bi-pause-circle-fill::before{content:"\f4c1"}.bi-pause-circle::before{content:"\f4c2"}.bi-pause-fill::before{content:"\f4c3"}.bi-pause::before{content:"\f4c4"}.bi-peace-fill::before{content:"\f4c5"}.bi-peace::before{content:"\f4c6"}.bi-pen-fill::before{content:"\f4c7"}.bi-pen::before{content:"\f4c8"}.bi-pencil-fill::before{content:"\f4c9"}.bi-pencil-square::before{content:"\f4ca"}.bi-pencil::before{content:"\f4cb"}.bi-pentagon-fill::before{content:"\f4cc"}.bi-pentagon-half::before{content:"\f4cd"}.bi-pentagon::before{content:"\f4ce"}.bi-people-fill::before{content:"\f4cf"}.bi-people::before{content:"\f4d0"}.bi-percent::before{content:"\f4d1"}.bi-person-badge-fill::before{content:"\f4d2"}.bi-person-badge::before{content:"\f4d3"}.bi-person-bounding-box::before{content:"\f4d4"}.bi-person-check-fill::before{content:"\f4d5"}.bi-person-check::before{content:"\f4d6"}.bi-person-circle::before{content:"\f4d7"}.bi-person-dash-fill::before{content:"\f4d8"}.bi-person-dash::before{content:"\f4d9"}.bi-person-fill::before{content:"\f4da"}.bi-person-lines-fill::before{content:"\f4db"}.bi-person-plus-fill::before{content:"\f4dc"}.bi-person-plus::before{content:"\f4dd"}.bi-person-square::before{content:"\f4de"}.bi-person-x-fill::before{content:"\f4df"}.bi-person-x::before{content:"\f4e0"}.bi-person::before{content:"\f4e1"}.bi-phone-fill::before{content:"\f4e2"}.bi-phone-landscape-fill::before{content:"\f4e3"}.bi-phone-landscape::before{content:"\f4e4"}.bi-phone-vibrate-fill::before{content:"\f4e5"}.bi-phone-vibrate::before{content:"\f4e6"}.bi-phone::before{content:"\f4e7"}.bi-pie-chart-fill::before{content:"\f4e8"}.bi-pie-chart::before{content:"\f4e9"}.bi-pin-angle-fill::before{content:"\f4ea"}.bi-pin-angle::before{content:"\f4eb"}.bi-pin-fill::before{content:"\f4ec"}.bi-pin::before{content:"\f4ed"}.bi-pip-fill::before{content:"\f4ee"}.bi-pip::before{content:"\f4ef"}.bi-play-btn-fill::before{content:"\f4f0"}.bi-play-btn::before{content:"\f4f1"}.bi-play-circle-fill::before{content:"\f4f2"}.bi-play-circle::before{content:"\f4f3"}.bi-play-fill::before{content:"\f4f4"}.bi-play::before{content:"\f4f5"}.bi-plug-fill::before{content:"\f4f6"}.bi-plug::before{content:"\f4f7"}.bi-plus-circle-dotted::before{content:"\f4f8"}.bi-plus-circle-fill::before{content:"\f4f9"}.bi-plus-circle::before{content:"\f4fa"}.bi-plus-square-dotted::before{content:"\f4fb"}.bi-plus-square-fill::before{content:"\f4fc"}.bi-plus-square::before{content:"\f4fd"}.bi-plus::before{content:"\f4fe"}.bi-power::before{content:"\f4ff"}.bi-printer-fill::before{content:"\f500"}.bi-printer::before{content:"\f501"}.bi-puzzle-fill::before{content:"\f502"}.bi-puzzle::before{content:"\f503"}.bi-question-circle-fill::before{content:"\f504"}.bi-question-circle::before{content:"\f505"}.bi-question-diamond-fill::before{content:"\f506"}.bi-question-diamond::before{content:"\f507"}.bi-question-octagon-fill::before{content:"\f508"}.bi-question-octagon::before{content:"\f509"}.bi-question-square-fill::before{content:"\f50a"}.bi-question-square::before{content:"\f50b"}.bi-question::before{content:"\f50c"}.bi-rainbow::before{content:"\f50d"}.bi-receipt-cutoff::before{content:"\f50e"}.bi-receipt::before{content:"\f50f"}.bi-reception-0::before{content:"\f510"}.bi-reception-1::before{content:"\f511"}.bi-reception-2::before{content:"\f512"}.bi-reception-3::before{content:"\f513"}.bi-reception-4::before{content:"\f514"}.bi-record-btn-fill::before{content:"\f515"}.bi-record-btn::before{content:"\f516"}.bi-record-circle-fill::before{content:"\f517"}.bi-record-circle::before{content:"\f518"}.bi-record-fill::before{content:"\f519"}.bi-record::before{content:"\f51a"}.bi-record2-fill::before{content:"\f51b"}.bi-record2::before{content:"\f51c"}.bi-reply-all-fill::before{content:"\f51d"}.bi-reply-all::before{content:"\f51e"}.bi-reply-fill::before{content:"\f51f"}.bi-reply::before{content:"\f520"}.bi-rss-fill::before{content:"\f521"}.bi-rss::before{content:"\f522"}.bi-rulers::before{content:"\f523"}.bi-save-fill::before{content:"\f524"}.bi-save::before{content:"\f525"}.bi-save2-fill::before{content:"\f526"}.bi-save2::before{content:"\f527"}.bi-scissors::before{content:"\f528"}.bi-screwdriver::before{content:"\f529"}.bi-search::before{content:"\f52a"}.bi-segmented-nav::before{content:"\f52b"}.bi-server::before{content:"\f52c"}.bi-share-fill::before{content:"\f52d"}.bi-share::before{content:"\f52e"}.bi-shield-check::before{content:"\f52f"}.bi-shield-exclamation::before{content:"\f530"}.bi-shield-fill-check::before{content:"\f531"}.bi-shield-fill-exclamation::before{content:"\f532"}.bi-shield-fill-minus::before{content:"\f533"}.bi-shield-fill-plus::before{content:"\f534"}.bi-shield-fill-x::before{content:"\f535"}.bi-shield-fill::before{content:"\f536"}.bi-shield-lock-fill::before{content:"\f537"}.bi-shield-lock::before{content:"\f538"}.bi-shield-minus::before{content:"\f539"}.bi-shield-plus::before{content:"\f53a"}.bi-shield-shaded::before{content:"\f53b"}.bi-shield-slash-fill::before{content:"\f53c"}.bi-shield-slash::before{content:"\f53d"}.bi-shield-x::before{content:"\f53e"}.bi-shield::before{content:"\f53f"}.bi-shift-fill::before{content:"\f540"}.bi-shift::before{content:"\f541"}.bi-shop-window::before{content:"\f542"}.bi-shop::before{content:"\f543"}.bi-shuffle::before{content:"\f544"}.bi-signpost-2-fill::before{content:"\f545"}.bi-signpost-2::before{content:"\f546"}.bi-signpost-fill::before{content:"\f547"}.bi-signpost-split-fill::before{content:"\f548"}.bi-signpost-split::before{content:"\f549"}.bi-signpost::before{content:"\f54a"}.bi-sim-fill::before{content:"\f54b"}.bi-sim::before{content:"\f54c"}.bi-skip-backward-btn-fill::before{content:"\f54d"}.bi-skip-backward-btn::before{content:"\f54e"}.bi-skip-backward-circle-fill::before{content:"\f54f"}.bi-skip-backward-circle::before{content:"\f550"}.bi-skip-backward-fill::before{content:"\f551"}.bi-skip-backward::before{content:"\f552"}.bi-skip-end-btn-fill::before{content:"\f553"}.bi-skip-end-btn::before{content:"\f554"}.bi-skip-end-circle-fill::before{content:"\f555"}.bi-skip-end-circle::before{content:"\f556"}.bi-skip-end-fill::before{content:"\f557"}.bi-skip-end::before{content:"\f558"}.bi-skip-forward-btn-fill::before{content:"\f559"}.bi-skip-forward-btn::before{content:"\f55a"}.bi-skip-forward-circle-fill::before{content:"\f55b"}.bi-skip-forward-circle::before{content:"\f55c"}.bi-skip-forward-fill::before{content:"\f55d"}.bi-skip-forward::before{content:"\f55e"}.bi-skip-start-btn-fill::before{content:"\f55f"}.bi-skip-start-btn::before{content:"\f560"}.bi-skip-start-circle-fill::before{content:"\f561"}.bi-skip-start-circle::before{content:"\f562"}.bi-skip-start-fill::before{content:"\f563"}.bi-skip-start::before{content:"\f564"}.bi-slack::before{content:"\f565"}.bi-slash-circle-fill::before{content:"\f566"}.bi-slash-circle::before{content:"\f567"}.bi-slash-square-fill::before{content:"\f568"}.bi-slash-square::before{content:"\f569"}.bi-slash::before{content:"\f56a"}.bi-sliders::before{content:"\f56b"}.bi-smartwatch::before{content:"\f56c"}.bi-snow::before{content:"\f56d"}.bi-snow2::before{content:"\f56e"}.bi-snow3::before{content:"\f56f"}.bi-sort-alpha-down-alt::before{content:"\f570"}.bi-sort-alpha-down::before{content:"\f571"}.bi-sort-alpha-up-alt::before{content:"\f572"}.bi-sort-alpha-up::before{content:"\f573"}.bi-sort-down-alt::before{content:"\f574"}.bi-sort-down::before{content:"\f575"}.bi-sort-numeric-down-alt::before{content:"\f576"}.bi-sort-numeric-down::before{content:"\f577"}.bi-sort-numeric-up-alt::before{content:"\f578"}.bi-sort-numeric-up::before{content:"\f579"}.bi-sort-up-alt::before{content:"\f57a"}.bi-sort-up::before{content:"\f57b"}.bi-soundwave::before{content:"\f57c"}.bi-speaker-fill::before{content:"\f57d"}.bi-speaker::before{content:"\f57e"}.bi-speedometer::before{content:"\f57f"}.bi-speedometer2::before{content:"\f580"}.bi-spellcheck::before{content:"\f581"}.bi-square-fill::before{content:"\f582"}.bi-square-half::before{content:"\f583"}.bi-square::before{content:"\f584"}.bi-stack::before{content:"\f585"}.bi-star-fill::before{content:"\f586"}.bi-star-half::before{content:"\f587"}.bi-star::before{content:"\f588"}.bi-stars::before{content:"\f589"}.bi-stickies-fill::before{content:"\f58a"}.bi-stickies::before{content:"\f58b"}.bi-sticky-fill::before{content:"\f58c"}.bi-sticky::before{content:"\f58d"}.bi-stop-btn-fill::before{content:"\f58e"}.bi-stop-btn::before{content:"\f58f"}.bi-stop-circle-fill::before{content:"\f590"}.bi-stop-circle::before{content:"\f591"}.bi-stop-fill::before{content:"\f592"}.bi-stop::before{content:"\f593"}.bi-stoplights-fill::before{content:"\f594"}.bi-stoplights::before{content:"\f595"}.bi-stopwatch-fill::before{content:"\f596"}.bi-stopwatch::before{content:"\f597"}.bi-subtract::before{content:"\f598"}.bi-suit-club-fill::before{content:"\f599"}.bi-suit-club::before{content:"\f59a"}.bi-suit-diamond-fill::before{content:"\f59b"}.bi-suit-diamond::before{content:"\f59c"}.bi-suit-heart-fill::before{content:"\f59d"}.bi-suit-heart::before{content:"\f59e"}.bi-suit-spade-fill::before{content:"\f59f"}.bi-suit-spade::before{content:"\f5a0"}.bi-sun-fill::before{content:"\f5a1"}.bi-sun::before{content:"\f5a2"}.bi-sunglasses::before{content:"\f5a3"}.bi-sunrise-fill::before{content:"\f5a4"}.bi-sunrise::before{content:"\f5a5"}.bi-sunset-fill::before{content:"\f5a6"}.bi-sunset::before{content:"\f5a7"}.bi-symmetry-horizontal::before{content:"\f5a8"}.bi-symmetry-vertical::before{content:"\f5a9"}.bi-table::before{content:"\f5aa"}.bi-tablet-fill::before{content:"\f5ab"}.bi-tablet-landscape-fill::before{content:"\f5ac"}.bi-tablet-landscape::before{content:"\f5ad"}.bi-tablet::before{content:"\f5ae"}.bi-tag-fill::before{content:"\f5af"}.bi-tag::before{content:"\f5b0"}.bi-tags-fill::before{content:"\f5b1"}.bi-tags::before{content:"\f5b2"}.bi-telegram::before{content:"\f5b3"}.bi-telephone-fill::before{content:"\f5b4"}.bi-telephone-forward-fill::before{content:"\f5b5"}.bi-telephone-forward::before{content:"\f5b6"}.bi-telephone-inbound-fill::before{content:"\f5b7"}.bi-telephone-inbound::before{content:"\f5b8"}.bi-telephone-minus-fill::before{content:"\f5b9"}.bi-telephone-minus::before{content:"\f5ba"}.bi-telephone-outbound-fill::before{content:"\f5bb"}.bi-telephone-outbound::before{content:"\f5bc"}.bi-telephone-plus-fill::before{content:"\f5bd"}.bi-telephone-plus::before{content:"\f5be"}.bi-telephone-x-fill::before{content:"\f5bf"}.bi-telephone-x::before{content:"\f5c0"}.bi-telephone::before{content:"\f5c1"}.bi-terminal-fill::before{content:"\f5c2"}.bi-terminal::before{content:"\f5c3"}.bi-text-center::before{content:"\f5c4"}.bi-text-indent-left::before{content:"\f5c5"}.bi-text-indent-right::before{content:"\f5c6"}.bi-text-left::before{content:"\f5c7"}.bi-text-paragraph::before{content:"\f5c8"}.bi-text-right::before{content:"\f5c9"}.bi-textarea-resize::before{content:"\f5ca"}.bi-textarea-t::before{content:"\f5cb"}.bi-textarea::before{content:"\f5cc"}.bi-thermometer-half::before{content:"\f5cd"}.bi-thermometer-high::before{content:"\f5ce"}.bi-thermometer-low::before{content:"\f5cf"}.bi-thermometer-snow::before{content:"\f5d0"}.bi-thermometer-sun::before{content:"\f5d1"}.bi-thermometer::before{content:"\f5d2"}.bi-three-dots-vertical::before{content:"\f5d3"}.bi-three-dots::before{content:"\f5d4"}.bi-toggle-off::before{content:"\f5d5"}.bi-toggle-on::before{content:"\f5d6"}.bi-toggle2-off::before{content:"\f5d7"}.bi-toggle2-on::before{content:"\f5d8"}.bi-toggles::before{content:"\f5d9"}.bi-toggles2::before{content:"\f5da"}.bi-tools::before{content:"\f5db"}.bi-tornado::before{content:"\f5dc"}.bi-trash-fill::before{content:"\f5dd"}.bi-trash::before{content:"\f5de"}.bi-trash2-fill::before{content:"\f5df"}.bi-trash2::before{content:"\f5e0"}.bi-tree-fill::before{content:"\f5e1"}.bi-tree::before{content:"\f5e2"}.bi-triangle-fill::before{content:"\f5e3"}.bi-triangle-half::before{content:"\f5e4"}.bi-triangle::before{content:"\f5e5"}.bi-trophy-fill::before{content:"\f5e6"}.bi-trophy::before{content:"\f5e7"}.bi-tropical-storm::before{content:"\f5e8"}.bi-truck-flatbed::before{content:"\f5e9"}.bi-truck::before{content:"\f5ea"}.bi-tsunami::before{content:"\f5eb"}.bi-tv-fill::before{content:"\f5ec"}.bi-tv::before{content:"\f5ed"}.bi-twitch::before{content:"\f5ee"}.bi-twitter::before{content:"\f5ef"}.bi-type-bold::before{content:"\f5f0"}.bi-type-h1::before{content:"\f5f1"}.bi-type-h2::before{content:"\f5f2"}.bi-type-h3::before{content:"\f5f3"}.bi-type-italic::before{content:"\f5f4"}.bi-type-strikethrough::before{content:"\f5f5"}.bi-type-underline::before{content:"\f5f6"}.bi-type::before{content:"\f5f7"}.bi-ui-checks-grid::before{content:"\f5f8"}.bi-ui-checks::before{content:"\f5f9"}.bi-ui-radios-grid::before{content:"\f5fa"}.bi-ui-radios::before{content:"\f5fb"}.bi-umbrella-fill::before{content:"\f5fc"}.bi-umbrella::before{content:"\f5fd"}.bi-union::before{content:"\f5fe"}.bi-unlock-fill::before{content:"\f5ff"}.bi-unlock::before{content:"\f600"}.bi-upc-scan::before{content:"\f601"}.bi-upc::before{content:"\f602"}.bi-upload::before{content:"\f603"}.bi-vector-pen::before{content:"\f604"}.bi-view-list::before{content:"\f605"}.bi-view-stacked::before{content:"\f606"}.bi-vinyl-fill::before{content:"\f607"}.bi-vinyl::before{content:"\f608"}.bi-voicemail::before{content:"\f609"}.bi-volume-down-fill::before{content:"\f60a"}.bi-volume-down::before{content:"\f60b"}.bi-volume-mute-fill::before{content:"\f60c"}.bi-volume-mute::before{content:"\f60d"}.bi-volume-off-fill::before{content:"\f60e"}.bi-volume-off::before{content:"\f60f"}.bi-volume-up-fill::before{content:"\f610"}.bi-volume-up::before{content:"\f611"}.bi-vr::before{content:"\f612"}.bi-wallet-fill::before{content:"\f613"}.bi-wallet::before{content:"\f614"}.bi-wallet2::before{content:"\f615"}.bi-watch::before{content:"\f616"}.bi-water::before{content:"\f617"}.bi-whatsapp::before{content:"\f618"}.bi-wifi-1::before{content:"\f619"}.bi-wifi-2::before{content:"\f61a"}.bi-wifi-off::before{content:"\f61b"}.bi-wifi::before{content:"\f61c"}.bi-wind::before{content:"\f61d"}.bi-window-dock::before{content:"\f61e"}.bi-window-sidebar::before{content:"\f61f"}.bi-window::before{content:"\f620"}.bi-wrench::before{content:"\f621"}.bi-x-circle-fill::before{content:"\f622"}.bi-x-circle::before{content:"\f623"}.bi-x-diamond-fill::before{content:"\f624"}.bi-x-diamond::before{content:"\f625"}.bi-x-octagon-fill::before{content:"\f626"}.bi-x-octagon::before{content:"\f627"}.bi-x-square-fill::before{content:"\f628"}.bi-x-square::before{content:"\f629"}.bi-x::before{content:"\f62a"}.bi-youtube::before{content:"\f62b"}.bi-zoom-in::before{content:"\f62c"}.bi-zoom-out::before{content:"\f62d"}.bi-bank::before{content:"\f62e"}.bi-bank2::before{content:"\f62f"}.bi-bell-slash-fill::before{content:"\f630"}.bi-bell-slash::before{content:"\f631"}.bi-cash-coin::before{content:"\f632"}.bi-check-lg::before{content:"\f633"}.bi-coin::before{content:"\f634"}.bi-currency-bitcoin::before{content:"\f635"}.bi-currency-dollar::before{content:"\f636"}.bi-currency-euro::before{content:"\f637"}.bi-currency-exchange::before{content:"\f638"}.bi-currency-pound::before{content:"\f639"}.bi-currency-yen::before{content:"\f63a"}.bi-dash-lg::before{content:"\f63b"}.bi-exclamation-lg::before{content:"\f63c"}.bi-file-earmark-pdf-fill::before{content:"\f63d"}.bi-file-earmark-pdf::before{content:"\f63e"}.bi-file-pdf-fill::before{content:"\f63f"}.bi-file-pdf::before{content:"\f640"}.bi-gender-ambiguous::before{content:"\f641"}.bi-gender-female::before{content:"\f642"}.bi-gender-male::before{content:"\f643"}.bi-gender-trans::before{content:"\f644"}.bi-headset-vr::before{content:"\f645"}.bi-info-lg::before{content:"\f646"}.bi-mastodon::before{content:"\f647"}.bi-messenger::before{content:"\f648"}.bi-piggy-bank-fill::before{content:"\f649"}.bi-piggy-bank::before{content:"\f64a"}.bi-pin-map-fill::before{content:"\f64b"}.bi-pin-map::before{content:"\f64c"}.bi-plus-lg::before{content:"\f64d"}.bi-question-lg::before{content:"\f64e"}.bi-recycle::before{content:"\f64f"}.bi-reddit::before{content:"\f650"}.bi-safe-fill::before{content:"\f651"}.bi-safe2-fill::before{content:"\f652"}.bi-safe2::before{content:"\f653"}.bi-sd-card-fill::before{content:"\f654"}.bi-sd-card::before{content:"\f655"}.bi-skype::before{content:"\f656"}.bi-slash-lg::before{content:"\f657"}.bi-translate::before{content:"\f658"}.bi-x-lg::before{content:"\f659"}.bi-safe::before{content:"\f65a"}.bi-apple::before{content:"\f65b"}.bi-microsoft::before{content:"\f65d"}.bi-windows::before{content:"\f65e"}.bi-behance::before{content:"\f65c"}.bi-dribbble::before{content:"\f65f"}.bi-line::before{content:"\f660"}.bi-medium::before{content:"\f661"}.bi-paypal::before{content:"\f662"}.bi-pinterest::before{content:"\f663"}.bi-signal::before{content:"\f664"}.bi-snapchat::before{content:"\f665"}.bi-spotify::before{content:"\f666"}.bi-stack-overflow::before{content:"\f667"}.bi-strava::before{content:"\f668"}.bi-wordpress::before{content:"\f669"}.bi-vimeo::before{content:"\f66a"}.bi-activity::before{content:"\f66b"}.bi-easel2-fill::before{content:"\f66c"}.bi-easel2::before{content:"\f66d"}.bi-easel3-fill::before{content:"\f66e"}.bi-easel3::before{content:"\f66f"}.bi-fan::before{content:"\f670"}.bi-fingerprint::before{content:"\f671"}.bi-graph-down-arrow::before{content:"\f672"}.bi-graph-up-arrow::before{content:"\f673"}.bi-hypnotize::before{content:"\f674"}.bi-magic::before{content:"\f675"}.bi-person-rolodex::before{content:"\f676"}.bi-person-video::before{content:"\f677"}.bi-person-video2::before{content:"\f678"}.bi-person-video3::before{content:"\f679"}.bi-person-workspace::before{content:"\f67a"}.bi-radioactive::before{content:"\f67b"}.bi-webcam-fill::before{content:"\f67c"}.bi-webcam::before{content:"\f67d"}.bi-yin-yang::before{content:"\f67e"}.bi-bandaid-fill::before{content:"\f680"}.bi-bandaid::before{content:"\f681"}.bi-bluetooth::before{content:"\f682"}.bi-body-text::before{content:"\f683"}.bi-boombox::before{content:"\f684"}.bi-boxes::before{content:"\f685"}.bi-dpad-fill::before{content:"\f686"}.bi-dpad::before{content:"\f687"}.bi-ear-fill::before{content:"\f688"}.bi-ear::before{content:"\f689"}.bi-envelope-check-fill::before{content:"\f68b"}.bi-envelope-check::before{content:"\f68c"}.bi-envelope-dash-fill::before{content:"\f68e"}.bi-envelope-dash::before{content:"\f68f"}.bi-envelope-exclamation-fill::before{content:"\f691"}.bi-envelope-exclamation::before{content:"\f692"}.bi-envelope-plus-fill::before{content:"\f693"}.bi-envelope-plus::before{content:"\f694"}.bi-envelope-slash-fill::before{content:"\f696"}.bi-envelope-slash::before{content:"\f697"}.bi-envelope-x-fill::before{content:"\f699"}.bi-envelope-x::before{content:"\f69a"}.bi-explicit-fill::before{content:"\f69b"}.bi-explicit::before{content:"\f69c"}.bi-git::before{content:"\f69d"}.bi-infinity::before{content:"\f69e"}.bi-list-columns-reverse::before{content:"\f69f"}.bi-list-columns::before{content:"\f6a0"}.bi-meta::before{content:"\f6a1"}.bi-nintendo-switch::before{content:"\f6a4"}.bi-pc-display-horizontal::before{content:"\f6a5"}.bi-pc-display::before{content:"\f6a6"}.bi-pc-horizontal::before{content:"\f6a7"}.bi-pc::before{content:"\f6a8"}.bi-playstation::before{content:"\f6a9"}.bi-plus-slash-minus::before{content:"\f6aa"}.bi-projector-fill::before{content:"\f6ab"}.bi-projector::before{content:"\f6ac"}.bi-qr-code-scan::before{content:"\f6ad"}.bi-qr-code::before{content:"\f6ae"}.bi-quora::before{content:"\f6af"}.bi-quote::before{content:"\f6b0"}.bi-robot::before{content:"\f6b1"}.bi-send-check-fill::before{content:"\f6b2"}.bi-send-check::before{content:"\f6b3"}.bi-send-dash-fill::before{content:"\f6b4"}.bi-send-dash::before{content:"\f6b5"}.bi-send-exclamation-fill::before{content:"\f6b7"}.bi-send-exclamation::before{content:"\f6b8"}.bi-send-fill::before{content:"\f6b9"}.bi-send-plus-fill::before{content:"\f6ba"}.bi-send-plus::before{content:"\f6bb"}.bi-send-slash-fill::before{content:"\f6bc"}.bi-send-slash::before{content:"\f6bd"}.bi-send-x-fill::before{content:"\f6be"}.bi-send-x::before{content:"\f6bf"}.bi-send::before{content:"\f6c0"}.bi-steam::before{content:"\f6c1"}.bi-terminal-dash::before{content:"\f6c3"}.bi-terminal-plus::before{content:"\f6c4"}.bi-terminal-split::before{content:"\f6c5"}.bi-ticket-detailed-fill::before{content:"\f6c6"}.bi-ticket-detailed::before{content:"\f6c7"}.bi-ticket-fill::before{content:"\f6c8"}.bi-ticket-perforated-fill::before{content:"\f6c9"}.bi-ticket-perforated::before{content:"\f6ca"}.bi-ticket::before{content:"\f6cb"}.bi-tiktok::before{content:"\f6cc"}.bi-window-dash::before{content:"\f6cd"}.bi-window-desktop::before{content:"\f6ce"}.bi-window-fullscreen::before{content:"\f6cf"}.bi-window-plus::before{content:"\f6d0"}.bi-window-split::before{content:"\f6d1"}.bi-window-stack::before{content:"\f6d2"}.bi-window-x::before{content:"\f6d3"}.bi-xbox::before{content:"\f6d4"}.bi-ethernet::before{content:"\f6d5"}.bi-hdmi-fill::before{content:"\f6d6"}.bi-hdmi::before{content:"\f6d7"}.bi-usb-c-fill::before{content:"\f6d8"}.bi-usb-c::before{content:"\f6d9"}.bi-usb-fill::before{content:"\f6da"}.bi-usb-plug-fill::before{content:"\f6db"}.bi-usb-plug::before{content:"\f6dc"}.bi-usb-symbol::before{content:"\f6dd"}.bi-usb::before{content:"\f6de"}.bi-boombox-fill::before{content:"\f6df"}.bi-displayport::before{content:"\f6e1"}.bi-gpu-card::before{content:"\f6e2"}.bi-memory::before{content:"\f6e3"}.bi-modem-fill::before{content:"\f6e4"}.bi-modem::before{content:"\f6e5"}.bi-motherboard-fill::before{content:"\f6e6"}.bi-motherboard::before{content:"\f6e7"}.bi-optical-audio-fill::before{content:"\f6e8"}.bi-optical-audio::before{content:"\f6e9"}.bi-pci-card::before{content:"\f6ea"}.bi-router-fill::before{content:"\f6eb"}.bi-router::before{content:"\f6ec"}.bi-thunderbolt-fill::before{content:"\f6ef"}.bi-thunderbolt::before{content:"\f6f0"}.bi-usb-drive-fill::before{content:"\f6f1"}.bi-usb-drive::before{content:"\f6f2"}.bi-usb-micro-fill::before{content:"\f6f3"}.bi-usb-micro::before{content:"\f6f4"}.bi-usb-mini-fill::before{content:"\f6f5"}.bi-usb-mini::before{content:"\f6f6"}.bi-cloud-haze2::before{content:"\f6f7"}.bi-device-hdd-fill::before{content:"\f6f8"}.bi-device-hdd::before{content:"\f6f9"}.bi-device-ssd-fill::before{content:"\f6fa"}.bi-device-ssd::before{content:"\f6fb"}.bi-displayport-fill::before{content:"\f6fc"}.bi-mortarboard-fill::before{content:"\f6fd"}.bi-mortarboard::before{content:"\f6fe"}.bi-terminal-x::before{content:"\f6ff"}.bi-arrow-through-heart-fill::before{content:"\f700"}.bi-arrow-through-heart::before{content:"\f701"}.bi-badge-sd-fill::before{content:"\f702"}.bi-badge-sd::before{content:"\f703"}.bi-bag-heart-fill::before{content:"\f704"}.bi-bag-heart::before{content:"\f705"}.bi-balloon-fill::before{content:"\f706"}.bi-balloon-heart-fill::before{content:"\f707"}.bi-balloon-heart::before{content:"\f708"}.bi-balloon::before{content:"\f709"}.bi-box2-fill::before{content:"\f70a"}.bi-box2-heart-fill::before{content:"\f70b"}.bi-box2-heart::before{content:"\f70c"}.bi-box2::before{content:"\f70d"}.bi-braces-asterisk::before{content:"\f70e"}.bi-calendar-heart-fill::before{content:"\f70f"}.bi-calendar-heart::before{content:"\f710"}.bi-calendar2-heart-fill::before{content:"\f711"}.bi-calendar2-heart::before{content:"\f712"}.bi-chat-heart-fill::before{content:"\f713"}.bi-chat-heart::before{content:"\f714"}.bi-chat-left-heart-fill::before{content:"\f715"}.bi-chat-left-heart::before{content:"\f716"}.bi-chat-right-heart-fill::before{content:"\f717"}.bi-chat-right-heart::before{content:"\f718"}.bi-chat-square-heart-fill::before{content:"\f719"}.bi-chat-square-heart::before{content:"\f71a"}.bi-clipboard-check-fill::before{content:"\f71b"}.bi-clipboard-data-fill::before{content:"\f71c"}.bi-clipboard-fill::before{content:"\f71d"}.bi-clipboard-heart-fill::before{content:"\f71e"}.bi-clipboard-heart::before{content:"\f71f"}.bi-clipboard-minus-fill::before{content:"\f720"}.bi-clipboard-plus-fill::before{content:"\f721"}.bi-clipboard-pulse::before{content:"\f722"}.bi-clipboard-x-fill::before{content:"\f723"}.bi-clipboard2-check-fill::before{content:"\f724"}.bi-clipboard2-check::before{content:"\f725"}.bi-clipboard2-data-fill::before{content:"\f726"}.bi-clipboard2-data::before{content:"\f727"}.bi-clipboard2-fill::before{content:"\f728"}.bi-clipboard2-heart-fill::before{content:"\f729"}.bi-clipboard2-heart::before{content:"\f72a"}.bi-clipboard2-minus-fill::before{content:"\f72b"}.bi-clipboard2-minus::before{content:"\f72c"}.bi-clipboard2-plus-fill::before{content:"\f72d"}.bi-clipboard2-plus::before{content:"\f72e"}.bi-clipboard2-pulse-fill::before{content:"\f72f"}.bi-clipboard2-pulse::before{content:"\f730"}.bi-clipboard2-x-fill::before{content:"\f731"}.bi-clipboard2-x::before{content:"\f732"}.bi-clipboard2::before{content:"\f733"}.bi-emoji-kiss-fill::before{content:"\f734"}.bi-emoji-kiss::before{content:"\f735"}.bi-envelope-heart-fill::before{content:"\f736"}.bi-envelope-heart::before{content:"\f737"}.bi-envelope-open-heart-fill::before{content:"\f738"}.bi-envelope-open-heart::before{content:"\f739"}.bi-envelope-paper-fill::before{content:"\f73a"}.bi-envelope-paper-heart-fill::before{content:"\f73b"}.bi-envelope-paper-heart::before{content:"\f73c"}.bi-envelope-paper::before{content:"\f73d"}.bi-filetype-aac::before{content:"\f73e"}.bi-filetype-ai::before{content:"\f73f"}.bi-filetype-bmp::before{content:"\f740"}.bi-filetype-cs::before{content:"\f741"}.bi-filetype-css::before{content:"\f742"}.bi-filetype-csv::before{content:"\f743"}.bi-filetype-doc::before{content:"\f744"}.bi-filetype-docx::before{content:"\f745"}.bi-filetype-exe::before{content:"\f746"}.bi-filetype-gif::before{content:"\f747"}.bi-filetype-heic::before{content:"\f748"}.bi-filetype-html::before{content:"\f749"}.bi-filetype-java::before{content:"\f74a"}.bi-filetype-jpg::before{content:"\f74b"}.bi-filetype-js::before{content:"\f74c"}.bi-filetype-jsx::before{content:"\f74d"}.bi-filetype-key::before{content:"\f74e"}.bi-filetype-m4p::before{content:"\f74f"}.bi-filetype-md::before{content:"\f750"}.bi-filetype-mdx::before{content:"\f751"}.bi-filetype-mov::before{content:"\f752"}.bi-filetype-mp3::before{content:"\f753"}.bi-filetype-mp4::before{content:"\f754"}.bi-filetype-otf::before{content:"\f755"}.bi-filetype-pdf::before{content:"\f756"}.bi-filetype-php::before{content:"\f757"}.bi-filetype-png::before{content:"\f758"}.bi-filetype-ppt::before{content:"\f75a"}.bi-filetype-psd::before{content:"\f75b"}.bi-filetype-py::before{content:"\f75c"}.bi-filetype-raw::before{content:"\f75d"}.bi-filetype-rb::before{content:"\f75e"}.bi-filetype-sass::before{content:"\f75f"}.bi-filetype-scss::before{content:"\f760"}.bi-filetype-sh::before{content:"\f761"}.bi-filetype-svg::before{content:"\f762"}.bi-filetype-tiff::before{content:"\f763"}.bi-filetype-tsx::before{content:"\f764"}.bi-filetype-ttf::before{content:"\f765"}.bi-filetype-txt::before{content:"\f766"}.bi-filetype-wav::before{content:"\f767"}.bi-filetype-woff::before{content:"\f768"}.bi-filetype-xls::before{content:"\f76a"}.bi-filetype-xml::before{content:"\f76b"}.bi-filetype-yml::before{content:"\f76c"}.bi-heart-arrow::before{content:"\f76d"}.bi-heart-pulse-fill::before{content:"\f76e"}.bi-heart-pulse::before{content:"\f76f"}.bi-heartbreak-fill::before{content:"\f770"}.bi-heartbreak::before{content:"\f771"}.bi-hearts::before{content:"\f772"}.bi-hospital-fill::before{content:"\f773"}.bi-hospital::before{content:"\f774"}.bi-house-heart-fill::before{content:"\f775"}.bi-house-heart::before{content:"\f776"}.bi-incognito::before{content:"\f777"}.bi-magnet-fill::before{content:"\f778"}.bi-magnet::before{content:"\f779"}.bi-person-heart::before{content:"\f77a"}.bi-person-hearts::before{content:"\f77b"}.bi-phone-flip::before{content:"\f77c"}.bi-plugin::before{content:"\f77d"}.bi-postage-fill::before{content:"\f77e"}.bi-postage-heart-fill::before{content:"\f77f"}.bi-postage-heart::before{content:"\f780"}.bi-postage::before{content:"\f781"}.bi-postcard-fill::before{content:"\f782"}.bi-postcard-heart-fill::before{content:"\f783"}.bi-postcard-heart::before{content:"\f784"}.bi-postcard::before{content:"\f785"}.bi-search-heart-fill::before{content:"\f786"}.bi-search-heart::before{content:"\f787"}.bi-sliders2-vertical::before{content:"\f788"}.bi-sliders2::before{content:"\f789"}.bi-trash3-fill::before{content:"\f78a"}.bi-trash3::before{content:"\f78b"}.bi-valentine::before{content:"\f78c"}.bi-valentine2::before{content:"\f78d"}.bi-wrench-adjustable-circle-fill::before{content:"\f78e"}.bi-wrench-adjustable-circle::before{content:"\f78f"}.bi-wrench-adjustable::before{content:"\f790"}.bi-filetype-json::before{content:"\f791"}.bi-filetype-pptx::before{content:"\f792"}.bi-filetype-xlsx::before{content:"\f793"}.bi-1-circle-fill::before{content:"\f796"}.bi-1-circle::before{content:"\f797"}.bi-1-square-fill::before{content:"\f798"}.bi-1-square::before{content:"\f799"}.bi-2-circle-fill::before{content:"\f79c"}.bi-2-circle::before{content:"\f79d"}.bi-2-square-fill::before{content:"\f79e"}.bi-2-square::before{content:"\f79f"}.bi-3-circle-fill::before{content:"\f7a2"}.bi-3-circle::before{content:"\f7a3"}.bi-3-square-fill::before{content:"\f7a4"}.bi-3-square::before{content:"\f7a5"}.bi-4-circle-fill::before{content:"\f7a8"}.bi-4-circle::before{content:"\f7a9"}.bi-4-square-fill::before{content:"\f7aa"}.bi-4-square::before{content:"\f7ab"}.bi-5-circle-fill::before{content:"\f7ae"}.bi-5-circle::before{content:"\f7af"}.bi-5-square-fill::before{content:"\f7b0"}.bi-5-square::before{content:"\f7b1"}.bi-6-circle-fill::before{content:"\f7b4"}.bi-6-circle::before{content:"\f7b5"}.bi-6-square-fill::before{content:"\f7b6"}.bi-6-square::before{content:"\f7b7"}.bi-7-circle-fill::before{content:"\f7ba"}.bi-7-circle::before{content:"\f7bb"}.bi-7-square-fill::before{content:"\f7bc"}.bi-7-square::before{content:"\f7bd"}.bi-8-circle-fill::before{content:"\f7c0"}.bi-8-circle::before{content:"\f7c1"}.bi-8-square-fill::before{content:"\f7c2"}.bi-8-square::before{content:"\f7c3"}.bi-9-circle-fill::before{content:"\f7c6"}.bi-9-circle::before{content:"\f7c7"}.bi-9-square-fill::before{content:"\f7c8"}.bi-9-square::before{content:"\f7c9"}.bi-airplane-engines-fill::before{content:"\f7ca"}.bi-airplane-engines::before{content:"\f7cb"}.bi-airplane-fill::before{content:"\f7cc"}.bi-airplane::before{content:"\f7cd"}.bi-alexa::before{content:"\f7ce"}.bi-alipay::before{content:"\f7cf"}.bi-android::before{content:"\f7d0"}.bi-android2::before{content:"\f7d1"}.bi-box-fill::before{content:"\f7d2"}.bi-box-seam-fill::before{content:"\f7d3"}.bi-browser-chrome::before{content:"\f7d4"}.bi-browser-edge::before{content:"\f7d5"}.bi-browser-firefox::before{content:"\f7d6"}.bi-browser-safari::before{content:"\f7d7"}.bi-c-circle-fill::before{content:"\f7da"}.bi-c-circle::before{content:"\f7db"}.bi-c-square-fill::before{content:"\f7dc"}.bi-c-square::before{content:"\f7dd"}.bi-capsule-pill::before{content:"\f7de"}.bi-capsule::before{content:"\f7df"}.bi-car-front-fill::before{content:"\f7e0"}.bi-car-front::before{content:"\f7e1"}.bi-cassette-fill::before{content:"\f7e2"}.bi-cassette::before{content:"\f7e3"}.bi-cc-circle-fill::before{content:"\f7e6"}.bi-cc-circle::before{content:"\f7e7"}.bi-cc-square-fill::before{content:"\f7e8"}.bi-cc-square::before{content:"\f7e9"}.bi-cup-hot-fill::before{content:"\f7ea"}.bi-cup-hot::before{content:"\f7eb"}.bi-currency-rupee::before{content:"\f7ec"}.bi-dropbox::before{content:"\f7ed"}.bi-escape::before{content:"\f7ee"}.bi-fast-forward-btn-fill::before{content:"\f7ef"}.bi-fast-forward-btn::before{content:"\f7f0"}.bi-fast-forward-circle-fill::before{content:"\f7f1"}.bi-fast-forward-circle::before{content:"\f7f2"}.bi-fast-forward-fill::before{content:"\f7f3"}.bi-fast-forward::before{content:"\f7f4"}.bi-filetype-sql::before{content:"\f7f5"}.bi-fire::before{content:"\f7f6"}.bi-google-play::before{content:"\f7f7"}.bi-h-circle-fill::before{content:"\f7fa"}.bi-h-circle::before{content:"\f7fb"}.bi-h-square-fill::before{content:"\f7fc"}.bi-h-square::before{content:"\f7fd"}.bi-indent::before{content:"\f7fe"}.bi-lungs-fill::before{content:"\f7ff"}.bi-lungs::before{content:"\f800"}.bi-microsoft-teams::before{content:"\f801"}.bi-p-circle-fill::before{content:"\f804"}.bi-p-circle::before{content:"\f805"}.bi-p-square-fill::before{content:"\f806"}.bi-p-square::before{content:"\f807"}.bi-pass-fill::before{content:"\f808"}.bi-pass::before{content:"\f809"}.bi-prescription::before{content:"\f80a"}.bi-prescription2::before{content:"\f80b"}.bi-r-circle-fill::before{content:"\f80e"}.bi-r-circle::before{content:"\f80f"}.bi-r-square-fill::before{content:"\f810"}.bi-r-square::before{content:"\f811"}.bi-repeat-1::before{content:"\f812"}.bi-repeat::before{content:"\f813"}.bi-rewind-btn-fill::before{content:"\f814"}.bi-rewind-btn::before{content:"\f815"}.bi-rewind-circle-fill::before{content:"\f816"}.bi-rewind-circle::before{content:"\f817"}.bi-rewind-fill::before{content:"\f818"}.bi-rewind::before{content:"\f819"}.bi-train-freight-front-fill::before{content:"\f81a"}.bi-train-freight-front::before{content:"\f81b"}.bi-train-front-fill::before{content:"\f81c"}.bi-train-front::before{content:"\f81d"}.bi-train-lightrail-front-fill::before{content:"\f81e"}.bi-train-lightrail-front::before{content:"\f81f"}.bi-truck-front-fill::before{content:"\f820"}.bi-truck-front::before{content:"\f821"}.bi-ubuntu::before{content:"\f822"}.bi-unindent::before{content:"\f823"}.bi-unity::before{content:"\f824"}.bi-universal-access-circle::before{content:"\f825"}.bi-universal-access::before{content:"\f826"}.bi-virus::before{content:"\f827"}.bi-virus2::before{content:"\f828"}.bi-wechat::before{content:"\f829"}.bi-yelp::before{content:"\f82a"}.bi-sign-stop-fill::before{content:"\f82b"}.bi-sign-stop-lights-fill::before{content:"\f82c"}.bi-sign-stop-lights::before{content:"\f82d"}.bi-sign-stop::before{content:"\f82e"}.bi-sign-turn-left-fill::before{content:"\f82f"}.bi-sign-turn-left::before{content:"\f830"}.bi-sign-turn-right-fill::before{content:"\f831"}.bi-sign-turn-right::before{content:"\f832"}.bi-sign-turn-slight-left-fill::before{content:"\f833"}.bi-sign-turn-slight-left::before{content:"\f834"}.bi-sign-turn-slight-right-fill::before{content:"\f835"}.bi-sign-turn-slight-right::before{content:"\f836"}.bi-sign-yield-fill::before{content:"\f837"}.bi-sign-yield::before{content:"\f838"}.bi-ev-station-fill::before{content:"\f839"}.bi-ev-station::before{content:"\f83a"}.bi-fuel-pump-diesel-fill::before{content:"\f83b"}.bi-fuel-pump-diesel::before{content:"\f83c"}.bi-fuel-pump-fill::before{content:"\f83d"}.bi-fuel-pump::before{content:"\f83e"}.bi-0-circle-fill::before{content:"\f83f"}.bi-0-circle::before{content:"\f840"}.bi-0-square-fill::before{content:"\f841"}.bi-0-square::before{content:"\f842"}.bi-rocket-fill::before{content:"\f843"}.bi-rocket-takeoff-fill::before{content:"\f844"}.bi-rocket-takeoff::before{content:"\f845"}.bi-rocket::before{content:"\f846"}.bi-stripe::before{content:"\f847"}.bi-subscript::before{content:"\f848"}.bi-superscript::before{content:"\f849"}.bi-trello::before{content:"\f84a"}.bi-envelope-at-fill::before{content:"\f84b"}.bi-envelope-at::before{content:"\f84c"}.bi-regex::before{content:"\f84d"}.bi-text-wrap::before{content:"\f84e"}.bi-sign-dead-end-fill::before{content:"\f84f"}.bi-sign-dead-end::before{content:"\f850"}.bi-sign-do-not-enter-fill::before{content:"\f851"}.bi-sign-do-not-enter::before{content:"\f852"}.bi-sign-intersection-fill::before{content:"\f853"}.bi-sign-intersection-side-fill::before{content:"\f854"}.bi-sign-intersection-side::before{content:"\f855"}.bi-sign-intersection-t-fill::before{content:"\f856"}.bi-sign-intersection-t::before{content:"\f857"}.bi-sign-intersection-y-fill::before{content:"\f858"}.bi-sign-intersection-y::before{content:"\f859"}.bi-sign-intersection::before{content:"\f85a"}.bi-sign-merge-left-fill::before{content:"\f85b"}.bi-sign-merge-left::before{content:"\f85c"}.bi-sign-merge-right-fill::before{content:"\f85d"}.bi-sign-merge-right::before{content:"\f85e"}.bi-sign-no-left-turn-fill::before{content:"\f85f"}.bi-sign-no-left-turn::before{content:"\f860"}.bi-sign-no-parking-fill::before{content:"\f861"}.bi-sign-no-parking::before{content:"\f862"}.bi-sign-no-right-turn-fill::before{content:"\f863"}.bi-sign-no-right-turn::before{content:"\f864"}.bi-sign-railroad-fill::before{content:"\f865"}.bi-sign-railroad::before{content:"\f866"}.bi-building-add::before{content:"\f867"}.bi-building-check::before{content:"\f868"}.bi-building-dash::before{content:"\f869"}.bi-building-down::before{content:"\f86a"}.bi-building-exclamation::before{content:"\f86b"}.bi-building-fill-add::before{content:"\f86c"}.bi-building-fill-check::before{content:"\f86d"}.bi-building-fill-dash::before{content:"\f86e"}.bi-building-fill-down::before{content:"\f86f"}.bi-building-fill-exclamation::before{content:"\f870"}.bi-building-fill-gear::before{content:"\f871"}.bi-building-fill-lock::before{content:"\f872"}.bi-building-fill-slash::before{content:"\f873"}.bi-building-fill-up::before{content:"\f874"}.bi-building-fill-x::before{content:"\f875"}.bi-building-fill::before{content:"\f876"}.bi-building-gear::before{content:"\f877"}.bi-building-lock::before{content:"\f878"}.bi-building-slash::before{content:"\f879"}.bi-building-up::before{content:"\f87a"}.bi-building-x::before{content:"\f87b"}.bi-buildings-fill::before{content:"\f87c"}.bi-buildings::before{content:"\f87d"}.bi-bus-front-fill::before{content:"\f87e"}.bi-bus-front::before{content:"\f87f"}.bi-ev-front-fill::before{content:"\f880"}.bi-ev-front::before{content:"\f881"}.bi-globe-americas::before{content:"\f882"}.bi-globe-asia-australia::before{content:"\f883"}.bi-globe-central-south-asia::before{content:"\f884"}.bi-globe-europe-africa::before{content:"\f885"}.bi-house-add-fill::before{content:"\f886"}.bi-house-add::before{content:"\f887"}.bi-house-check-fill::before{content:"\f888"}.bi-house-check::before{content:"\f889"}.bi-house-dash-fill::before{content:"\f88a"}.bi-house-dash::before{content:"\f88b"}.bi-house-down-fill::before{content:"\f88c"}.bi-house-down::before{content:"\f88d"}.bi-house-exclamation-fill::before{content:"\f88e"}.bi-house-exclamation::before{content:"\f88f"}.bi-house-gear-fill::before{content:"\f890"}.bi-house-gear::before{content:"\f891"}.bi-house-lock-fill::before{content:"\f892"}.bi-house-lock::before{content:"\f893"}.bi-house-slash-fill::before{content:"\f894"}.bi-house-slash::before{content:"\f895"}.bi-house-up-fill::before{content:"\f896"}.bi-house-up::before{content:"\f897"}.bi-house-x-fill::before{content:"\f898"}.bi-house-x::before{content:"\f899"}.bi-person-add::before{content:"\f89a"}.bi-person-down::before{content:"\f89b"}.bi-person-exclamation::before{content:"\f89c"}.bi-person-fill-add::before{content:"\f89d"}.bi-person-fill-check::before{content:"\f89e"}.bi-person-fill-dash::before{content:"\f89f"}.bi-person-fill-down::before{content:"\f8a0"}.bi-person-fill-exclamation::before{content:"\f8a1"}.bi-person-fill-gear::before{content:"\f8a2"}.bi-person-fill-lock::before{content:"\f8a3"}.bi-person-fill-slash::before{content:"\f8a4"}.bi-person-fill-up::before{content:"\f8a5"}.bi-person-fill-x::before{content:"\f8a6"}.bi-person-gear::before{content:"\f8a7"}.bi-person-lock::before{content:"\f8a8"}.bi-person-slash::before{content:"\f8a9"}.bi-person-up::before{content:"\f8aa"}.bi-scooter::before{content:"\f8ab"}.bi-taxi-front-fill::before{content:"\f8ac"}.bi-taxi-front::before{content:"\f8ad"}.bi-amd::before{content:"\f8ae"}.bi-database-add::before{content:"\f8af"}.bi-database-check::before{content:"\f8b0"}.bi-database-dash::before{content:"\f8b1"}.bi-database-down::before{content:"\f8b2"}.bi-database-exclamation::before{content:"\f8b3"}.bi-database-fill-add::before{content:"\f8b4"}.bi-database-fill-check::before{content:"\f8b5"}.bi-database-fill-dash::before{content:"\f8b6"}.bi-database-fill-down::before{content:"\f8b7"}.bi-database-fill-exclamation::before{content:"\f8b8"}.bi-database-fill-gear::before{content:"\f8b9"}.bi-database-fill-lock::before{content:"\f8ba"}.bi-database-fill-slash::before{content:"\f8bb"}.bi-database-fill-up::before{content:"\f8bc"}.bi-database-fill-x::before{content:"\f8bd"}.bi-database-fill::before{content:"\f8be"}.bi-database-gear::before{content:"\f8bf"}.bi-database-lock::before{content:"\f8c0"}.bi-database-slash::before{content:"\f8c1"}.bi-database-up::before{content:"\f8c2"}.bi-database-x::before{content:"\f8c3"}.bi-database::before{content:"\f8c4"}.bi-houses-fill::before{content:"\f8c5"}.bi-houses::before{content:"\f8c6"}.bi-nvidia::before{content:"\f8c7"}.bi-person-vcard-fill::before{content:"\f8c8"}.bi-person-vcard::before{content:"\f8c9"}.bi-sina-weibo::before{content:"\f8ca"}.bi-tencent-qq::before{content:"\f8cb"}.bi-wikipedia::before{content:"\f8cc"}.bi-alphabet-uppercase::before{content:"\f2a5"}.bi-alphabet::before{content:"\f68a"}.bi-amazon::before{content:"\f68d"}.bi-arrows-collapse-vertical::before{content:"\f690"}.bi-arrows-expand-vertical::before{content:"\f695"}.bi-arrows-vertical::before{content:"\f698"}.bi-arrows::before{content:"\f6a2"}.bi-ban-fill::before{content:"\f6a3"}.bi-ban::before{content:"\f6b6"}.bi-bing::before{content:"\f6c2"}.bi-cake::before{content:"\f6e0"}.bi-cake2::before{content:"\f6ed"}.bi-cookie::before{content:"\f6ee"}.bi-copy::before{content:"\f759"}.bi-crosshair::before{content:"\f769"}.bi-crosshair2::before{content:"\f794"}.bi-emoji-astonished-fill::before{content:"\f795"}.bi-emoji-astonished::before{content:"\f79a"}.bi-emoji-grimace-fill::before{content:"\f79b"}.bi-emoji-grimace::before{content:"\f7a0"}.bi-emoji-grin-fill::before{content:"\f7a1"}.bi-emoji-grin::before{content:"\f7a6"}.bi-emoji-surprise-fill::before{content:"\f7a7"}.bi-emoji-surprise::before{content:"\f7ac"}.bi-emoji-tear-fill::before{content:"\f7ad"}.bi-emoji-tear::before{content:"\f7b2"}.bi-envelope-arrow-down-fill::before{content:"\f7b3"}.bi-envelope-arrow-down::before{content:"\f7b8"}.bi-envelope-arrow-up-fill::before{content:"\f7b9"}.bi-envelope-arrow-up::before{content:"\f7be"}.bi-feather::before{content:"\f7bf"}.bi-feather2::before{content:"\f7c4"}.bi-floppy-fill::before{content:"\f7c5"}.bi-floppy::before{content:"\f7d8"}.bi-floppy2-fill::before{content:"\f7d9"}.bi-floppy2::before{content:"\f7e4"}.bi-gitlab::before{content:"\f7e5"}.bi-highlighter::before{content:"\f7f8"}.bi-marker-tip::before{content:"\f802"}.bi-nvme-fill::before{content:"\f803"}.bi-nvme::before{content:"\f80c"}.bi-opencollective::before{content:"\f80d"}.bi-pci-card-network::before{content:"\f8cd"}.bi-pci-card-sound::before{content:"\f8ce"}.bi-radar::before{content:"\f8cf"}.bi-send-arrow-down-fill::before{content:"\f8d0"}.bi-send-arrow-down::before{content:"\f8d1"}.bi-send-arrow-up-fill::before{content:"\f8d2"}.bi-send-arrow-up::before{content:"\f8d3"}.bi-sim-slash-fill::before{content:"\f8d4"}.bi-sim-slash::before{content:"\f8d5"}.bi-sourceforge::before{content:"\f8d6"}.bi-substack::before{content:"\f8d7"}.bi-threads-fill::before{content:"\f8d8"}.bi-threads::before{content:"\f8d9"}.bi-transparency::before{content:"\f8da"}.bi-twitter-x::before{content:"\f8db"}.bi-type-h4::before{content:"\f8dc"}.bi-type-h5::before{content:"\f8dd"}.bi-type-h6::before{content:"\f8de"}.bi-backpack-fill::before{content:"\f8df"}.bi-backpack::before{content:"\f8e0"}.bi-backpack2-fill::before{content:"\f8e1"}.bi-backpack2::before{content:"\f8e2"}.bi-backpack3-fill::before{content:"\f8e3"}.bi-backpack3::before{content:"\f8e4"}.bi-backpack4-fill::before{content:"\f8e5"}.bi-backpack4::before{content:"\f8e6"}.bi-brilliance::before{content:"\f8e7"}.bi-cake-fill::before{content:"\f8e8"}.bi-cake2-fill::before{content:"\f8e9"}.bi-duffle-fill::before{content:"\f8ea"}.bi-duffle::before{content:"\f8eb"}.bi-exposure::before{content:"\f8ec"}.bi-gender-neuter::before{content:"\f8ed"}.bi-highlights::before{content:"\f8ee"}.bi-luggage-fill::before{content:"\f8ef"}.bi-luggage::before{content:"\f8f0"}.bi-mailbox-flag::before{content:"\f8f1"}.bi-mailbox2-flag::before{content:"\f8f2"}.bi-noise-reduction::before{content:"\f8f3"}.bi-passport-fill::before{content:"\f8f4"}.bi-passport::before{content:"\f8f5"}.bi-person-arms-up::before{content:"\f8f6"}.bi-person-raised-hand::before{content:"\f8f7"}.bi-person-standing-dress::before{content:"\f8f8"}.bi-person-standing::before{content:"\f8f9"}.bi-person-walking::before{content:"\f8fa"}.bi-person-wheelchair::before{content:"\f8fb"}.bi-shadows::before{content:"\f8fc"}.bi-suitcase-fill::before{content:"\f8fd"}.bi-suitcase-lg-fill::before{content:"\f8fe"}.bi-suitcase-lg::before{content:"\f8ff"}.bi-suitcase::before{content:"\f900"}.bi-suitcase2-fill::before{content:"\f901"}.bi-suitcase2::before{content:"\f902"}.bi-vignette::before{content:"\f903"}.bi-bluesky::before{content:"\f7f9"}.bi-tux::before{content:"\f904"}.bi-beaker-fill::before{content:"\f905"}.bi-beaker::before{content:"\f906"}.bi-flask-fill::before{content:"\f907"}.bi-flask-florence-fill::before{content:"\f908"}.bi-flask-florence::before{content:"\f909"}.bi-flask::before{content:"\f90a"}.bi-leaf-fill::before{content:"\f90b"}.bi-leaf::before{content:"\f90c"}.bi-measuring-cup-fill::before{content:"\f90d"}.bi-measuring-cup::before{content:"\f90e"}.bi-unlock2-fill::before{content:"\f90f"}.bi-unlock2::before{content:"\f910"}.bi-battery-low::before{content:"\f911"}.bi-anthropic::before{content:"\f912"}.bi-apple-music::before{content:"\f913"}.bi-claude::before{content:"\f914"}.bi-openai::before{content:"\f915"}.bi-perplexity::before{content:"\f916"}.bi-css::before{content:"\f917"}.bi-javascript::before{content:"\f918"}.bi-typescript::before{content:"\f919"}.bi-fork-knife::before{content:"\f91a"}.bi-globe-americas-fill::before{content:"\f91b"}.bi-globe-asia-australia-fill::before{content:"\f91c"}.bi-globe-central-south-asia-fill::before{content:"\f91d"}.bi-globe-europe-africa-fill::before{content:"\f91e"} \ No newline at end of file diff --git a/extensions/pagetop-bootsier/assets/bootstrap-icons-1.13.1/bootstrap-icons.scss b/extensions/pagetop-bootsier/assets/bootstrap-icons-1.13.1/bootstrap-icons.scss deleted file mode 100644 index 19735c42..00000000 --- a/extensions/pagetop-bootsier/assets/bootstrap-icons-1.13.1/bootstrap-icons.scss +++ /dev/null @@ -1,2118 +0,0 @@ -/*! - * Bootstrap Icons v1.13.1 (https://icons.getbootstrap.com/) - * Copyright 2019-2024 The Bootstrap Authors - * Licensed under MIT (https://github.com/twbs/icons/blob/main/LICENSE) - */ - -$bootstrap-icons-font: "bootstrap-icons" !default; -$bootstrap-icons-font-dir: "./fonts" !default; -$bootstrap-icons-font-file: "#{$bootstrap-icons-font-dir}/#{$bootstrap-icons-font}" !default; -$bootstrap-icons-font-hash: "24e3eb84d0bcaf83d77f904c78ac1f47" !default; -$bootstrap-icons-font-src: url("#{$bootstrap-icons-font-file}.woff2?#{$bootstrap-icons-font-hash}") format("woff2"), - url("#{$bootstrap-icons-font-file}.woff?#{$bootstrap-icons-font-hash}") format("woff") !default; - -@font-face { - font-display: block; - font-family: $bootstrap-icons-font; - src: $bootstrap-icons-font-src; -} - -.bi::before, -[class^="bi-"]::before, -[class*=" bi-"]::before { - display: inline-block; - font-family: $bootstrap-icons-font !important; - font-style: normal; - font-weight: normal !important; - font-variant: normal; - text-transform: none; - line-height: 1; - vertical-align: -.125em; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -$bootstrap-icons-map: ( - "123": "\f67f", - "alarm-fill": "\f101", - "alarm": "\f102", - "align-bottom": "\f103", - "align-center": "\f104", - "align-end": "\f105", - "align-middle": "\f106", - "align-start": "\f107", - "align-top": "\f108", - "alt": "\f109", - "app-indicator": "\f10a", - "app": "\f10b", - "archive-fill": "\f10c", - "archive": "\f10d", - "arrow-90deg-down": "\f10e", - "arrow-90deg-left": "\f10f", - "arrow-90deg-right": "\f110", - "arrow-90deg-up": "\f111", - "arrow-bar-down": "\f112", - "arrow-bar-left": "\f113", - "arrow-bar-right": "\f114", - "arrow-bar-up": "\f115", - "arrow-clockwise": "\f116", - "arrow-counterclockwise": "\f117", - "arrow-down-circle-fill": "\f118", - "arrow-down-circle": "\f119", - "arrow-down-left-circle-fill": "\f11a", - "arrow-down-left-circle": "\f11b", - "arrow-down-left-square-fill": "\f11c", - "arrow-down-left-square": "\f11d", - "arrow-down-left": "\f11e", - "arrow-down-right-circle-fill": "\f11f", - "arrow-down-right-circle": "\f120", - "arrow-down-right-square-fill": "\f121", - "arrow-down-right-square": "\f122", - "arrow-down-right": "\f123", - "arrow-down-short": "\f124", - "arrow-down-square-fill": "\f125", - "arrow-down-square": "\f126", - "arrow-down-up": "\f127", - "arrow-down": "\f128", - "arrow-left-circle-fill": "\f129", - "arrow-left-circle": "\f12a", - "arrow-left-right": "\f12b", - "arrow-left-short": "\f12c", - "arrow-left-square-fill": "\f12d", - "arrow-left-square": "\f12e", - "arrow-left": "\f12f", - "arrow-repeat": "\f130", - "arrow-return-left": "\f131", - "arrow-return-right": "\f132", - "arrow-right-circle-fill": "\f133", - "arrow-right-circle": "\f134", - "arrow-right-short": "\f135", - "arrow-right-square-fill": "\f136", - "arrow-right-square": "\f137", - "arrow-right": "\f138", - "arrow-up-circle-fill": "\f139", - "arrow-up-circle": "\f13a", - "arrow-up-left-circle-fill": "\f13b", - "arrow-up-left-circle": "\f13c", - "arrow-up-left-square-fill": "\f13d", - "arrow-up-left-square": "\f13e", - "arrow-up-left": "\f13f", - "arrow-up-right-circle-fill": "\f140", - "arrow-up-right-circle": "\f141", - "arrow-up-right-square-fill": "\f142", - "arrow-up-right-square": "\f143", - "arrow-up-right": "\f144", - "arrow-up-short": "\f145", - "arrow-up-square-fill": "\f146", - "arrow-up-square": "\f147", - "arrow-up": "\f148", - "arrows-angle-contract": "\f149", - "arrows-angle-expand": "\f14a", - "arrows-collapse": "\f14b", - "arrows-expand": "\f14c", - "arrows-fullscreen": "\f14d", - "arrows-move": "\f14e", - "aspect-ratio-fill": "\f14f", - "aspect-ratio": "\f150", - "asterisk": "\f151", - "at": "\f152", - "award-fill": "\f153", - "award": "\f154", - "back": "\f155", - "backspace-fill": "\f156", - "backspace-reverse-fill": "\f157", - "backspace-reverse": "\f158", - "backspace": "\f159", - "badge-3d-fill": "\f15a", - "badge-3d": "\f15b", - "badge-4k-fill": "\f15c", - "badge-4k": "\f15d", - "badge-8k-fill": "\f15e", - "badge-8k": "\f15f", - "badge-ad-fill": "\f160", - "badge-ad": "\f161", - "badge-ar-fill": "\f162", - "badge-ar": "\f163", - "badge-cc-fill": "\f164", - "badge-cc": "\f165", - "badge-hd-fill": "\f166", - "badge-hd": "\f167", - "badge-tm-fill": "\f168", - "badge-tm": "\f169", - "badge-vo-fill": "\f16a", - "badge-vo": "\f16b", - "badge-vr-fill": "\f16c", - "badge-vr": "\f16d", - "badge-wc-fill": "\f16e", - "badge-wc": "\f16f", - "bag-check-fill": "\f170", - "bag-check": "\f171", - "bag-dash-fill": "\f172", - "bag-dash": "\f173", - "bag-fill": "\f174", - "bag-plus-fill": "\f175", - "bag-plus": "\f176", - "bag-x-fill": "\f177", - "bag-x": "\f178", - "bag": "\f179", - "bar-chart-fill": "\f17a", - "bar-chart-line-fill": "\f17b", - "bar-chart-line": "\f17c", - "bar-chart-steps": "\f17d", - "bar-chart": "\f17e", - "basket-fill": "\f17f", - "basket": "\f180", - "basket2-fill": "\f181", - "basket2": "\f182", - "basket3-fill": "\f183", - "basket3": "\f184", - "battery-charging": "\f185", - "battery-full": "\f186", - "battery-half": "\f187", - "battery": "\f188", - "bell-fill": "\f189", - "bell": "\f18a", - "bezier": "\f18b", - "bezier2": "\f18c", - "bicycle": "\f18d", - "binoculars-fill": "\f18e", - "binoculars": "\f18f", - "blockquote-left": "\f190", - "blockquote-right": "\f191", - "book-fill": "\f192", - "book-half": "\f193", - "book": "\f194", - "bookmark-check-fill": "\f195", - "bookmark-check": "\f196", - "bookmark-dash-fill": "\f197", - "bookmark-dash": "\f198", - "bookmark-fill": "\f199", - "bookmark-heart-fill": "\f19a", - "bookmark-heart": "\f19b", - "bookmark-plus-fill": "\f19c", - "bookmark-plus": "\f19d", - "bookmark-star-fill": "\f19e", - "bookmark-star": "\f19f", - "bookmark-x-fill": "\f1a0", - "bookmark-x": "\f1a1", - "bookmark": "\f1a2", - "bookmarks-fill": "\f1a3", - "bookmarks": "\f1a4", - "bookshelf": "\f1a5", - "bootstrap-fill": "\f1a6", - "bootstrap-reboot": "\f1a7", - "bootstrap": "\f1a8", - "border-all": "\f1a9", - "border-bottom": "\f1aa", - "border-center": "\f1ab", - "border-inner": "\f1ac", - "border-left": "\f1ad", - "border-middle": "\f1ae", - "border-outer": "\f1af", - "border-right": "\f1b0", - "border-style": "\f1b1", - "border-top": "\f1b2", - "border-width": "\f1b3", - "border": "\f1b4", - "bounding-box-circles": "\f1b5", - "bounding-box": "\f1b6", - "box-arrow-down-left": "\f1b7", - "box-arrow-down-right": "\f1b8", - "box-arrow-down": "\f1b9", - "box-arrow-in-down-left": "\f1ba", - "box-arrow-in-down-right": "\f1bb", - "box-arrow-in-down": "\f1bc", - "box-arrow-in-left": "\f1bd", - "box-arrow-in-right": "\f1be", - "box-arrow-in-up-left": "\f1bf", - "box-arrow-in-up-right": "\f1c0", - "box-arrow-in-up": "\f1c1", - "box-arrow-left": "\f1c2", - "box-arrow-right": "\f1c3", - "box-arrow-up-left": "\f1c4", - "box-arrow-up-right": "\f1c5", - "box-arrow-up": "\f1c6", - "box-seam": "\f1c7", - "box": "\f1c8", - "braces": "\f1c9", - "bricks": "\f1ca", - "briefcase-fill": "\f1cb", - "briefcase": "\f1cc", - "brightness-alt-high-fill": "\f1cd", - "brightness-alt-high": "\f1ce", - "brightness-alt-low-fill": "\f1cf", - "brightness-alt-low": "\f1d0", - "brightness-high-fill": "\f1d1", - "brightness-high": "\f1d2", - "brightness-low-fill": "\f1d3", - "brightness-low": "\f1d4", - "broadcast-pin": "\f1d5", - "broadcast": "\f1d6", - "brush-fill": "\f1d7", - "brush": "\f1d8", - "bucket-fill": "\f1d9", - "bucket": "\f1da", - "bug-fill": "\f1db", - "bug": "\f1dc", - "building": "\f1dd", - "bullseye": "\f1de", - "calculator-fill": "\f1df", - "calculator": "\f1e0", - "calendar-check-fill": "\f1e1", - "calendar-check": "\f1e2", - "calendar-date-fill": "\f1e3", - "calendar-date": "\f1e4", - "calendar-day-fill": "\f1e5", - "calendar-day": "\f1e6", - "calendar-event-fill": "\f1e7", - "calendar-event": "\f1e8", - "calendar-fill": "\f1e9", - "calendar-minus-fill": "\f1ea", - "calendar-minus": "\f1eb", - "calendar-month-fill": "\f1ec", - "calendar-month": "\f1ed", - "calendar-plus-fill": "\f1ee", - "calendar-plus": "\f1ef", - "calendar-range-fill": "\f1f0", - "calendar-range": "\f1f1", - "calendar-week-fill": "\f1f2", - "calendar-week": "\f1f3", - "calendar-x-fill": "\f1f4", - "calendar-x": "\f1f5", - "calendar": "\f1f6", - "calendar2-check-fill": "\f1f7", - "calendar2-check": "\f1f8", - "calendar2-date-fill": "\f1f9", - "calendar2-date": "\f1fa", - "calendar2-day-fill": "\f1fb", - "calendar2-day": "\f1fc", - "calendar2-event-fill": "\f1fd", - "calendar2-event": "\f1fe", - "calendar2-fill": "\f1ff", - "calendar2-minus-fill": "\f200", - "calendar2-minus": "\f201", - "calendar2-month-fill": "\f202", - "calendar2-month": "\f203", - "calendar2-plus-fill": "\f204", - "calendar2-plus": "\f205", - "calendar2-range-fill": "\f206", - "calendar2-range": "\f207", - "calendar2-week-fill": "\f208", - "calendar2-week": "\f209", - "calendar2-x-fill": "\f20a", - "calendar2-x": "\f20b", - "calendar2": "\f20c", - "calendar3-event-fill": "\f20d", - "calendar3-event": "\f20e", - "calendar3-fill": "\f20f", - "calendar3-range-fill": "\f210", - "calendar3-range": "\f211", - "calendar3-week-fill": "\f212", - "calendar3-week": "\f213", - "calendar3": "\f214", - "calendar4-event": "\f215", - "calendar4-range": "\f216", - "calendar4-week": "\f217", - "calendar4": "\f218", - "camera-fill": "\f219", - "camera-reels-fill": "\f21a", - "camera-reels": "\f21b", - "camera-video-fill": "\f21c", - "camera-video-off-fill": "\f21d", - "camera-video-off": "\f21e", - "camera-video": "\f21f", - "camera": "\f220", - "camera2": "\f221", - "capslock-fill": "\f222", - "capslock": "\f223", - "card-checklist": "\f224", - "card-heading": "\f225", - "card-image": "\f226", - "card-list": "\f227", - "card-text": "\f228", - "caret-down-fill": "\f229", - "caret-down-square-fill": "\f22a", - "caret-down-square": "\f22b", - "caret-down": "\f22c", - "caret-left-fill": "\f22d", - "caret-left-square-fill": "\f22e", - "caret-left-square": "\f22f", - "caret-left": "\f230", - "caret-right-fill": "\f231", - "caret-right-square-fill": "\f232", - "caret-right-square": "\f233", - "caret-right": "\f234", - "caret-up-fill": "\f235", - "caret-up-square-fill": "\f236", - "caret-up-square": "\f237", - "caret-up": "\f238", - "cart-check-fill": "\f239", - "cart-check": "\f23a", - "cart-dash-fill": "\f23b", - "cart-dash": "\f23c", - "cart-fill": "\f23d", - "cart-plus-fill": "\f23e", - "cart-plus": "\f23f", - "cart-x-fill": "\f240", - "cart-x": "\f241", - "cart": "\f242", - "cart2": "\f243", - "cart3": "\f244", - "cart4": "\f245", - "cash-stack": "\f246", - "cash": "\f247", - "cast": "\f248", - "chat-dots-fill": "\f249", - "chat-dots": "\f24a", - "chat-fill": "\f24b", - "chat-left-dots-fill": "\f24c", - "chat-left-dots": "\f24d", - "chat-left-fill": "\f24e", - "chat-left-quote-fill": "\f24f", - "chat-left-quote": "\f250", - "chat-left-text-fill": "\f251", - "chat-left-text": "\f252", - "chat-left": "\f253", - "chat-quote-fill": "\f254", - "chat-quote": "\f255", - "chat-right-dots-fill": "\f256", - "chat-right-dots": "\f257", - "chat-right-fill": "\f258", - "chat-right-quote-fill": "\f259", - "chat-right-quote": "\f25a", - "chat-right-text-fill": "\f25b", - "chat-right-text": "\f25c", - "chat-right": "\f25d", - "chat-square-dots-fill": "\f25e", - "chat-square-dots": "\f25f", - "chat-square-fill": "\f260", - "chat-square-quote-fill": "\f261", - "chat-square-quote": "\f262", - "chat-square-text-fill": "\f263", - "chat-square-text": "\f264", - "chat-square": "\f265", - "chat-text-fill": "\f266", - "chat-text": "\f267", - "chat": "\f268", - "check-all": "\f269", - "check-circle-fill": "\f26a", - "check-circle": "\f26b", - "check-square-fill": "\f26c", - "check-square": "\f26d", - "check": "\f26e", - "check2-all": "\f26f", - "check2-circle": "\f270", - "check2-square": "\f271", - "check2": "\f272", - "chevron-bar-contract": "\f273", - "chevron-bar-down": "\f274", - "chevron-bar-expand": "\f275", - "chevron-bar-left": "\f276", - "chevron-bar-right": "\f277", - "chevron-bar-up": "\f278", - "chevron-compact-down": "\f279", - "chevron-compact-left": "\f27a", - "chevron-compact-right": "\f27b", - "chevron-compact-up": "\f27c", - "chevron-contract": "\f27d", - "chevron-double-down": "\f27e", - "chevron-double-left": "\f27f", - "chevron-double-right": "\f280", - "chevron-double-up": "\f281", - "chevron-down": "\f282", - "chevron-expand": "\f283", - "chevron-left": "\f284", - "chevron-right": "\f285", - "chevron-up": "\f286", - "circle-fill": "\f287", - "circle-half": "\f288", - "circle-square": "\f289", - "circle": "\f28a", - "clipboard-check": "\f28b", - "clipboard-data": "\f28c", - "clipboard-minus": "\f28d", - "clipboard-plus": "\f28e", - "clipboard-x": "\f28f", - "clipboard": "\f290", - "clock-fill": "\f291", - "clock-history": "\f292", - "clock": "\f293", - "cloud-arrow-down-fill": "\f294", - "cloud-arrow-down": "\f295", - "cloud-arrow-up-fill": "\f296", - "cloud-arrow-up": "\f297", - "cloud-check-fill": "\f298", - "cloud-check": "\f299", - "cloud-download-fill": "\f29a", - "cloud-download": "\f29b", - "cloud-drizzle-fill": "\f29c", - "cloud-drizzle": "\f29d", - "cloud-fill": "\f29e", - "cloud-fog-fill": "\f29f", - "cloud-fog": "\f2a0", - "cloud-fog2-fill": "\f2a1", - "cloud-fog2": "\f2a2", - "cloud-hail-fill": "\f2a3", - "cloud-hail": "\f2a4", - "cloud-haze-fill": "\f2a6", - "cloud-haze": "\f2a7", - "cloud-haze2-fill": "\f2a8", - "cloud-lightning-fill": "\f2a9", - "cloud-lightning-rain-fill": "\f2aa", - "cloud-lightning-rain": "\f2ab", - "cloud-lightning": "\f2ac", - "cloud-minus-fill": "\f2ad", - "cloud-minus": "\f2ae", - "cloud-moon-fill": "\f2af", - "cloud-moon": "\f2b0", - "cloud-plus-fill": "\f2b1", - "cloud-plus": "\f2b2", - "cloud-rain-fill": "\f2b3", - "cloud-rain-heavy-fill": "\f2b4", - "cloud-rain-heavy": "\f2b5", - "cloud-rain": "\f2b6", - "cloud-slash-fill": "\f2b7", - "cloud-slash": "\f2b8", - "cloud-sleet-fill": "\f2b9", - "cloud-sleet": "\f2ba", - "cloud-snow-fill": "\f2bb", - "cloud-snow": "\f2bc", - "cloud-sun-fill": "\f2bd", - "cloud-sun": "\f2be", - "cloud-upload-fill": "\f2bf", - "cloud-upload": "\f2c0", - "cloud": "\f2c1", - "clouds-fill": "\f2c2", - "clouds": "\f2c3", - "cloudy-fill": "\f2c4", - "cloudy": "\f2c5", - "code-slash": "\f2c6", - "code-square": "\f2c7", - "code": "\f2c8", - "collection-fill": "\f2c9", - "collection-play-fill": "\f2ca", - "collection-play": "\f2cb", - "collection": "\f2cc", - "columns-gap": "\f2cd", - "columns": "\f2ce", - "command": "\f2cf", - "compass-fill": "\f2d0", - "compass": "\f2d1", - "cone-striped": "\f2d2", - "cone": "\f2d3", - "controller": "\f2d4", - "cpu-fill": "\f2d5", - "cpu": "\f2d6", - "credit-card-2-back-fill": "\f2d7", - "credit-card-2-back": "\f2d8", - "credit-card-2-front-fill": "\f2d9", - "credit-card-2-front": "\f2da", - "credit-card-fill": "\f2db", - "credit-card": "\f2dc", - "crop": "\f2dd", - "cup-fill": "\f2de", - "cup-straw": "\f2df", - "cup": "\f2e0", - "cursor-fill": "\f2e1", - "cursor-text": "\f2e2", - "cursor": "\f2e3", - "dash-circle-dotted": "\f2e4", - "dash-circle-fill": "\f2e5", - "dash-circle": "\f2e6", - "dash-square-dotted": "\f2e7", - "dash-square-fill": "\f2e8", - "dash-square": "\f2e9", - "dash": "\f2ea", - "diagram-2-fill": "\f2eb", - "diagram-2": "\f2ec", - "diagram-3-fill": "\f2ed", - "diagram-3": "\f2ee", - "diamond-fill": "\f2ef", - "diamond-half": "\f2f0", - "diamond": "\f2f1", - "dice-1-fill": "\f2f2", - "dice-1": "\f2f3", - "dice-2-fill": "\f2f4", - "dice-2": "\f2f5", - "dice-3-fill": "\f2f6", - "dice-3": "\f2f7", - "dice-4-fill": "\f2f8", - "dice-4": "\f2f9", - "dice-5-fill": "\f2fa", - "dice-5": "\f2fb", - "dice-6-fill": "\f2fc", - "dice-6": "\f2fd", - "disc-fill": "\f2fe", - "disc": "\f2ff", - "discord": "\f300", - "display-fill": "\f301", - "display": "\f302", - "distribute-horizontal": "\f303", - "distribute-vertical": "\f304", - "door-closed-fill": "\f305", - "door-closed": "\f306", - "door-open-fill": "\f307", - "door-open": "\f308", - "dot": "\f309", - "download": "\f30a", - "droplet-fill": "\f30b", - "droplet-half": "\f30c", - "droplet": "\f30d", - "earbuds": "\f30e", - "easel-fill": "\f30f", - "easel": "\f310", - "egg-fill": "\f311", - "egg-fried": "\f312", - "egg": "\f313", - "eject-fill": "\f314", - "eject": "\f315", - "emoji-angry-fill": "\f316", - "emoji-angry": "\f317", - "emoji-dizzy-fill": "\f318", - "emoji-dizzy": "\f319", - "emoji-expressionless-fill": "\f31a", - "emoji-expressionless": "\f31b", - "emoji-frown-fill": "\f31c", - "emoji-frown": "\f31d", - "emoji-heart-eyes-fill": "\f31e", - "emoji-heart-eyes": "\f31f", - "emoji-laughing-fill": "\f320", - "emoji-laughing": "\f321", - "emoji-neutral-fill": "\f322", - "emoji-neutral": "\f323", - "emoji-smile-fill": "\f324", - "emoji-smile-upside-down-fill": "\f325", - "emoji-smile-upside-down": "\f326", - "emoji-smile": "\f327", - "emoji-sunglasses-fill": "\f328", - "emoji-sunglasses": "\f329", - "emoji-wink-fill": "\f32a", - "emoji-wink": "\f32b", - "envelope-fill": "\f32c", - "envelope-open-fill": "\f32d", - "envelope-open": "\f32e", - "envelope": "\f32f", - "eraser-fill": "\f330", - "eraser": "\f331", - "exclamation-circle-fill": "\f332", - "exclamation-circle": "\f333", - "exclamation-diamond-fill": "\f334", - "exclamation-diamond": "\f335", - "exclamation-octagon-fill": "\f336", - "exclamation-octagon": "\f337", - "exclamation-square-fill": "\f338", - "exclamation-square": "\f339", - "exclamation-triangle-fill": "\f33a", - "exclamation-triangle": "\f33b", - "exclamation": "\f33c", - "exclude": "\f33d", - "eye-fill": "\f33e", - "eye-slash-fill": "\f33f", - "eye-slash": "\f340", - "eye": "\f341", - "eyedropper": "\f342", - "eyeglasses": "\f343", - "facebook": "\f344", - "file-arrow-down-fill": "\f345", - "file-arrow-down": "\f346", - "file-arrow-up-fill": "\f347", - "file-arrow-up": "\f348", - "file-bar-graph-fill": "\f349", - "file-bar-graph": "\f34a", - "file-binary-fill": "\f34b", - "file-binary": "\f34c", - "file-break-fill": "\f34d", - "file-break": "\f34e", - "file-check-fill": "\f34f", - "file-check": "\f350", - "file-code-fill": "\f351", - "file-code": "\f352", - "file-diff-fill": "\f353", - "file-diff": "\f354", - "file-earmark-arrow-down-fill": "\f355", - "file-earmark-arrow-down": "\f356", - "file-earmark-arrow-up-fill": "\f357", - "file-earmark-arrow-up": "\f358", - "file-earmark-bar-graph-fill": "\f359", - "file-earmark-bar-graph": "\f35a", - "file-earmark-binary-fill": "\f35b", - "file-earmark-binary": "\f35c", - "file-earmark-break-fill": "\f35d", - "file-earmark-break": "\f35e", - "file-earmark-check-fill": "\f35f", - "file-earmark-check": "\f360", - "file-earmark-code-fill": "\f361", - "file-earmark-code": "\f362", - "file-earmark-diff-fill": "\f363", - "file-earmark-diff": "\f364", - "file-earmark-easel-fill": "\f365", - "file-earmark-easel": "\f366", - "file-earmark-excel-fill": "\f367", - "file-earmark-excel": "\f368", - "file-earmark-fill": "\f369", - "file-earmark-font-fill": "\f36a", - "file-earmark-font": "\f36b", - "file-earmark-image-fill": "\f36c", - "file-earmark-image": "\f36d", - "file-earmark-lock-fill": "\f36e", - "file-earmark-lock": "\f36f", - "file-earmark-lock2-fill": "\f370", - "file-earmark-lock2": "\f371", - "file-earmark-medical-fill": "\f372", - "file-earmark-medical": "\f373", - "file-earmark-minus-fill": "\f374", - "file-earmark-minus": "\f375", - "file-earmark-music-fill": "\f376", - "file-earmark-music": "\f377", - "file-earmark-person-fill": "\f378", - "file-earmark-person": "\f379", - "file-earmark-play-fill": "\f37a", - "file-earmark-play": "\f37b", - "file-earmark-plus-fill": "\f37c", - "file-earmark-plus": "\f37d", - "file-earmark-post-fill": "\f37e", - "file-earmark-post": "\f37f", - "file-earmark-ppt-fill": "\f380", - "file-earmark-ppt": "\f381", - "file-earmark-richtext-fill": "\f382", - "file-earmark-richtext": "\f383", - "file-earmark-ruled-fill": "\f384", - "file-earmark-ruled": "\f385", - "file-earmark-slides-fill": "\f386", - "file-earmark-slides": "\f387", - "file-earmark-spreadsheet-fill": "\f388", - "file-earmark-spreadsheet": "\f389", - "file-earmark-text-fill": "\f38a", - "file-earmark-text": "\f38b", - "file-earmark-word-fill": "\f38c", - "file-earmark-word": "\f38d", - "file-earmark-x-fill": "\f38e", - "file-earmark-x": "\f38f", - "file-earmark-zip-fill": "\f390", - "file-earmark-zip": "\f391", - "file-earmark": "\f392", - "file-easel-fill": "\f393", - "file-easel": "\f394", - "file-excel-fill": "\f395", - "file-excel": "\f396", - "file-fill": "\f397", - "file-font-fill": "\f398", - "file-font": "\f399", - "file-image-fill": "\f39a", - "file-image": "\f39b", - "file-lock-fill": "\f39c", - "file-lock": "\f39d", - "file-lock2-fill": "\f39e", - "file-lock2": "\f39f", - "file-medical-fill": "\f3a0", - "file-medical": "\f3a1", - "file-minus-fill": "\f3a2", - "file-minus": "\f3a3", - "file-music-fill": "\f3a4", - "file-music": "\f3a5", - "file-person-fill": "\f3a6", - "file-person": "\f3a7", - "file-play-fill": "\f3a8", - "file-play": "\f3a9", - "file-plus-fill": "\f3aa", - "file-plus": "\f3ab", - "file-post-fill": "\f3ac", - "file-post": "\f3ad", - "file-ppt-fill": "\f3ae", - "file-ppt": "\f3af", - "file-richtext-fill": "\f3b0", - "file-richtext": "\f3b1", - "file-ruled-fill": "\f3b2", - "file-ruled": "\f3b3", - "file-slides-fill": "\f3b4", - "file-slides": "\f3b5", - "file-spreadsheet-fill": "\f3b6", - "file-spreadsheet": "\f3b7", - "file-text-fill": "\f3b8", - "file-text": "\f3b9", - "file-word-fill": "\f3ba", - "file-word": "\f3bb", - "file-x-fill": "\f3bc", - "file-x": "\f3bd", - "file-zip-fill": "\f3be", - "file-zip": "\f3bf", - "file": "\f3c0", - "files-alt": "\f3c1", - "files": "\f3c2", - "film": "\f3c3", - "filter-circle-fill": "\f3c4", - "filter-circle": "\f3c5", - "filter-left": "\f3c6", - "filter-right": "\f3c7", - "filter-square-fill": "\f3c8", - "filter-square": "\f3c9", - "filter": "\f3ca", - "flag-fill": "\f3cb", - "flag": "\f3cc", - "flower1": "\f3cd", - "flower2": "\f3ce", - "flower3": "\f3cf", - "folder-check": "\f3d0", - "folder-fill": "\f3d1", - "folder-minus": "\f3d2", - "folder-plus": "\f3d3", - "folder-symlink-fill": "\f3d4", - "folder-symlink": "\f3d5", - "folder-x": "\f3d6", - "folder": "\f3d7", - "folder2-open": "\f3d8", - "folder2": "\f3d9", - "fonts": "\f3da", - "forward-fill": "\f3db", - "forward": "\f3dc", - "front": "\f3dd", - "fullscreen-exit": "\f3de", - "fullscreen": "\f3df", - "funnel-fill": "\f3e0", - "funnel": "\f3e1", - "gear-fill": "\f3e2", - "gear-wide-connected": "\f3e3", - "gear-wide": "\f3e4", - "gear": "\f3e5", - "gem": "\f3e6", - "geo-alt-fill": "\f3e7", - "geo-alt": "\f3e8", - "geo-fill": "\f3e9", - "geo": "\f3ea", - "gift-fill": "\f3eb", - "gift": "\f3ec", - "github": "\f3ed", - "globe": "\f3ee", - "globe2": "\f3ef", - "google": "\f3f0", - "graph-down": "\f3f1", - "graph-up": "\f3f2", - "grid-1x2-fill": "\f3f3", - "grid-1x2": "\f3f4", - "grid-3x2-gap-fill": "\f3f5", - "grid-3x2-gap": "\f3f6", - "grid-3x2": "\f3f7", - "grid-3x3-gap-fill": "\f3f8", - "grid-3x3-gap": "\f3f9", - "grid-3x3": "\f3fa", - "grid-fill": "\f3fb", - "grid": "\f3fc", - "grip-horizontal": "\f3fd", - "grip-vertical": "\f3fe", - "hammer": "\f3ff", - "hand-index-fill": "\f400", - "hand-index-thumb-fill": "\f401", - "hand-index-thumb": "\f402", - "hand-index": "\f403", - "hand-thumbs-down-fill": "\f404", - "hand-thumbs-down": "\f405", - "hand-thumbs-up-fill": "\f406", - "hand-thumbs-up": "\f407", - "handbag-fill": "\f408", - "handbag": "\f409", - "hash": "\f40a", - "hdd-fill": "\f40b", - "hdd-network-fill": "\f40c", - "hdd-network": "\f40d", - "hdd-rack-fill": "\f40e", - "hdd-rack": "\f40f", - "hdd-stack-fill": "\f410", - "hdd-stack": "\f411", - "hdd": "\f412", - "headphones": "\f413", - "headset": "\f414", - "heart-fill": "\f415", - "heart-half": "\f416", - "heart": "\f417", - "heptagon-fill": "\f418", - "heptagon-half": "\f419", - "heptagon": "\f41a", - "hexagon-fill": "\f41b", - "hexagon-half": "\f41c", - "hexagon": "\f41d", - "hourglass-bottom": "\f41e", - "hourglass-split": "\f41f", - "hourglass-top": "\f420", - "hourglass": "\f421", - "house-door-fill": "\f422", - "house-door": "\f423", - "house-fill": "\f424", - "house": "\f425", - "hr": "\f426", - "hurricane": "\f427", - "image-alt": "\f428", - "image-fill": "\f429", - "image": "\f42a", - "images": "\f42b", - "inbox-fill": "\f42c", - "inbox": "\f42d", - "inboxes-fill": "\f42e", - "inboxes": "\f42f", - "info-circle-fill": "\f430", - "info-circle": "\f431", - "info-square-fill": "\f432", - "info-square": "\f433", - "info": "\f434", - "input-cursor-text": "\f435", - "input-cursor": "\f436", - "instagram": "\f437", - "intersect": "\f438", - "journal-album": "\f439", - "journal-arrow-down": "\f43a", - "journal-arrow-up": "\f43b", - "journal-bookmark-fill": "\f43c", - "journal-bookmark": "\f43d", - "journal-check": "\f43e", - "journal-code": "\f43f", - "journal-medical": "\f440", - "journal-minus": "\f441", - "journal-plus": "\f442", - "journal-richtext": "\f443", - "journal-text": "\f444", - "journal-x": "\f445", - "journal": "\f446", - "journals": "\f447", - "joystick": "\f448", - "justify-left": "\f449", - "justify-right": "\f44a", - "justify": "\f44b", - "kanban-fill": "\f44c", - "kanban": "\f44d", - "key-fill": "\f44e", - "key": "\f44f", - "keyboard-fill": "\f450", - "keyboard": "\f451", - "ladder": "\f452", - "lamp-fill": "\f453", - "lamp": "\f454", - "laptop-fill": "\f455", - "laptop": "\f456", - "layer-backward": "\f457", - "layer-forward": "\f458", - "layers-fill": "\f459", - "layers-half": "\f45a", - "layers": "\f45b", - "layout-sidebar-inset-reverse": "\f45c", - "layout-sidebar-inset": "\f45d", - "layout-sidebar-reverse": "\f45e", - "layout-sidebar": "\f45f", - "layout-split": "\f460", - "layout-text-sidebar-reverse": "\f461", - "layout-text-sidebar": "\f462", - "layout-text-window-reverse": "\f463", - "layout-text-window": "\f464", - "layout-three-columns": "\f465", - "layout-wtf": "\f466", - "life-preserver": "\f467", - "lightbulb-fill": "\f468", - "lightbulb-off-fill": "\f469", - "lightbulb-off": "\f46a", - "lightbulb": "\f46b", - "lightning-charge-fill": "\f46c", - "lightning-charge": "\f46d", - "lightning-fill": "\f46e", - "lightning": "\f46f", - "link-45deg": "\f470", - "link": "\f471", - "linkedin": "\f472", - "list-check": "\f473", - "list-nested": "\f474", - "list-ol": "\f475", - "list-stars": "\f476", - "list-task": "\f477", - "list-ul": "\f478", - "list": "\f479", - "lock-fill": "\f47a", - "lock": "\f47b", - "mailbox": "\f47c", - "mailbox2": "\f47d", - "map-fill": "\f47e", - "map": "\f47f", - "markdown-fill": "\f480", - "markdown": "\f481", - "mask": "\f482", - "megaphone-fill": "\f483", - "megaphone": "\f484", - "menu-app-fill": "\f485", - "menu-app": "\f486", - "menu-button-fill": "\f487", - "menu-button-wide-fill": "\f488", - "menu-button-wide": "\f489", - "menu-button": "\f48a", - "menu-down": "\f48b", - "menu-up": "\f48c", - "mic-fill": "\f48d", - "mic-mute-fill": "\f48e", - "mic-mute": "\f48f", - "mic": "\f490", - "minecart-loaded": "\f491", - "minecart": "\f492", - "moisture": "\f493", - "moon-fill": "\f494", - "moon-stars-fill": "\f495", - "moon-stars": "\f496", - "moon": "\f497", - "mouse-fill": "\f498", - "mouse": "\f499", - "mouse2-fill": "\f49a", - "mouse2": "\f49b", - "mouse3-fill": "\f49c", - "mouse3": "\f49d", - "music-note-beamed": "\f49e", - "music-note-list": "\f49f", - "music-note": "\f4a0", - "music-player-fill": "\f4a1", - "music-player": "\f4a2", - "newspaper": "\f4a3", - "node-minus-fill": "\f4a4", - "node-minus": "\f4a5", - "node-plus-fill": "\f4a6", - "node-plus": "\f4a7", - "nut-fill": "\f4a8", - "nut": "\f4a9", - "octagon-fill": "\f4aa", - "octagon-half": "\f4ab", - "octagon": "\f4ac", - "option": "\f4ad", - "outlet": "\f4ae", - "paint-bucket": "\f4af", - "palette-fill": "\f4b0", - "palette": "\f4b1", - "palette2": "\f4b2", - "paperclip": "\f4b3", - "paragraph": "\f4b4", - "patch-check-fill": "\f4b5", - "patch-check": "\f4b6", - "patch-exclamation-fill": "\f4b7", - "patch-exclamation": "\f4b8", - "patch-minus-fill": "\f4b9", - "patch-minus": "\f4ba", - "patch-plus-fill": "\f4bb", - "patch-plus": "\f4bc", - "patch-question-fill": "\f4bd", - "patch-question": "\f4be", - "pause-btn-fill": "\f4bf", - "pause-btn": "\f4c0", - "pause-circle-fill": "\f4c1", - "pause-circle": "\f4c2", - "pause-fill": "\f4c3", - "pause": "\f4c4", - "peace-fill": "\f4c5", - "peace": "\f4c6", - "pen-fill": "\f4c7", - "pen": "\f4c8", - "pencil-fill": "\f4c9", - "pencil-square": "\f4ca", - "pencil": "\f4cb", - "pentagon-fill": "\f4cc", - "pentagon-half": "\f4cd", - "pentagon": "\f4ce", - "people-fill": "\f4cf", - "people": "\f4d0", - "percent": "\f4d1", - "person-badge-fill": "\f4d2", - "person-badge": "\f4d3", - "person-bounding-box": "\f4d4", - "person-check-fill": "\f4d5", - "person-check": "\f4d6", - "person-circle": "\f4d7", - "person-dash-fill": "\f4d8", - "person-dash": "\f4d9", - "person-fill": "\f4da", - "person-lines-fill": "\f4db", - "person-plus-fill": "\f4dc", - "person-plus": "\f4dd", - "person-square": "\f4de", - "person-x-fill": "\f4df", - "person-x": "\f4e0", - "person": "\f4e1", - "phone-fill": "\f4e2", - "phone-landscape-fill": "\f4e3", - "phone-landscape": "\f4e4", - "phone-vibrate-fill": "\f4e5", - "phone-vibrate": "\f4e6", - "phone": "\f4e7", - "pie-chart-fill": "\f4e8", - "pie-chart": "\f4e9", - "pin-angle-fill": "\f4ea", - "pin-angle": "\f4eb", - "pin-fill": "\f4ec", - "pin": "\f4ed", - "pip-fill": "\f4ee", - "pip": "\f4ef", - "play-btn-fill": "\f4f0", - "play-btn": "\f4f1", - "play-circle-fill": "\f4f2", - "play-circle": "\f4f3", - "play-fill": "\f4f4", - "play": "\f4f5", - "plug-fill": "\f4f6", - "plug": "\f4f7", - "plus-circle-dotted": "\f4f8", - "plus-circle-fill": "\f4f9", - "plus-circle": "\f4fa", - "plus-square-dotted": "\f4fb", - "plus-square-fill": "\f4fc", - "plus-square": "\f4fd", - "plus": "\f4fe", - "power": "\f4ff", - "printer-fill": "\f500", - "printer": "\f501", - "puzzle-fill": "\f502", - "puzzle": "\f503", - "question-circle-fill": "\f504", - "question-circle": "\f505", - "question-diamond-fill": "\f506", - "question-diamond": "\f507", - "question-octagon-fill": "\f508", - "question-octagon": "\f509", - "question-square-fill": "\f50a", - "question-square": "\f50b", - "question": "\f50c", - "rainbow": "\f50d", - "receipt-cutoff": "\f50e", - "receipt": "\f50f", - "reception-0": "\f510", - "reception-1": "\f511", - "reception-2": "\f512", - "reception-3": "\f513", - "reception-4": "\f514", - "record-btn-fill": "\f515", - "record-btn": "\f516", - "record-circle-fill": "\f517", - "record-circle": "\f518", - "record-fill": "\f519", - "record": "\f51a", - "record2-fill": "\f51b", - "record2": "\f51c", - "reply-all-fill": "\f51d", - "reply-all": "\f51e", - "reply-fill": "\f51f", - "reply": "\f520", - "rss-fill": "\f521", - "rss": "\f522", - "rulers": "\f523", - "save-fill": "\f524", - "save": "\f525", - "save2-fill": "\f526", - "save2": "\f527", - "scissors": "\f528", - "screwdriver": "\f529", - "search": "\f52a", - "segmented-nav": "\f52b", - "server": "\f52c", - "share-fill": "\f52d", - "share": "\f52e", - "shield-check": "\f52f", - "shield-exclamation": "\f530", - "shield-fill-check": "\f531", - "shield-fill-exclamation": "\f532", - "shield-fill-minus": "\f533", - "shield-fill-plus": "\f534", - "shield-fill-x": "\f535", - "shield-fill": "\f536", - "shield-lock-fill": "\f537", - "shield-lock": "\f538", - "shield-minus": "\f539", - "shield-plus": "\f53a", - "shield-shaded": "\f53b", - "shield-slash-fill": "\f53c", - "shield-slash": "\f53d", - "shield-x": "\f53e", - "shield": "\f53f", - "shift-fill": "\f540", - "shift": "\f541", - "shop-window": "\f542", - "shop": "\f543", - "shuffle": "\f544", - "signpost-2-fill": "\f545", - "signpost-2": "\f546", - "signpost-fill": "\f547", - "signpost-split-fill": "\f548", - "signpost-split": "\f549", - "signpost": "\f54a", - "sim-fill": "\f54b", - "sim": "\f54c", - "skip-backward-btn-fill": "\f54d", - "skip-backward-btn": "\f54e", - "skip-backward-circle-fill": "\f54f", - "skip-backward-circle": "\f550", - "skip-backward-fill": "\f551", - "skip-backward": "\f552", - "skip-end-btn-fill": "\f553", - "skip-end-btn": "\f554", - "skip-end-circle-fill": "\f555", - "skip-end-circle": "\f556", - "skip-end-fill": "\f557", - "skip-end": "\f558", - "skip-forward-btn-fill": "\f559", - "skip-forward-btn": "\f55a", - "skip-forward-circle-fill": "\f55b", - "skip-forward-circle": "\f55c", - "skip-forward-fill": "\f55d", - "skip-forward": "\f55e", - "skip-start-btn-fill": "\f55f", - "skip-start-btn": "\f560", - "skip-start-circle-fill": "\f561", - "skip-start-circle": "\f562", - "skip-start-fill": "\f563", - "skip-start": "\f564", - "slack": "\f565", - "slash-circle-fill": "\f566", - "slash-circle": "\f567", - "slash-square-fill": "\f568", - "slash-square": "\f569", - "slash": "\f56a", - "sliders": "\f56b", - "smartwatch": "\f56c", - "snow": "\f56d", - "snow2": "\f56e", - "snow3": "\f56f", - "sort-alpha-down-alt": "\f570", - "sort-alpha-down": "\f571", - "sort-alpha-up-alt": "\f572", - "sort-alpha-up": "\f573", - "sort-down-alt": "\f574", - "sort-down": "\f575", - "sort-numeric-down-alt": "\f576", - "sort-numeric-down": "\f577", - "sort-numeric-up-alt": "\f578", - "sort-numeric-up": "\f579", - "sort-up-alt": "\f57a", - "sort-up": "\f57b", - "soundwave": "\f57c", - "speaker-fill": "\f57d", - "speaker": "\f57e", - "speedometer": "\f57f", - "speedometer2": "\f580", - "spellcheck": "\f581", - "square-fill": "\f582", - "square-half": "\f583", - "square": "\f584", - "stack": "\f585", - "star-fill": "\f586", - "star-half": "\f587", - "star": "\f588", - "stars": "\f589", - "stickies-fill": "\f58a", - "stickies": "\f58b", - "sticky-fill": "\f58c", - "sticky": "\f58d", - "stop-btn-fill": "\f58e", - "stop-btn": "\f58f", - "stop-circle-fill": "\f590", - "stop-circle": "\f591", - "stop-fill": "\f592", - "stop": "\f593", - "stoplights-fill": "\f594", - "stoplights": "\f595", - "stopwatch-fill": "\f596", - "stopwatch": "\f597", - "subtract": "\f598", - "suit-club-fill": "\f599", - "suit-club": "\f59a", - "suit-diamond-fill": "\f59b", - "suit-diamond": "\f59c", - "suit-heart-fill": "\f59d", - "suit-heart": "\f59e", - "suit-spade-fill": "\f59f", - "suit-spade": "\f5a0", - "sun-fill": "\f5a1", - "sun": "\f5a2", - "sunglasses": "\f5a3", - "sunrise-fill": "\f5a4", - "sunrise": "\f5a5", - "sunset-fill": "\f5a6", - "sunset": "\f5a7", - "symmetry-horizontal": "\f5a8", - "symmetry-vertical": "\f5a9", - "table": "\f5aa", - "tablet-fill": "\f5ab", - "tablet-landscape-fill": "\f5ac", - "tablet-landscape": "\f5ad", - "tablet": "\f5ae", - "tag-fill": "\f5af", - "tag": "\f5b0", - "tags-fill": "\f5b1", - "tags": "\f5b2", - "telegram": "\f5b3", - "telephone-fill": "\f5b4", - "telephone-forward-fill": "\f5b5", - "telephone-forward": "\f5b6", - "telephone-inbound-fill": "\f5b7", - "telephone-inbound": "\f5b8", - "telephone-minus-fill": "\f5b9", - "telephone-minus": "\f5ba", - "telephone-outbound-fill": "\f5bb", - "telephone-outbound": "\f5bc", - "telephone-plus-fill": "\f5bd", - "telephone-plus": "\f5be", - "telephone-x-fill": "\f5bf", - "telephone-x": "\f5c0", - "telephone": "\f5c1", - "terminal-fill": "\f5c2", - "terminal": "\f5c3", - "text-center": "\f5c4", - "text-indent-left": "\f5c5", - "text-indent-right": "\f5c6", - "text-left": "\f5c7", - "text-paragraph": "\f5c8", - "text-right": "\f5c9", - "textarea-resize": "\f5ca", - "textarea-t": "\f5cb", - "textarea": "\f5cc", - "thermometer-half": "\f5cd", - "thermometer-high": "\f5ce", - "thermometer-low": "\f5cf", - "thermometer-snow": "\f5d0", - "thermometer-sun": "\f5d1", - "thermometer": "\f5d2", - "three-dots-vertical": "\f5d3", - "three-dots": "\f5d4", - "toggle-off": "\f5d5", - "toggle-on": "\f5d6", - "toggle2-off": "\f5d7", - "toggle2-on": "\f5d8", - "toggles": "\f5d9", - "toggles2": "\f5da", - "tools": "\f5db", - "tornado": "\f5dc", - "trash-fill": "\f5dd", - "trash": "\f5de", - "trash2-fill": "\f5df", - "trash2": "\f5e0", - "tree-fill": "\f5e1", - "tree": "\f5e2", - "triangle-fill": "\f5e3", - "triangle-half": "\f5e4", - "triangle": "\f5e5", - "trophy-fill": "\f5e6", - "trophy": "\f5e7", - "tropical-storm": "\f5e8", - "truck-flatbed": "\f5e9", - "truck": "\f5ea", - "tsunami": "\f5eb", - "tv-fill": "\f5ec", - "tv": "\f5ed", - "twitch": "\f5ee", - "twitter": "\f5ef", - "type-bold": "\f5f0", - "type-h1": "\f5f1", - "type-h2": "\f5f2", - "type-h3": "\f5f3", - "type-italic": "\f5f4", - "type-strikethrough": "\f5f5", - "type-underline": "\f5f6", - "type": "\f5f7", - "ui-checks-grid": "\f5f8", - "ui-checks": "\f5f9", - "ui-radios-grid": "\f5fa", - "ui-radios": "\f5fb", - "umbrella-fill": "\f5fc", - "umbrella": "\f5fd", - "union": "\f5fe", - "unlock-fill": "\f5ff", - "unlock": "\f600", - "upc-scan": "\f601", - "upc": "\f602", - "upload": "\f603", - "vector-pen": "\f604", - "view-list": "\f605", - "view-stacked": "\f606", - "vinyl-fill": "\f607", - "vinyl": "\f608", - "voicemail": "\f609", - "volume-down-fill": "\f60a", - "volume-down": "\f60b", - "volume-mute-fill": "\f60c", - "volume-mute": "\f60d", - "volume-off-fill": "\f60e", - "volume-off": "\f60f", - "volume-up-fill": "\f610", - "volume-up": "\f611", - "vr": "\f612", - "wallet-fill": "\f613", - "wallet": "\f614", - "wallet2": "\f615", - "watch": "\f616", - "water": "\f617", - "whatsapp": "\f618", - "wifi-1": "\f619", - "wifi-2": "\f61a", - "wifi-off": "\f61b", - "wifi": "\f61c", - "wind": "\f61d", - "window-dock": "\f61e", - "window-sidebar": "\f61f", - "window": "\f620", - "wrench": "\f621", - "x-circle-fill": "\f622", - "x-circle": "\f623", - "x-diamond-fill": "\f624", - "x-diamond": "\f625", - "x-octagon-fill": "\f626", - "x-octagon": "\f627", - "x-square-fill": "\f628", - "x-square": "\f629", - "x": "\f62a", - "youtube": "\f62b", - "zoom-in": "\f62c", - "zoom-out": "\f62d", - "bank": "\f62e", - "bank2": "\f62f", - "bell-slash-fill": "\f630", - "bell-slash": "\f631", - "cash-coin": "\f632", - "check-lg": "\f633", - "coin": "\f634", - "currency-bitcoin": "\f635", - "currency-dollar": "\f636", - "currency-euro": "\f637", - "currency-exchange": "\f638", - "currency-pound": "\f639", - "currency-yen": "\f63a", - "dash-lg": "\f63b", - "exclamation-lg": "\f63c", - "file-earmark-pdf-fill": "\f63d", - "file-earmark-pdf": "\f63e", - "file-pdf-fill": "\f63f", - "file-pdf": "\f640", - "gender-ambiguous": "\f641", - "gender-female": "\f642", - "gender-male": "\f643", - "gender-trans": "\f644", - "headset-vr": "\f645", - "info-lg": "\f646", - "mastodon": "\f647", - "messenger": "\f648", - "piggy-bank-fill": "\f649", - "piggy-bank": "\f64a", - "pin-map-fill": "\f64b", - "pin-map": "\f64c", - "plus-lg": "\f64d", - "question-lg": "\f64e", - "recycle": "\f64f", - "reddit": "\f650", - "safe-fill": "\f651", - "safe2-fill": "\f652", - "safe2": "\f653", - "sd-card-fill": "\f654", - "sd-card": "\f655", - "skype": "\f656", - "slash-lg": "\f657", - "translate": "\f658", - "x-lg": "\f659", - "safe": "\f65a", - "apple": "\f65b", - "microsoft": "\f65d", - "windows": "\f65e", - "behance": "\f65c", - "dribbble": "\f65f", - "line": "\f660", - "medium": "\f661", - "paypal": "\f662", - "pinterest": "\f663", - "signal": "\f664", - "snapchat": "\f665", - "spotify": "\f666", - "stack-overflow": "\f667", - "strava": "\f668", - "wordpress": "\f669", - "vimeo": "\f66a", - "activity": "\f66b", - "easel2-fill": "\f66c", - "easel2": "\f66d", - "easel3-fill": "\f66e", - "easel3": "\f66f", - "fan": "\f670", - "fingerprint": "\f671", - "graph-down-arrow": "\f672", - "graph-up-arrow": "\f673", - "hypnotize": "\f674", - "magic": "\f675", - "person-rolodex": "\f676", - "person-video": "\f677", - "person-video2": "\f678", - "person-video3": "\f679", - "person-workspace": "\f67a", - "radioactive": "\f67b", - "webcam-fill": "\f67c", - "webcam": "\f67d", - "yin-yang": "\f67e", - "bandaid-fill": "\f680", - "bandaid": "\f681", - "bluetooth": "\f682", - "body-text": "\f683", - "boombox": "\f684", - "boxes": "\f685", - "dpad-fill": "\f686", - "dpad": "\f687", - "ear-fill": "\f688", - "ear": "\f689", - "envelope-check-fill": "\f68b", - "envelope-check": "\f68c", - "envelope-dash-fill": "\f68e", - "envelope-dash": "\f68f", - "envelope-exclamation-fill": "\f691", - "envelope-exclamation": "\f692", - "envelope-plus-fill": "\f693", - "envelope-plus": "\f694", - "envelope-slash-fill": "\f696", - "envelope-slash": "\f697", - "envelope-x-fill": "\f699", - "envelope-x": "\f69a", - "explicit-fill": "\f69b", - "explicit": "\f69c", - "git": "\f69d", - "infinity": "\f69e", - "list-columns-reverse": "\f69f", - "list-columns": "\f6a0", - "meta": "\f6a1", - "nintendo-switch": "\f6a4", - "pc-display-horizontal": "\f6a5", - "pc-display": "\f6a6", - "pc-horizontal": "\f6a7", - "pc": "\f6a8", - "playstation": "\f6a9", - "plus-slash-minus": "\f6aa", - "projector-fill": "\f6ab", - "projector": "\f6ac", - "qr-code-scan": "\f6ad", - "qr-code": "\f6ae", - "quora": "\f6af", - "quote": "\f6b0", - "robot": "\f6b1", - "send-check-fill": "\f6b2", - "send-check": "\f6b3", - "send-dash-fill": "\f6b4", - "send-dash": "\f6b5", - "send-exclamation-fill": "\f6b7", - "send-exclamation": "\f6b8", - "send-fill": "\f6b9", - "send-plus-fill": "\f6ba", - "send-plus": "\f6bb", - "send-slash-fill": "\f6bc", - "send-slash": "\f6bd", - "send-x-fill": "\f6be", - "send-x": "\f6bf", - "send": "\f6c0", - "steam": "\f6c1", - "terminal-dash": "\f6c3", - "terminal-plus": "\f6c4", - "terminal-split": "\f6c5", - "ticket-detailed-fill": "\f6c6", - "ticket-detailed": "\f6c7", - "ticket-fill": "\f6c8", - "ticket-perforated-fill": "\f6c9", - "ticket-perforated": "\f6ca", - "ticket": "\f6cb", - "tiktok": "\f6cc", - "window-dash": "\f6cd", - "window-desktop": "\f6ce", - "window-fullscreen": "\f6cf", - "window-plus": "\f6d0", - "window-split": "\f6d1", - "window-stack": "\f6d2", - "window-x": "\f6d3", - "xbox": "\f6d4", - "ethernet": "\f6d5", - "hdmi-fill": "\f6d6", - "hdmi": "\f6d7", - "usb-c-fill": "\f6d8", - "usb-c": "\f6d9", - "usb-fill": "\f6da", - "usb-plug-fill": "\f6db", - "usb-plug": "\f6dc", - "usb-symbol": "\f6dd", - "usb": "\f6de", - "boombox-fill": "\f6df", - "displayport": "\f6e1", - "gpu-card": "\f6e2", - "memory": "\f6e3", - "modem-fill": "\f6e4", - "modem": "\f6e5", - "motherboard-fill": "\f6e6", - "motherboard": "\f6e7", - "optical-audio-fill": "\f6e8", - "optical-audio": "\f6e9", - "pci-card": "\f6ea", - "router-fill": "\f6eb", - "router": "\f6ec", - "thunderbolt-fill": "\f6ef", - "thunderbolt": "\f6f0", - "usb-drive-fill": "\f6f1", - "usb-drive": "\f6f2", - "usb-micro-fill": "\f6f3", - "usb-micro": "\f6f4", - "usb-mini-fill": "\f6f5", - "usb-mini": "\f6f6", - "cloud-haze2": "\f6f7", - "device-hdd-fill": "\f6f8", - "device-hdd": "\f6f9", - "device-ssd-fill": "\f6fa", - "device-ssd": "\f6fb", - "displayport-fill": "\f6fc", - "mortarboard-fill": "\f6fd", - "mortarboard": "\f6fe", - "terminal-x": "\f6ff", - "arrow-through-heart-fill": "\f700", - "arrow-through-heart": "\f701", - "badge-sd-fill": "\f702", - "badge-sd": "\f703", - "bag-heart-fill": "\f704", - "bag-heart": "\f705", - "balloon-fill": "\f706", - "balloon-heart-fill": "\f707", - "balloon-heart": "\f708", - "balloon": "\f709", - "box2-fill": "\f70a", - "box2-heart-fill": "\f70b", - "box2-heart": "\f70c", - "box2": "\f70d", - "braces-asterisk": "\f70e", - "calendar-heart-fill": "\f70f", - "calendar-heart": "\f710", - "calendar2-heart-fill": "\f711", - "calendar2-heart": "\f712", - "chat-heart-fill": "\f713", - "chat-heart": "\f714", - "chat-left-heart-fill": "\f715", - "chat-left-heart": "\f716", - "chat-right-heart-fill": "\f717", - "chat-right-heart": "\f718", - "chat-square-heart-fill": "\f719", - "chat-square-heart": "\f71a", - "clipboard-check-fill": "\f71b", - "clipboard-data-fill": "\f71c", - "clipboard-fill": "\f71d", - "clipboard-heart-fill": "\f71e", - "clipboard-heart": "\f71f", - "clipboard-minus-fill": "\f720", - "clipboard-plus-fill": "\f721", - "clipboard-pulse": "\f722", - "clipboard-x-fill": "\f723", - "clipboard2-check-fill": "\f724", - "clipboard2-check": "\f725", - "clipboard2-data-fill": "\f726", - "clipboard2-data": "\f727", - "clipboard2-fill": "\f728", - "clipboard2-heart-fill": "\f729", - "clipboard2-heart": "\f72a", - "clipboard2-minus-fill": "\f72b", - "clipboard2-minus": "\f72c", - "clipboard2-plus-fill": "\f72d", - "clipboard2-plus": "\f72e", - "clipboard2-pulse-fill": "\f72f", - "clipboard2-pulse": "\f730", - "clipboard2-x-fill": "\f731", - "clipboard2-x": "\f732", - "clipboard2": "\f733", - "emoji-kiss-fill": "\f734", - "emoji-kiss": "\f735", - "envelope-heart-fill": "\f736", - "envelope-heart": "\f737", - "envelope-open-heart-fill": "\f738", - "envelope-open-heart": "\f739", - "envelope-paper-fill": "\f73a", - "envelope-paper-heart-fill": "\f73b", - "envelope-paper-heart": "\f73c", - "envelope-paper": "\f73d", - "filetype-aac": "\f73e", - "filetype-ai": "\f73f", - "filetype-bmp": "\f740", - "filetype-cs": "\f741", - "filetype-css": "\f742", - "filetype-csv": "\f743", - "filetype-doc": "\f744", - "filetype-docx": "\f745", - "filetype-exe": "\f746", - "filetype-gif": "\f747", - "filetype-heic": "\f748", - "filetype-html": "\f749", - "filetype-java": "\f74a", - "filetype-jpg": "\f74b", - "filetype-js": "\f74c", - "filetype-jsx": "\f74d", - "filetype-key": "\f74e", - "filetype-m4p": "\f74f", - "filetype-md": "\f750", - "filetype-mdx": "\f751", - "filetype-mov": "\f752", - "filetype-mp3": "\f753", - "filetype-mp4": "\f754", - "filetype-otf": "\f755", - "filetype-pdf": "\f756", - "filetype-php": "\f757", - "filetype-png": "\f758", - "filetype-ppt": "\f75a", - "filetype-psd": "\f75b", - "filetype-py": "\f75c", - "filetype-raw": "\f75d", - "filetype-rb": "\f75e", - "filetype-sass": "\f75f", - "filetype-scss": "\f760", - "filetype-sh": "\f761", - "filetype-svg": "\f762", - "filetype-tiff": "\f763", - "filetype-tsx": "\f764", - "filetype-ttf": "\f765", - "filetype-txt": "\f766", - "filetype-wav": "\f767", - "filetype-woff": "\f768", - "filetype-xls": "\f76a", - "filetype-xml": "\f76b", - "filetype-yml": "\f76c", - "heart-arrow": "\f76d", - "heart-pulse-fill": "\f76e", - "heart-pulse": "\f76f", - "heartbreak-fill": "\f770", - "heartbreak": "\f771", - "hearts": "\f772", - "hospital-fill": "\f773", - "hospital": "\f774", - "house-heart-fill": "\f775", - "house-heart": "\f776", - "incognito": "\f777", - "magnet-fill": "\f778", - "magnet": "\f779", - "person-heart": "\f77a", - "person-hearts": "\f77b", - "phone-flip": "\f77c", - "plugin": "\f77d", - "postage-fill": "\f77e", - "postage-heart-fill": "\f77f", - "postage-heart": "\f780", - "postage": "\f781", - "postcard-fill": "\f782", - "postcard-heart-fill": "\f783", - "postcard-heart": "\f784", - "postcard": "\f785", - "search-heart-fill": "\f786", - "search-heart": "\f787", - "sliders2-vertical": "\f788", - "sliders2": "\f789", - "trash3-fill": "\f78a", - "trash3": "\f78b", - "valentine": "\f78c", - "valentine2": "\f78d", - "wrench-adjustable-circle-fill": "\f78e", - "wrench-adjustable-circle": "\f78f", - "wrench-adjustable": "\f790", - "filetype-json": "\f791", - "filetype-pptx": "\f792", - "filetype-xlsx": "\f793", - "1-circle-fill": "\f796", - "1-circle": "\f797", - "1-square-fill": "\f798", - "1-square": "\f799", - "2-circle-fill": "\f79c", - "2-circle": "\f79d", - "2-square-fill": "\f79e", - "2-square": "\f79f", - "3-circle-fill": "\f7a2", - "3-circle": "\f7a3", - "3-square-fill": "\f7a4", - "3-square": "\f7a5", - "4-circle-fill": "\f7a8", - "4-circle": "\f7a9", - "4-square-fill": "\f7aa", - "4-square": "\f7ab", - "5-circle-fill": "\f7ae", - "5-circle": "\f7af", - "5-square-fill": "\f7b0", - "5-square": "\f7b1", - "6-circle-fill": "\f7b4", - "6-circle": "\f7b5", - "6-square-fill": "\f7b6", - "6-square": "\f7b7", - "7-circle-fill": "\f7ba", - "7-circle": "\f7bb", - "7-square-fill": "\f7bc", - "7-square": "\f7bd", - "8-circle-fill": "\f7c0", - "8-circle": "\f7c1", - "8-square-fill": "\f7c2", - "8-square": "\f7c3", - "9-circle-fill": "\f7c6", - "9-circle": "\f7c7", - "9-square-fill": "\f7c8", - "9-square": "\f7c9", - "airplane-engines-fill": "\f7ca", - "airplane-engines": "\f7cb", - "airplane-fill": "\f7cc", - "airplane": "\f7cd", - "alexa": "\f7ce", - "alipay": "\f7cf", - "android": "\f7d0", - "android2": "\f7d1", - "box-fill": "\f7d2", - "box-seam-fill": "\f7d3", - "browser-chrome": "\f7d4", - "browser-edge": "\f7d5", - "browser-firefox": "\f7d6", - "browser-safari": "\f7d7", - "c-circle-fill": "\f7da", - "c-circle": "\f7db", - "c-square-fill": "\f7dc", - "c-square": "\f7dd", - "capsule-pill": "\f7de", - "capsule": "\f7df", - "car-front-fill": "\f7e0", - "car-front": "\f7e1", - "cassette-fill": "\f7e2", - "cassette": "\f7e3", - "cc-circle-fill": "\f7e6", - "cc-circle": "\f7e7", - "cc-square-fill": "\f7e8", - "cc-square": "\f7e9", - "cup-hot-fill": "\f7ea", - "cup-hot": "\f7eb", - "currency-rupee": "\f7ec", - "dropbox": "\f7ed", - "escape": "\f7ee", - "fast-forward-btn-fill": "\f7ef", - "fast-forward-btn": "\f7f0", - "fast-forward-circle-fill": "\f7f1", - "fast-forward-circle": "\f7f2", - "fast-forward-fill": "\f7f3", - "fast-forward": "\f7f4", - "filetype-sql": "\f7f5", - "fire": "\f7f6", - "google-play": "\f7f7", - "h-circle-fill": "\f7fa", - "h-circle": "\f7fb", - "h-square-fill": "\f7fc", - "h-square": "\f7fd", - "indent": "\f7fe", - "lungs-fill": "\f7ff", - "lungs": "\f800", - "microsoft-teams": "\f801", - "p-circle-fill": "\f804", - "p-circle": "\f805", - "p-square-fill": "\f806", - "p-square": "\f807", - "pass-fill": "\f808", - "pass": "\f809", - "prescription": "\f80a", - "prescription2": "\f80b", - "r-circle-fill": "\f80e", - "r-circle": "\f80f", - "r-square-fill": "\f810", - "r-square": "\f811", - "repeat-1": "\f812", - "repeat": "\f813", - "rewind-btn-fill": "\f814", - "rewind-btn": "\f815", - "rewind-circle-fill": "\f816", - "rewind-circle": "\f817", - "rewind-fill": "\f818", - "rewind": "\f819", - "train-freight-front-fill": "\f81a", - "train-freight-front": "\f81b", - "train-front-fill": "\f81c", - "train-front": "\f81d", - "train-lightrail-front-fill": "\f81e", - "train-lightrail-front": "\f81f", - "truck-front-fill": "\f820", - "truck-front": "\f821", - "ubuntu": "\f822", - "unindent": "\f823", - "unity": "\f824", - "universal-access-circle": "\f825", - "universal-access": "\f826", - "virus": "\f827", - "virus2": "\f828", - "wechat": "\f829", - "yelp": "\f82a", - "sign-stop-fill": "\f82b", - "sign-stop-lights-fill": "\f82c", - "sign-stop-lights": "\f82d", - "sign-stop": "\f82e", - "sign-turn-left-fill": "\f82f", - "sign-turn-left": "\f830", - "sign-turn-right-fill": "\f831", - "sign-turn-right": "\f832", - "sign-turn-slight-left-fill": "\f833", - "sign-turn-slight-left": "\f834", - "sign-turn-slight-right-fill": "\f835", - "sign-turn-slight-right": "\f836", - "sign-yield-fill": "\f837", - "sign-yield": "\f838", - "ev-station-fill": "\f839", - "ev-station": "\f83a", - "fuel-pump-diesel-fill": "\f83b", - "fuel-pump-diesel": "\f83c", - "fuel-pump-fill": "\f83d", - "fuel-pump": "\f83e", - "0-circle-fill": "\f83f", - "0-circle": "\f840", - "0-square-fill": "\f841", - "0-square": "\f842", - "rocket-fill": "\f843", - "rocket-takeoff-fill": "\f844", - "rocket-takeoff": "\f845", - "rocket": "\f846", - "stripe": "\f847", - "subscript": "\f848", - "superscript": "\f849", - "trello": "\f84a", - "envelope-at-fill": "\f84b", - "envelope-at": "\f84c", - "regex": "\f84d", - "text-wrap": "\f84e", - "sign-dead-end-fill": "\f84f", - "sign-dead-end": "\f850", - "sign-do-not-enter-fill": "\f851", - "sign-do-not-enter": "\f852", - "sign-intersection-fill": "\f853", - "sign-intersection-side-fill": "\f854", - "sign-intersection-side": "\f855", - "sign-intersection-t-fill": "\f856", - "sign-intersection-t": "\f857", - "sign-intersection-y-fill": "\f858", - "sign-intersection-y": "\f859", - "sign-intersection": "\f85a", - "sign-merge-left-fill": "\f85b", - "sign-merge-left": "\f85c", - "sign-merge-right-fill": "\f85d", - "sign-merge-right": "\f85e", - "sign-no-left-turn-fill": "\f85f", - "sign-no-left-turn": "\f860", - "sign-no-parking-fill": "\f861", - "sign-no-parking": "\f862", - "sign-no-right-turn-fill": "\f863", - "sign-no-right-turn": "\f864", - "sign-railroad-fill": "\f865", - "sign-railroad": "\f866", - "building-add": "\f867", - "building-check": "\f868", - "building-dash": "\f869", - "building-down": "\f86a", - "building-exclamation": "\f86b", - "building-fill-add": "\f86c", - "building-fill-check": "\f86d", - "building-fill-dash": "\f86e", - "building-fill-down": "\f86f", - "building-fill-exclamation": "\f870", - "building-fill-gear": "\f871", - "building-fill-lock": "\f872", - "building-fill-slash": "\f873", - "building-fill-up": "\f874", - "building-fill-x": "\f875", - "building-fill": "\f876", - "building-gear": "\f877", - "building-lock": "\f878", - "building-slash": "\f879", - "building-up": "\f87a", - "building-x": "\f87b", - "buildings-fill": "\f87c", - "buildings": "\f87d", - "bus-front-fill": "\f87e", - "bus-front": "\f87f", - "ev-front-fill": "\f880", - "ev-front": "\f881", - "globe-americas": "\f882", - "globe-asia-australia": "\f883", - "globe-central-south-asia": "\f884", - "globe-europe-africa": "\f885", - "house-add-fill": "\f886", - "house-add": "\f887", - "house-check-fill": "\f888", - "house-check": "\f889", - "house-dash-fill": "\f88a", - "house-dash": "\f88b", - "house-down-fill": "\f88c", - "house-down": "\f88d", - "house-exclamation-fill": "\f88e", - "house-exclamation": "\f88f", - "house-gear-fill": "\f890", - "house-gear": "\f891", - "house-lock-fill": "\f892", - "house-lock": "\f893", - "house-slash-fill": "\f894", - "house-slash": "\f895", - "house-up-fill": "\f896", - "house-up": "\f897", - "house-x-fill": "\f898", - "house-x": "\f899", - "person-add": "\f89a", - "person-down": "\f89b", - "person-exclamation": "\f89c", - "person-fill-add": "\f89d", - "person-fill-check": "\f89e", - "person-fill-dash": "\f89f", - "person-fill-down": "\f8a0", - "person-fill-exclamation": "\f8a1", - "person-fill-gear": "\f8a2", - "person-fill-lock": "\f8a3", - "person-fill-slash": "\f8a4", - "person-fill-up": "\f8a5", - "person-fill-x": "\f8a6", - "person-gear": "\f8a7", - "person-lock": "\f8a8", - "person-slash": "\f8a9", - "person-up": "\f8aa", - "scooter": "\f8ab", - "taxi-front-fill": "\f8ac", - "taxi-front": "\f8ad", - "amd": "\f8ae", - "database-add": "\f8af", - "database-check": "\f8b0", - "database-dash": "\f8b1", - "database-down": "\f8b2", - "database-exclamation": "\f8b3", - "database-fill-add": "\f8b4", - "database-fill-check": "\f8b5", - "database-fill-dash": "\f8b6", - "database-fill-down": "\f8b7", - "database-fill-exclamation": "\f8b8", - "database-fill-gear": "\f8b9", - "database-fill-lock": "\f8ba", - "database-fill-slash": "\f8bb", - "database-fill-up": "\f8bc", - "database-fill-x": "\f8bd", - "database-fill": "\f8be", - "database-gear": "\f8bf", - "database-lock": "\f8c0", - "database-slash": "\f8c1", - "database-up": "\f8c2", - "database-x": "\f8c3", - "database": "\f8c4", - "houses-fill": "\f8c5", - "houses": "\f8c6", - "nvidia": "\f8c7", - "person-vcard-fill": "\f8c8", - "person-vcard": "\f8c9", - "sina-weibo": "\f8ca", - "tencent-qq": "\f8cb", - "wikipedia": "\f8cc", - "alphabet-uppercase": "\f2a5", - "alphabet": "\f68a", - "amazon": "\f68d", - "arrows-collapse-vertical": "\f690", - "arrows-expand-vertical": "\f695", - "arrows-vertical": "\f698", - "arrows": "\f6a2", - "ban-fill": "\f6a3", - "ban": "\f6b6", - "bing": "\f6c2", - "cake": "\f6e0", - "cake2": "\f6ed", - "cookie": "\f6ee", - "copy": "\f759", - "crosshair": "\f769", - "crosshair2": "\f794", - "emoji-astonished-fill": "\f795", - "emoji-astonished": "\f79a", - "emoji-grimace-fill": "\f79b", - "emoji-grimace": "\f7a0", - "emoji-grin-fill": "\f7a1", - "emoji-grin": "\f7a6", - "emoji-surprise-fill": "\f7a7", - "emoji-surprise": "\f7ac", - "emoji-tear-fill": "\f7ad", - "emoji-tear": "\f7b2", - "envelope-arrow-down-fill": "\f7b3", - "envelope-arrow-down": "\f7b8", - "envelope-arrow-up-fill": "\f7b9", - "envelope-arrow-up": "\f7be", - "feather": "\f7bf", - "feather2": "\f7c4", - "floppy-fill": "\f7c5", - "floppy": "\f7d8", - "floppy2-fill": "\f7d9", - "floppy2": "\f7e4", - "gitlab": "\f7e5", - "highlighter": "\f7f8", - "marker-tip": "\f802", - "nvme-fill": "\f803", - "nvme": "\f80c", - "opencollective": "\f80d", - "pci-card-network": "\f8cd", - "pci-card-sound": "\f8ce", - "radar": "\f8cf", - "send-arrow-down-fill": "\f8d0", - "send-arrow-down": "\f8d1", - "send-arrow-up-fill": "\f8d2", - "send-arrow-up": "\f8d3", - "sim-slash-fill": "\f8d4", - "sim-slash": "\f8d5", - "sourceforge": "\f8d6", - "substack": "\f8d7", - "threads-fill": "\f8d8", - "threads": "\f8d9", - "transparency": "\f8da", - "twitter-x": "\f8db", - "type-h4": "\f8dc", - "type-h5": "\f8dd", - "type-h6": "\f8de", - "backpack-fill": "\f8df", - "backpack": "\f8e0", - "backpack2-fill": "\f8e1", - "backpack2": "\f8e2", - "backpack3-fill": "\f8e3", - "backpack3": "\f8e4", - "backpack4-fill": "\f8e5", - "backpack4": "\f8e6", - "brilliance": "\f8e7", - "cake-fill": "\f8e8", - "cake2-fill": "\f8e9", - "duffle-fill": "\f8ea", - "duffle": "\f8eb", - "exposure": "\f8ec", - "gender-neuter": "\f8ed", - "highlights": "\f8ee", - "luggage-fill": "\f8ef", - "luggage": "\f8f0", - "mailbox-flag": "\f8f1", - "mailbox2-flag": "\f8f2", - "noise-reduction": "\f8f3", - "passport-fill": "\f8f4", - "passport": "\f8f5", - "person-arms-up": "\f8f6", - "person-raised-hand": "\f8f7", - "person-standing-dress": "\f8f8", - "person-standing": "\f8f9", - "person-walking": "\f8fa", - "person-wheelchair": "\f8fb", - "shadows": "\f8fc", - "suitcase-fill": "\f8fd", - "suitcase-lg-fill": "\f8fe", - "suitcase-lg": "\f8ff", - "suitcase": "\f900", - "suitcase2-fill": "\f901", - "suitcase2": "\f902", - "vignette": "\f903", - "bluesky": "\f7f9", - "tux": "\f904", - "beaker-fill": "\f905", - "beaker": "\f906", - "flask-fill": "\f907", - "flask-florence-fill": "\f908", - "flask-florence": "\f909", - "flask": "\f90a", - "leaf-fill": "\f90b", - "leaf": "\f90c", - "measuring-cup-fill": "\f90d", - "measuring-cup": "\f90e", - "unlock2-fill": "\f90f", - "unlock2": "\f910", - "battery-low": "\f911", - "anthropic": "\f912", - "apple-music": "\f913", - "claude": "\f914", - "openai": "\f915", - "perplexity": "\f916", - "css": "\f917", - "javascript": "\f918", - "typescript": "\f919", - "fork-knife": "\f91a", - "globe-americas-fill": "\f91b", - "globe-asia-australia-fill": "\f91c", - "globe-central-south-asia-fill": "\f91d", - "globe-europe-africa-fill": "\f91e", -); - -@each $icon, $codepoint in $bootstrap-icons-map { - .bi-#{$icon}::before { content: $codepoint; } -} diff --git a/extensions/pagetop-bootsier/assets/bootstrap-icons-1.13.1/fonts/bootstrap-icons.woff b/extensions/pagetop-bootsier/assets/bootstrap-icons-1.13.1/fonts/bootstrap-icons.woff deleted file mode 100644 index a4fa4f02..00000000 Binary files a/extensions/pagetop-bootsier/assets/bootstrap-icons-1.13.1/fonts/bootstrap-icons.woff and /dev/null differ diff --git a/extensions/pagetop-bootsier/assets/bootstrap-icons-1.13.1/fonts/bootstrap-icons.woff2 b/extensions/pagetop-bootsier/assets/bootstrap-icons-1.13.1/fonts/bootstrap-icons.woff2 deleted file mode 100644 index 4d8c490e..00000000 Binary files a/extensions/pagetop-bootsier/assets/bootstrap-icons-1.13.1/fonts/bootstrap-icons.woff2 and /dev/null differ diff --git a/extensions/pagetop-bootsier/build.rs b/extensions/pagetop-bootsier/build.rs index 8de7c387..df7a2750 100644 --- a/extensions/pagetop-bootsier/build.rs +++ b/extensions/pagetop-bootsier/build.rs @@ -1,85 +1,20 @@ -//! Script de compilacion de activos estaticos. -//! -//! Genera el directorio `static/` a partir de `assets/` y embebe su contenido en el binario: -//! -//! - `static/css/` - CSS compilado a partir de los archivos SCSS de `assets/`. -//! - `static/js/` - JS copiado desde `assets/`, renombrando AdminLTE a `bootsier.min.js`. -//! - `static/fonts/` - Fuentes copiadas desde `assets/`. -//! -//! Los archivos `.map` se copian a `static/js/` para uso en desarrollo pero no se incluyen en el -//! binario embebido. - -use pagetop_build::{StaticFilesBundle, compile_scss, copy_file, copy_file_replacing, minify_js}; +use pagetop_build::StaticFilesBundle; +use std::env; use std::path::Path; fn main() -> std::io::Result<()> { - // Regenera `static/` desde cero sólo si hay cambios en `assets/`. - println!("cargo:rerun-if-changed=assets"); - let _ = std::fs::remove_dir_all("static"); - - // CSS: Bootstrap 5.3.8 + AdminLTE 4.0.0 + Bootstrap Icons 1.13.1. - compile_scss("assets/bootsier.scss", "static/css/bootsier.min.css")?; - - // JS: Bootstrap bundle. - copy_file( - "assets/bootstrap-5.3.8/js/bootstrap.bundle.min.js", - "static/js/bootsier.bundle.min.js", - )?; - copy_file( - "assets/bootstrap-5.3.8/js/bootstrap.bundle.min.js.map", - "static/js/bootsier.bundle.min.js.map", - )?; - // JS: AdminLTE renombrado a bootsier.extended.min.js. - copy_file_replacing( - "assets/adminlte-4.0.0/js/adminlte.min.js", - "static/js/bootsier.extended.min.js", - &[("adminlte.min.js.map", "bootsier.extended.min.js.map")], - )?; - copy_file( - "assets/adminlte-4.0.0/js/adminlte.min.js.map", - "static/js/bootsier.extended.min.js.map", - )?; - // JS: shell de Bootsier. - minify_js( - "assets/bootsier.shell.js", - "static/js/bootsier.shell.min.js", - )?; - - // Fuentes: Bootstrap Icons. - copy_file( - "assets/bootstrap-icons-1.13.1/fonts/bootstrap-icons.woff2", - "static/fonts/bootsier.icons.woff2", - )?; - copy_file( - "assets/bootstrap-icons-1.13.1/fonts/bootstrap-icons.woff", - "static/fonts/bootsier.icons.woff", - )?; - // Fuentes: Source Sans 3 (SIL OFL 1.1). - copy_file( - "assets/adminlte-4.0.0/fonts/SourceSans3VF-Upright.otf.woff2", - "static/fonts/bootsier.font.woff2", - )?; - copy_file( - "assets/adminlte-4.0.0/fonts/SourceSans3VF-Italic.otf.woff2", - "static/fonts/bootsier.font.italic.woff2", - )?; - - // Preparación de los paquetes para embeber en el binario. - StaticFilesBundle::from_dir("./static/css", None) - .with_name("bootsier_css") + StaticFilesBundle::from_scss("./static/scss/bootsier.scss", "bootstrap.min.css") + .with_name("bootsier_bs") .build()?; - - StaticFilesBundle::from_dir("./static/js", Some(only_js_files)) + StaticFilesBundle::from_dir("./static/js", Some(bootstrap_js_files)) .with_name("bootsier_js") - .build()?; - - StaticFilesBundle::from_dir("./static/fonts", None) - .with_name("bootsier_fonts") .build() } -// Los archivos .map no se embeben en el binario; solo se sirven desde disco en desarrollo. -fn only_js_files(path: &Path) -> bool { - path.extension().map_or(false, |ext| ext == "js") +fn bootstrap_js_files(path: &Path) -> bool { + let bootstrap_js = "bootstrap.bundle.min.js"; + // No filtra durante el desarrollo, solo en la compilación "release". + env::var("PROFILE").unwrap_or_else(|_| "release".to_string()) != "release" + || path.file_name().is_some_and(|f| f == bootstrap_js) } diff --git a/extensions/pagetop-bootsier/src/config.rs b/extensions/pagetop-bootsier/src/config.rs index dede3c4c..14ee27e1 100644 --- a/extensions/pagetop-bootsier/src/config.rs +++ b/extensions/pagetop-bootsier/src/config.rs @@ -9,7 +9,7 @@ //! //! Uso: //! -//! ```rust,no_run +//! ```rust //! # use pagetop::prelude::*; //! use pagetop_bootsier::config; //! @@ -26,15 +26,12 @@ use serde::Deserialize; include_config!(SETTINGS: Settings => [ // [bootsier] "bootsier.max_width" => "1440px", - // [dev] - "dev.bootsier_static_dir" => "", ]); /// Ajustes para la sección [`Bootsier`] de [`SETTINGS`]. #[derive(Debug, Deserialize)] pub struct Settings { pub bootsier: Bootsier, - pub dev: Dev, } /// Sección **`[bootsier]`** de la configuración. Forma parte de [`Settings`]. @@ -43,16 +40,3 @@ pub struct Bootsier { /// Ancho máximo predeterminado para la página, por ejemplo "100%" o "90rem". pub max_width: UnitValue, } - -/// Sección **`[dev]`** de la configuración. Forma parte de [`Settings`]. -#[derive(Debug, Deserialize)] -pub struct Dev { - /// Directorio raíz de `static/` para servir los archivos estáticos propios de Bootsier. - /// - /// Si se indica una ruta válida, absoluta o relativa al directorio del proyecto o del binario - /// en ejecución, los archivos estáticos se servirán desde disco. Útil para poder modificar los - /// archivos estáticos mientras la aplicación está en ejecución, sin necesidad de recompilar. - /// - /// Si la cadena está vacía, se ignora este ajuste. - pub bootsier_static_dir: String, -} diff --git a/extensions/pagetop-bootsier/src/handlers/button.rs b/extensions/pagetop-bootsier/src/handlers/button.rs deleted file mode 100644 index 5846283d..00000000 --- a/extensions/pagetop-bootsier/src/handlers/button.rs +++ /dev/null @@ -1,7 +0,0 @@ -use pagetop::prelude::*; - -pub fn setup(button: &mut Button) { - button - .alter_prop(PropsOp::remove_classes("button")) - .alter_prop(PropsOp::prepend_classes("btn")); -} diff --git a/extensions/pagetop-bootsier/src/handlers/input.rs b/extensions/pagetop-bootsier/src/handlers/input.rs deleted file mode 100644 index 94f906c4..00000000 --- a/extensions/pagetop-bootsier/src/handlers/input.rs +++ /dev/null @@ -1,59 +0,0 @@ -use pagetop::prelude::*; - -pub fn render(c: &form::input::Field, cx: &mut Context) -> Result { - let container_id = c.id(); - let input_id = container_id.as_deref().map(|id| util::join!(id, "-input")); - let floating = c.props().has_class("form-floating"); - let input_class = if *c.plaintext() { - "form-control-plaintext" - } else { - "form-control" - }; - // La etiqueta flotante requiere `placeholder` para animar la etiqueta; si no está definido, se - // fuerza `placeholder=""`. - let placeholder = if floating { - Some(c.placeholder().lookup(cx).unwrap_or_default()) - } else { - c.placeholder().lookup(cx) - }; - let label = match c.label().lookup(cx) { - Some(text) => html! { - label for=[input_id.as_deref()] class="form-label" { - (text) - @if *c.required() { - span - class="form-required" - title=(L10n::l("field_required").using(cx)) - { - "*" - } - } - } - }, - None => html! {}, - }; - Ok(html! { - div (c.props()) { - @if !floating { (label) } - input - type=(c.kind()) - id=[input_id.as_deref()] - class=(input_class) - name=[c.name().get()] - value=[c.value().get()] - minlength=[c.minlength().get()] - maxlength=[c.maxlength().get()] - placeholder=[placeholder] - inputmode=[c.inputmode().get()] - autocomplete=[c.autocomplete().get()] - autofocus[*c.autofocus()] - readonly[*c.readonly() || *c.plaintext()] - required[*c.required()] - disabled[*c.disabled()]; - @if floating { (label) } - @if let Some(description) = c.help_text().lookup(cx) { - div class="form-text" { (description) } - } - } - }) -} diff --git a/extensions/pagetop-bootsier/src/handlers/select.rs b/extensions/pagetop-bootsier/src/handlers/select.rs deleted file mode 100644 index 03a48735..00000000 --- a/extensions/pagetop-bootsier/src/handlers/select.rs +++ /dev/null @@ -1,80 +0,0 @@ -use pagetop::prelude::*; - -pub fn setup(c: &mut form::select::Field) { - if c.props().has_class("form-floating") { - c.alter_multiple(false); - c.alter_rows(None::); - } -} - -pub fn render(c: &form::select::Field, cx: &mut Context) -> Result { - let container_id = c.id(); - let select_id = container_id.as_deref().map(|id| util::join!(id, "-select")); - let floating = c.props().has_class("form-floating"); - let label = match c.label().lookup(cx) { - Some(text) => html! { - label for=[select_id.as_deref()] class="form-label" { - (text) - @if *c.required() { - span - class="form-required" - title=(L10n::l("field_required").using(cx)) - { - "*" - } - } - } - }, - None => html! {}, - }; - Ok(html! { - div (c.props()) { - @if !floating { (label) } - select - id=[select_id.as_deref()] - class="form-select" - name=[c.name().get()] - multiple[*c.multiple()] - size=[c.rows().get()] - autocomplete=[c.autocomplete().get()] - autofocus[*c.autofocus()] - required[*c.required()] - disabled[*c.disabled()] - { - @for entry in c.entries() { - @match entry { - form::select::Entry::Item(opt) => { - option - value=(opt.value().as_str().unwrap_or("")) - selected[*opt.selected()] - disabled[*opt.disabled()] - { - (opt.label().using(cx)) - } - } - form::select::Entry::Group(group) => { - optgroup - label=(group.label().using(cx)) - disabled[*group.disabled()] - { - @for opt in group.items() { - option - value=(opt.value().as_str().unwrap_or("")) - selected[*opt.selected()] - disabled[*opt.disabled()] - { - (opt.label().using(cx)) - } - } - } - } - } - } - } - @if floating { (label) } - @if let Some(description) = c.help_text().lookup(cx) { - div class="form-text" { (description) } - } - } - }) -} diff --git a/extensions/pagetop-bootsier/src/handlers/textarea.rs b/extensions/pagetop-bootsier/src/handlers/textarea.rs deleted file mode 100644 index 2f41a765..00000000 --- a/extensions/pagetop-bootsier/src/handlers/textarea.rs +++ /dev/null @@ -1,63 +0,0 @@ -use pagetop::prelude::*; - -pub fn setup(c: &mut form::Textarea) { - if c.props().has_class("form-floating") { - c.alter_rows(None::); - } -} - -pub fn render(c: &form::Textarea, cx: &mut Context) -> Result { - let container_id = c.id(); - let textarea_id = container_id - .as_deref() - .map(|id| util::join!(id, "-textarea")); - let floating = c.props().has_class("form-floating"); - // La etiqueta flotante requiere `placeholder` para animar la etiqueta; si no está definido se - // fuerza `placeholder=""`. - let placeholder = if floating { - Some(c.placeholder().lookup(cx).unwrap_or_default()) - } else { - c.placeholder().lookup(cx) - }; - let label = match c.label().lookup(cx) { - Some(text) => html! { - label for=[textarea_id.as_deref()] class="form-label" { - (text) - @if *c.required() { - span - class="form-required" - title=(L10n::l("field_required").using(cx)) - { - "*" - } - } - } - }, - None => html! {}, - }; - Ok(html! { - div (c.props()) { - @if !floating { (label) } - textarea - id=[textarea_id.as_deref()] - class="form-control" - name=[c.name().get()] - rows=[c.rows().get()] - minlength=[c.minlength().get()] - maxlength=[c.maxlength().get()] - placeholder=[placeholder] - autocomplete=[c.autocomplete().get()] - autofocus[*c.autofocus()] - readonly[*c.readonly()] - required[*c.required()] - disabled[*c.disabled()] - { - @if let Some(value) = c.value().get() { (value) } - } - @if floating { (label) } - @if let Some(description) = c.help_text().lookup(cx) { - div class="form-text" { (description) } - } - } - }) -} diff --git a/extensions/pagetop-bootsier/src/lib.rs b/extensions/pagetop-bootsier/src/lib.rs index 23e07722..8c0ec847 100644 --- a/extensions/pagetop-bootsier/src/lib.rs +++ b/extensions/pagetop-bootsier/src/lib.rs @@ -10,6 +10,7 @@ [![Descargas](https://img.shields.io/crates/d/pagetop-bootsier.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-bootsier) [![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/extensions/pagetop-bootsier#licencia) +
## Sobre PageTop @@ -18,7 +19,8 @@ clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y configurables, basadas en HTML, CSS y JavaScript. -## Guía rápida + +# ⚡️ Guía rápida Igual que con otras extensiones, **añade la dependencia** a tu `Cargo.toml`: @@ -44,6 +46,11 @@ impl Extension for MyApp { ] } } + +#[pagetop::main] +async fn main() -> std::io::Result<()> { + Application::prepare(&MyApp).run()?.await +} ``` Y **selecciona el tema en la configuración** de la aplicación: @@ -89,11 +96,32 @@ pub mod config; pub mod theme; -mod handlers { - pub mod button; - pub mod input; - pub mod select; - pub mod textarea; +/// Plantillas que Bootsier añade. +#[derive(AutoDefault)] +pub enum BootsierTemplate { + /// Plantilla predeterminada de Bootsier. + #[default] + Standard, +} + +impl Template for BootsierTemplate { + fn render(&'static self, cx: &mut Context) -> Markup { + match self { + Self::Standard => theme::Container::new() + .with_classes(ClassesOp::Add, "container-wrapper") + .with_width(theme::container::Width::FluidMax( + config::SETTINGS.bootsier.max_width, + )) + .with_child(Html::with(|cx| { + html! { + (DefaultRegion::Header.render(cx)) + (DefaultRegion::Content.render(cx)) + (DefaultRegion::Footer.render(cx)) + } + })), + } + .render(cx) + } } /// Implementa el tema. @@ -113,17 +141,8 @@ impl Extension for Bootsier { } fn configure_router(&self, router: Router) -> Router { - let base = &config::SETTINGS.dev.bootsier_static_dir; - let subdir = |s: &str| { - if base.is_empty() { - String::new() - } else { - format!("{base}/{s}") - } - }; - serve_static_files!(router, [subdir("css"), bootsier_css] => "/bootsier/css"); - serve_static_files!(router, [subdir("js"), bootsier_js] => "/bootsier/js"); - serve_static_files!(router, [subdir("fonts"), bootsier_fonts] => "/bootsier/fonts"); + serve_static_files!(router, [bootsier_bs] => "/bootsier/bs"); + serve_static_files!(router, [bootsier_js] => "/bootsier/js"); router } } @@ -134,38 +153,16 @@ impl Theme for Bootsier { &BootsierTemplate::Standard } - fn handle_component( - &self, - component: &mut dyn Component, - cx: &mut Context, - ) -> Option> { - setup_component!(component, { - Button => |c| handlers::button::setup(c), - form::select::Field => |c| handlers::select::setup(c), - form::Textarea => |c| handlers::textarea::setup(c), - }); - render_component!(component, { - form::input::Field => |c| handlers::input::render(c, cx), - form::select::Field => |c| handlers::select::render(c, cx), - form::Textarea => |c| handlers::textarea::render(c, cx), - }) - } - fn before_render_page_body(&self, page: &mut Page) { page.alter_assets(AssetsOp::AddStyleSheet( - StyleSheet::from("/bootsier/css/bootsier.min.css") - .with_version(ADMINLTE_VERSION) - .with_weight(-90), - )) - .alter_assets(AssetsOp::AddJavaScript( - JavaScript::defer("/bootsier/js/bootsier.bundle.min.js") + StyleSheet::from("/bootsier/bs/bootstrap.min.css") .with_version(BOOTSTRAP_VERSION) .with_weight(-90), )) .alter_assets(AssetsOp::AddJavaScript( - JavaScript::defer("/bootsier/js/bootsier.extended.min.js") - .with_version(ADMINLTE_VERSION) - .with_weight(-89), + JavaScript::defer("/bootsier/js/bootstrap.bundle.min.js") + .with_version(BOOTSTRAP_VERSION) + .with_weight(-90), )) .alter_child_in( &DefaultRegion::Footer, diff --git a/extensions/pagetop-bootsier/src/theme.rs b/extensions/pagetop-bootsier/src/theme.rs index 22e55792..fb7dd0ed 100644 --- a/extensions/pagetop-bootsier/src/theme.rs +++ b/extensions/pagetop-bootsier/src/theme.rs @@ -11,7 +11,7 @@ pub mod classes; // Button. mod button; -pub use button::{Button, ButtonAction}; +pub use button::Button; // Container. pub mod container; @@ -27,12 +27,6 @@ pub use dropdown::Dropdown; pub mod form; #[doc(inline)] pub use form::Form; -#[doc(hidden)] -pub use form::input::InputBootsier; -#[doc(hidden)] -pub use form::select::SelectBootsier; -#[doc(hidden)] -pub use form::textarea::TextareaBootsier; // Image. pub mod image; diff --git a/extensions/pagetop-bootsier/src/theme/attrs.rs b/extensions/pagetop-bootsier/src/theme/attrs.rs index 3b3be43a..e5f0a82f 100644 --- a/extensions/pagetop-bootsier/src/theme/attrs.rs +++ b/extensions/pagetop-bootsier/src/theme/attrs.rs @@ -15,3 +15,6 @@ pub use border::BorderColor; mod rounded; pub use rounded::RoundedRadius; + +mod button; +pub use button::{ButtonAction, ButtonColor, ButtonSize}; diff --git a/extensions/pagetop-bootsier/src/theme/attrs/border.rs b/extensions/pagetop-bootsier/src/theme/attrs/border.rs index f83bb13a..af66db78 100644 --- a/extensions/pagetop-bootsier/src/theme/attrs/border.rs +++ b/extensions/pagetop-bootsier/src/theme/attrs/border.rs @@ -57,7 +57,7 @@ impl BorderColor { /// /// # Ejemplos /// - /// ```rust,no_run + /// ```rust /// # use pagetop_bootsier::theme::*; /// assert_eq!(BorderColor::Theme(Color::Primary).to_class(), "border-primary"); /// assert_eq!(BorderColor::Subtle(Color::Warning).to_class(), "border-warning-subtle"); diff --git a/extensions/pagetop-bootsier/src/theme/attrs/breakpoint.rs b/extensions/pagetop-bootsier/src/theme/attrs/breakpoint.rs index 5ee64ad5..ec2c8568 100644 --- a/extensions/pagetop-bootsier/src/theme/attrs/breakpoint.rs +++ b/extensions/pagetop-bootsier/src/theme/attrs/breakpoint.rs @@ -69,7 +69,7 @@ impl BreakPoint { /// /// # Ejemplos /// - /// ```rust,no_run + /// ```rust /// # use pagetop_bootsier::theme::*; /// let bp = BreakPoint::MD; /// assert_eq!(bp.class_with("col", ""), "col-md"); diff --git a/extensions/pagetop-bootsier/src/theme/attrs/button.rs b/extensions/pagetop-bootsier/src/theme/attrs/button.rs new file mode 100644 index 00000000..01e0dd57 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/attrs/button.rs @@ -0,0 +1,145 @@ +use pagetop::prelude::*; + +use crate::theme::attrs::Color; + +// **< ButtonAction >********************************************************************************* + +/// Comportamiento de un [`Button`](crate::theme::Button) al activarse. +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] +pub enum ButtonAction { + /// Envía un formulario al servidor. Es el **tipo por defecto**. + #[default] + Submit, + /// Restablece todos los campos de un formulario a sus valores iniciales. + Reset, + /// Botón de propósito general, sin efecto predeterminado. Su comportamiento podría definirse + /// mediante JavaScript. + Plain, +} + +impl std::fmt::Display for ButtonAction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + ButtonAction::Submit => "submit", + ButtonAction::Reset => "reset", + ButtonAction::Plain => "button", + }) + } +} + +// **< ButtonColor >******************************************************************************** + +/// Esquema de color para [`Button`](crate::theme::Button). +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] +pub enum ButtonColor { + /// No define ninguna clase. + #[default] + Default, + /// Genera la clase `btn-{color}` (botón sólido). + Background(Color), + /// Genera la clase `btn-outline-{color}` (fondo transparente con contorno coloreado). + Outline(Color), + /// Aplica estilo de los enlaces (`btn-link`), sin caja ni fondo, heredando el color de texto. + Link, +} + +impl ButtonColor { + const BTN_PREFIX: &str = "btn-"; + const BTN_OUTLINE_PREFIX: &str = "btn-outline-"; + const BTN_LINK: &str = "btn-link"; + + /// Añade la clase `btn-*` a la cadena de clases. + #[inline] + pub(crate) fn push_class(self, classes: &mut String) { + if let Self::Default = self { + return; + } + if !classes.is_empty() { + classes.push(' '); + } + match self { + Self::Background(c) => { + classes.push_str(Self::BTN_PREFIX); + classes.push_str(c.as_str()); + } + Self::Outline(c) => { + classes.push_str(Self::BTN_OUTLINE_PREFIX); + classes.push_str(c.as_str()); + } + Self::Link => classes.push_str(Self::BTN_LINK), + Self::Default => unreachable!(), + } + } + + /// Devuelve la clase `btn-*` correspondiente al color del botón. + /// + /// # Ejemplos + /// + /// ```rust + /// # use pagetop_bootsier::theme::*; + /// assert_eq!( + /// ButtonColor::Background(Color::Primary).to_class(), + /// "btn-primary" + /// ); + /// assert_eq!( + /// ButtonColor::Outline(Color::Danger).to_class(), + /// "btn-outline-danger" + /// ); + /// assert_eq!(ButtonColor::Link.to_class(), "btn-link"); + /// assert_eq!(ButtonColor::Default.to_class(), ""); + /// ``` + pub fn to_class(self) -> String { + let mut class = String::new(); + self.push_class(&mut class); + class + } +} + +// **< ButtonSize >********************************************************************************* + +/// Tamaño visual de un botón. +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] +pub enum ButtonSize { + /// Tamaño por defecto del tema (no añade clase). + #[default] + Default, + /// Botón compacto. + Small, + /// Botón grande. + Large, +} + +impl ButtonSize { + const BTN_SM: &str = "btn-sm"; + const BTN_LG: &str = "btn-lg"; + + /// Añade la clase de tamaño `btn-sm` o `btn-lg` a la cadena de clases. + #[inline] + pub(crate) fn push_class(self, classes: &mut String) { + let class = match self { + Self::Default => return, + Self::Small => Self::BTN_SM, + Self::Large => Self::BTN_LG, + }; + if !classes.is_empty() { + classes.push(' '); + } + classes.push_str(class); + } + + /// Devuelve la clase `btn-sm` o `btn-lg` correspondiente al tamaño del botón. + /// + /// # Ejemplos + /// + /// ```rust + /// # use pagetop_bootsier::theme::*; + /// assert_eq!(ButtonSize::Small.to_class(), "btn-sm"); + /// assert_eq!(ButtonSize::Large.to_class(), "btn-lg"); + /// assert_eq!(ButtonSize::Default.to_class(), ""); + /// ``` + pub fn to_class(self) -> String { + let mut class = String::new(); + self.push_class(&mut class); + class + } +} diff --git a/extensions/pagetop-bootsier/src/theme/attrs/color.rs b/extensions/pagetop-bootsier/src/theme/attrs/color.rs index de254643..eb9f57d4 100644 --- a/extensions/pagetop-bootsier/src/theme/attrs/color.rs +++ b/extensions/pagetop-bootsier/src/theme/attrs/color.rs @@ -43,7 +43,7 @@ impl Color { /// /// # Ejemplos /// - /// ```rust,no_run + /// ```rust /// # use pagetop_bootsier::theme::*; /// assert_eq!(Color::Primary.to_class(), "primary"); /// assert_eq!(Color::Danger.to_class(), "danger"); @@ -123,7 +123,7 @@ impl Opacity { /// /// # Ejemplos /// - /// ```rust,no_run + /// ```rust /// # use pagetop_bootsier::theme::*; /// assert_eq!(Opacity::Opaque.class_with(""), "opacity-100"); /// assert_eq!(Opacity::Half.class_with("bg"), "bg-opacity-50"); @@ -155,7 +155,7 @@ impl Opacity { /// /// # Ejemplos /// - /// ```rust,no_run + /// ```rust /// # use pagetop_bootsier::theme::*; /// assert_eq!(Opacity::Opaque.to_class(), "opacity-100"); /// assert_eq!(Opacity::Half.to_class(), "opacity-50"); @@ -236,7 +236,7 @@ impl ColorBg { /// /// # Ejemplos /// - /// ```rust,no_run + /// ```rust /// # use pagetop_bootsier::theme::*; /// assert_eq!(ColorBg::Body.to_class(), "bg-body"); /// assert_eq!(ColorBg::Theme(Color::Primary).to_class(), "bg-primary"); @@ -320,7 +320,7 @@ impl ColorText { /// /// # Ejemplos /// - /// ```rust,no_run + /// ```rust /// # use pagetop_bootsier::theme::*; /// assert_eq!(ColorText::Body.to_class(), "text-body"); /// assert_eq!(ColorText::Theme(Color::Primary).to_class(), "text-primary"); diff --git a/extensions/pagetop-bootsier/src/theme/attrs/layout.rs b/extensions/pagetop-bootsier/src/theme/attrs/layout.rs index 54e3f516..81b07834 100644 --- a/extensions/pagetop-bootsier/src/theme/attrs/layout.rs +++ b/extensions/pagetop-bootsier/src/theme/attrs/layout.rs @@ -60,7 +60,7 @@ impl ScaleSize { /// /// # Ejemplo /// - /// ```rust,no_run + /// ```rust /// # use pagetop_bootsier::theme::*; /// assert_eq!(ScaleSize::Auto.class_with("border"), "border"); /// assert_eq!(ScaleSize::Zero.class_with("m"), "m-0"); diff --git a/extensions/pagetop-bootsier/src/theme/attrs/rounded.rs b/extensions/pagetop-bootsier/src/theme/attrs/rounded.rs index 0f9536a8..2a959767 100644 --- a/extensions/pagetop-bootsier/src/theme/attrs/rounded.rs +++ b/extensions/pagetop-bootsier/src/theme/attrs/rounded.rs @@ -70,7 +70,7 @@ impl RoundedRadius { /// /// # Ejemplos /// - /// ```rust,no_run + /// ```rust /// # use pagetop_bootsier::theme::*; /// assert_eq!(RoundedRadius::Scale2.class_with(""), "rounded-2"); /// assert_eq!(RoundedRadius::Zero.class_with("rounded-top"), "rounded-top-0"); @@ -102,7 +102,7 @@ impl RoundedRadius { /// /// # Ejemplos /// - /// ```rust,no_run + /// ```rust /// # use pagetop_bootsier::theme::*; /// assert_eq!(RoundedRadius::Default.to_class(), "rounded"); /// assert_eq!(RoundedRadius::Zero.to_class(), "rounded-0"); diff --git a/extensions/pagetop-bootsier/src/theme/button.rs b/extensions/pagetop-bootsier/src/theme/button.rs index eb705ce3..e494c1df 100644 --- a/extensions/pagetop-bootsier/src/theme/button.rs +++ b/extensions/pagetop-bootsier/src/theme/button.rs @@ -1 +1,214 @@ -pub use pagetop::base::component::{Button, ButtonAction}; +use pagetop::prelude::*; + +use crate::theme::{ButtonAction, ButtonColor, ButtonSize}; + +/// Componente para crear un **botón**. +/// +/// Renderiza un botón con soporte para las variantes disponibles en [`ButtonAction`] (`submit`, +/// `reset` y botón genérico) y con la variedad de estilos del tema a través de [`ButtonColor`] y +/// [`ButtonSize`]. +/// +/// El comportamiento del botón se establece al crearlo: +/// +/// - [`Button::submit()`]: botón de envío (por defecto). +/// - [`Button::reset()`]: botón de restablecimiento de valores. +/// - [`Button::plain()`]: botón genérico sin comportamiento predeterminado. +/// +/// El botón puede usarse dentro o fuera de un formulario. +/// +/// # Ejemplo +/// +/// ```rust +/// use pagetop::prelude::*; +/// use pagetop_bootsier::theme::*; +/// +/// let save = Button::submit(L10n::n("Save")) +/// .with_color(ButtonColor::Background(Color::Primary)); +/// +/// let cancel = Button::plain(L10n::n("Cancel")) +/// .with_color(ButtonColor::Outline(Color::Secondary)); +/// +/// let clear = Button::reset(L10n::n("Clear")) +/// .with_size(ButtonSize::Small); +/// ``` +/// +/// Cuando el botón activa el envío, el navegador incluye el par `name=value` en los datos del +/// formulario **sólo si** tiene el atributo `name` definido. Es la forma habitual de identificar +/// cuál de los botones de envío fue pulsado. En el servidor se deserializa como `Option`: +/// +/// ```rust,ignore +/// #[derive(serde::Deserialize)] +/// struct FormData { +/// #[serde(default)] +/// action: Option, // p. ej., "save" o "delete"; `None` si el botón no tenía `name`. +/// } +/// ``` +#[derive(AutoDefault, Clone, Debug, Getters)] +pub struct Button { + #[getters(skip)] + id: AttrId, + /// Devuelve las clases CSS del botón. + classes: Classes, + /// Devuelve el comportamiento del botón al activarse. + kind: ButtonAction, + /// Devuelve el esquema de color del botón. + color: ButtonColor, + /// Devuelve el tamaño visual del botón. + size: ButtonSize, + /// Devuelve el nombre del botón. + name: AttrName, + /// Devuelve el valor del botón. + value: AttrValue, + /// Devuelve la etiqueta del botón. + label: Attr, + /// Devuelve si el botón recibe el foco automáticamente al cargar la página. + autofocus: bool, + /// Devuelve si el botón está deshabilitado. + disabled: bool, +} + +impl Component for Button { + fn new() -> Self { + Self::default() + } + + fn id(&self) -> Option { + self.id.get() + } + + fn setup(&mut self, _cx: &Context) { + let mut classes = "btn".to_string(); + (*self.color()).push_class(&mut classes); + (*self.size()).push_class(&mut classes); + self.alter_classes(ClassesOp::Prepend, classes); + } + + fn prepare(&self, cx: &mut Context) -> Result { + Ok(html! { + button + id=[self.id()] + type=(self.kind()) + class=[self.classes().get()] + name=[self.name().get()] + value=[self.value().get()] + autofocus[*self.autofocus()] + disabled[*self.disabled()] + { + @if let Some(label) = self.label().lookup(cx) { + (label) + } + } + }) + } +} + +impl Button { + /// Crea un botón de **envío** (`type="submit"`). + /// + /// Es la acción predeterminada al pulsar un botón en la mayoría de los formularios: envía los + /// datos al servidor. + pub fn submit(label: L10n) -> Self { + Self { + kind: ButtonAction::Submit, + label: Attr::some(label), + ..Default::default() + } + } + + /// Crea un botón de **restablecimiento** (`type="reset"`). + /// + /// Al pulsarlo, devuelve todos los campos del formulario a sus valores iniciales. + pub fn reset(label: L10n) -> Self { + Self { + kind: ButtonAction::Reset, + label: Attr::some(label), + ..Default::default() + } + } + + /// Crea un **botón genérico** (`type="button"`). + /// + /// No tiene un comportamiento predeterminado sobre el formulario. Su comportamiento puede + /// definirse mediante JavaScript. + pub fn plain(label: L10n) -> Self { + Self { + kind: ButtonAction::Plain, + label: Attr::some(label), + ..Default::default() + } + } + + // **< Button BUILDER >************************************************************************* + + /// Establece el identificador único (`id`) del botón. + #[builder_fn] + pub fn with_id(mut self, id: impl AsRef) -> Self { + self.id.alter_id(id); + self + } + + /// Modifica la lista de clases CSS aplicadas al botón. + #[builder_fn] + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef) -> Self { + self.classes.alter_classes(op, classes); + self + } + + /// Establece el esquema de color del botón. + /// + /// Usa [`ButtonColor::Background`] para botones sólidos o [`ButtonColor::Outline`] para + /// variantes con contorno. + #[builder_fn] + pub fn with_color(mut self, color: ButtonColor) -> Self { + self.color = color; + self + } + + /// Establece el tamaño visual del botón. + #[builder_fn] + pub fn with_size(mut self, size: ButtonSize) -> Self { + self.size = size; + self + } + + /// Establece el nombre del botón (atributo `name`). + /// + /// Cuando el formulario tiene varios botones de envío, el navegador incluye en el envío el par + /// `name=value` sólo del botón que activó el formulario. Permite identificar cuál fue pulsado. + #[builder_fn] + pub fn with_name(mut self, name: impl AsRef) -> Self { + self.name.alter_name(name); + self + } + + /// Establece el valor del botón (atributo `value`). + /// + /// Es el dato que el navegador transmite al servidor junto con el `name` cuando este botón + /// activa el envío. Útil para distinguir entre varios botones de envío en un mismo formulario. + #[builder_fn] + pub fn with_value(mut self, value: impl AsRef) -> Self { + self.value.alter_str(value); + self + } + + /// Establece o elimina la etiqueta visible del botón (basta pasar `None` para quitarla). + #[builder_fn] + pub fn with_label(mut self, label: impl Into>) -> Self { + self.label.alter_opt(label.into()); + self + } + + /// Establece si el botón recibe el foco automáticamente al cargar la página. + #[builder_fn] + pub fn with_autofocus(mut self, autofocus: bool) -> Self { + self.autofocus = autofocus; + self + } + + /// Establece si el botón está deshabilitado. + #[builder_fn] + pub fn with_disabled(mut self, disabled: bool) -> Self { + self.disabled = disabled; + self + } +} diff --git a/extensions/pagetop-bootsier/src/theme/classes.rs b/extensions/pagetop-bootsier/src/theme/classes.rs index 2009922a..9e6c234d 100644 --- a/extensions/pagetop-bootsier/src/theme/classes.rs +++ b/extensions/pagetop-bootsier/src/theme/classes.rs @@ -3,9 +3,6 @@ mod color; pub use color::{Background, Text}; -mod button; -pub use button::{ButtonColor, ButtonSize}; - mod border; pub use border::Border; diff --git a/extensions/pagetop-bootsier/src/theme/classes/border.rs b/extensions/pagetop-bootsier/src/theme/classes/border.rs index 7085b2e6..bfa65522 100644 --- a/extensions/pagetop-bootsier/src/theme/classes/border.rs +++ b/extensions/pagetop-bootsier/src/theme/classes/border.rs @@ -26,7 +26,7 @@ use crate::theme::attrs::{BorderColor, Opacity, ScaleSize, Side}; /// /// # Ejemplos /// -/// ```rust,no_run +/// ```rust /// use pagetop_bootsier::theme::*; /// /// // Borde global. @@ -145,7 +145,7 @@ impl Border { /// /// # Ejemplos /// -/// ```rust,no_run +/// ```rust /// # use pagetop_bootsier::theme::*; /// // Convertir explícitamente con `From::from`: /// let b = classes::Border::from(ScaleSize::Two); diff --git a/extensions/pagetop-bootsier/src/theme/classes/button.rs b/extensions/pagetop-bootsier/src/theme/classes/button.rs deleted file mode 100644 index 93a8712e..00000000 --- a/extensions/pagetop-bootsier/src/theme/classes/button.rs +++ /dev/null @@ -1,170 +0,0 @@ -use pagetop::prelude::*; - -use crate::theme::attrs::Color; - -// **< ButtonColor >******************************************************************************** - -#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] -enum ButtonColorStyle { - #[default] - None, - Solid, - Outline, - Link, -} - -/// Clases para establecer el **color y estilo** de los botones. -/// -/// # Ejemplos -/// -/// ```rust,no_run -/// use pagetop::prelude::*; -/// use pagetop_bootsier::theme::*; -/// -/// // Botón sólido. -/// let save = Button::submit(L10n::n("Save")) -/// .with_prop(PropsOp::add_classes(classes::ButtonColor::solid(Color::Primary))); -/// -/// // Botón con contorno. -/// let cancel = Button::plain(L10n::n("Cancel")) -/// .with_prop(PropsOp::add_classes(classes::ButtonColor::outline(Color::Secondary))); -/// -/// // Botón tipo enlace. -/// let back = Button::plain(L10n::n("Back")) -/// .with_prop(PropsOp::add_classes(classes::ButtonColor::link())); -/// ``` -#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] -pub struct ButtonColor { - style: ButtonColorStyle, - color: Color, -} - -impl ButtonColor { - /// Sin clase de color (estilo por defecto del tema). - pub fn new() -> Self { - Self::default() - } - - /// Botón sólido: genera la clase `btn-{color}`. - pub fn solid(color: Color) -> Self { - Self { - style: ButtonColorStyle::Solid, - color, - ..Default::default() - } - } - - /// Botón con contorno: genera la clase `btn-outline-{color}`. - pub fn outline(color: Color) -> Self { - Self { - style: ButtonColorStyle::Outline, - color, - ..Default::default() - } - } - - /// Botón tipo enlace: genera la clase `btn-link`. - pub fn link() -> Self { - Self { - style: ButtonColorStyle::Link, - ..Default::default() - } - } - - // **< ButtonColor BUILDER >******************************************************************** - - /// Cambia el color aplicado al botón (`btn-*` o `btn-outline-*`). - pub fn with_color(mut self, color: Color) -> Self { - self.color = color; - self - } - - // **< ButtonColor HELPERS >******************************************************************** - - /// Devuelve la clase `btn-*` correspondiente al color del botón. - /// - /// Si no se ha definido ningún estilo, devuelve `""`. - pub fn to_class(self) -> String { - match self.style { - ButtonColorStyle::None => String::new(), - ButtonColorStyle::Solid => format!("btn-{}", self.color.as_str()), - ButtonColorStyle::Outline => format!("btn-outline-{}", self.color.as_str()), - ButtonColorStyle::Link => "btn-link".to_owned(), - } - } -} - -impl Into for ButtonColor { - fn into(self) -> CowStr { - self.to_class().into() - } -} - -// **< ButtonSize >********************************************************************************* - -#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] -enum ButtonSizeVariant { - #[default] - None, - Small, - Large, -} - -/// Clases para establecer el **tamaño** de los botones. -/// -/// # Ejemplos -/// -/// ```rust,no_run -/// use pagetop::prelude::*; -/// use pagetop_bootsier::theme::*; -/// -/// let small = Button::submit(L10n::n("Save")) -/// .with_prop(PropsOp::add_classes(classes::ButtonSize::small())); -/// -/// let large = Button::submit(L10n::n("Save")) -/// .with_prop(PropsOp::add_classes(classes::ButtonSize::large())); -/// ``` -#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] -pub struct ButtonSize { - size: ButtonSizeVariant, -} - -impl ButtonSize { - /// Sin clase de tamaño (tamaño por defecto del tema). - pub fn new() -> Self { - Self::default() - } - - /// Botón compacto: genera la clase `btn-sm`. - pub fn small() -> Self { - Self { - size: ButtonSizeVariant::Small, - } - } - - /// Botón grande: genera la clase `btn-lg`. - pub fn large() -> Self { - Self { - size: ButtonSizeVariant::Large, - } - } - - // **< ButtonSize HELPERS >********************************************************************* - - /// Devuelve la clase `btn-sm` o `btn-lg` correspondiente al tamaño del botón. - /// - /// Si no se ha definido ningún tamaño, devuelve `""`. - pub fn to_class(self) -> String { - match self.size { - ButtonSizeVariant::None => String::new(), - ButtonSizeVariant::Small => "btn-sm".to_owned(), - ButtonSizeVariant::Large => "btn-lg".to_owned(), - } - } -} - -impl Into for ButtonSize { - fn into(self) -> CowStr { - self.to_class().into() - } -} diff --git a/extensions/pagetop-bootsier/src/theme/classes/layout.rs b/extensions/pagetop-bootsier/src/theme/classes/layout.rs index 42612fec..1438b210 100644 --- a/extensions/pagetop-bootsier/src/theme/classes/layout.rs +++ b/extensions/pagetop-bootsier/src/theme/classes/layout.rs @@ -9,7 +9,7 @@ use crate::theme::attrs::{ScaleSize, Side}; /// /// # Ejemplos /// -/// ```rust,no_run +/// ```rust /// use pagetop_bootsier::theme::*; /// /// let m = classes::Margin::with(Side::Top, ScaleSize::Three); @@ -97,7 +97,7 @@ impl Margin { /// /// # Ejemplos /// -/// ```rust,no_run +/// ```rust /// use pagetop_bootsier::theme::*; /// /// let p = classes::Padding::with(Side::LeftAndRight, ScaleSize::Two); diff --git a/extensions/pagetop-bootsier/src/theme/classes/rounded.rs b/extensions/pagetop-bootsier/src/theme/classes/rounded.rs index d4fbdd2f..9fcb544e 100644 --- a/extensions/pagetop-bootsier/src/theme/classes/rounded.rs +++ b/extensions/pagetop-bootsier/src/theme/classes/rounded.rs @@ -14,7 +14,7 @@ use crate::theme::attrs::RoundedRadius; /// /// # Ejemplos /// -/// ```rust,no_run +/// ```rust /// use pagetop_bootsier::theme::*; /// /// // Radio global: diff --git a/extensions/pagetop-bootsier/src/theme/container/component.rs b/extensions/pagetop-bootsier/src/theme/container/component.rs index 07a835a7..9e4e33c0 100644 --- a/extensions/pagetop-bootsier/src/theme/container/component.rs +++ b/extensions/pagetop-bootsier/src/theme/container/component.rs @@ -11,7 +11,7 @@ use crate::theme::*; /// /// # Ejemplo /// -/// ```rust,no_run +/// ```rust /// use pagetop_bootsier::theme::*; /// /// let main = Container::main() @@ -20,8 +20,10 @@ use crate::theme::*; /// ``` #[derive(AutoDefault, Clone, Debug, Getters)] pub struct Container { - /// Devuelve identificador, clases CSS y atributos HTML del componente. - props: Props, + #[getters(skip)] + id: AttrId, + /// Devuelve las clases CSS asociadas al contenedor. + classes: Classes, /// Devuelve el tipo semántico del contenedor. container_kind: container::Kind, /// Devuelve el comportamiento para el ancho del contenedor. @@ -36,11 +38,11 @@ impl Component for Container { } fn id(&self) -> Option { - self.props.get_id() + self.id.get() } fn setup(&mut self, _cx: &Context) { - self.alter_prop(PropsOp::prepend_classes(self.container_width().to_class())); + self.alter_classes(ClassesOp::Prepend, self.container_width().to_class()); } fn prepare(&self, cx: &mut Context) -> Result { @@ -56,32 +58,32 @@ impl Component for Container { }; Ok(match self.container_kind() { container::Kind::Default => html! { - div (self.props()) style=[style] { + div id=[self.id()] class=[self.classes().get()] style=[style] { (output) } }, container::Kind::Main => html! { - main (self.props()) style=[style] { + main id=[self.id()] class=[self.classes().get()] style=[style] { (output) } }, container::Kind::Header => html! { - header (self.props()) style=[style] { + header id=[self.id()] class=[self.classes().get()] style=[style] { (output) } }, container::Kind::Footer => html! { - footer (self.props()) style=[style] { + footer id=[self.id()] class=[self.classes().get()] style=[style] { (output) } }, container::Kind::Section => html! { - section (self.props()) style=[style] { + section id=[self.id()] class=[self.classes().get()] style=[style] { (output) } }, container::Kind::Article => html! { - article (self.props()) style=[style] { + article id=[self.id()] class=[self.classes().get()] style=[style] { (output) } }, @@ -132,14 +134,14 @@ impl Container { // **< Container BUILDER >********************************************************************** - /// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`. + /// Establece el identificador único (`id`) del contenedor. #[builder_fn] - pub fn with_id(mut self, id: impl Into) -> Self { - self.props.alter_id(id); + pub fn with_id(mut self, id: impl AsRef) -> Self { + self.id.alter_id(id); self } - /// Modifica identificador, clases CSS o atributos HTML del componente. + /// Modifica la lista de clases CSS aplicadas al contenedor. /// /// También acepta clases predefinidas para: /// @@ -148,8 +150,8 @@ impl Container { /// - Establecer bordes ([`classes::Border`]). /// - Redondear las esquinas ([`classes::Rounded`]). #[builder_fn] - pub fn with_prop(mut self, op: PropsOp) -> Self { - self.props.alter_prop(op); + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef) -> Self { + self.classes.alter_classes(op, classes); self } diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/component.rs b/extensions/pagetop-bootsier/src/theme/dropdown/component.rs index e0231b4a..b70fed65 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown/component.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown/component.rs @@ -21,13 +21,13 @@ use crate::theme::*; /// /// # Ejemplo /// -/// ```rust,no_run +/// ```rust /// use pagetop::prelude::*; /// use pagetop_bootsier::theme::*; /// /// let dd = Dropdown::new() /// .with_title(L10n::n("Menu")) -/// .with_button_color(classes::ButtonColor::solid(Color::Secondary)) +/// .with_button_color(ButtonColor::Background(Color::Secondary)) /// .with_auto_close(dropdown::AutoClose::ClickableInside) /// .with_direction(dropdown::Direction::Dropend) /// .with_item(dropdown::Item::link(L10n::n("Home"), |_| "/".into())) @@ -38,14 +38,16 @@ use crate::theme::*; /// ``` #[derive(AutoDefault, Clone, Debug, Getters)] pub struct Dropdown { - /// Devuelve identificador, clases CSS y atributos HTML del componente. - props: Props, + #[getters(skip)] + id: AttrId, + /// Devuelve las clases CSS asociadas al menú desplegable. + classes: Classes, /// Devuelve el título del menú desplegable. title: L10n, /// Devuelve el tamaño configurado del botón. - button_size: classes::ButtonSize, + button_size: ButtonSize, /// Devuelve el color/estilo configurado del botón. - button_color: classes::ButtonColor, + button_color: ButtonColor, /// Devuelve si se debe desdoblar (*split*) el botón (botón de acción + *toggle*). button_split: bool, /// Devuelve si el botón del menú está integrado en un grupo de botones. @@ -68,13 +70,14 @@ impl Component for Dropdown { } fn id(&self) -> Option { - self.props.get_id() + self.id.get() } fn setup(&mut self, _cx: &Context) { - self.alter_prop(PropsOp::prepend_classes( + self.alter_classes( + ClassesOp::Prepend, self.direction().class_with(*self.button_grouped()), - )); + ); } fn prepare(&self, cx: &mut Context) -> Result { @@ -88,25 +91,23 @@ impl Component for Dropdown { let title = self.title().using(cx); Ok(html! { - div (self.props()) { + div id=[self.id()] class=[self.classes().get()] { @if !title.is_empty() { - @let btn_base = { - let size = self.button_size().to_class(); - let color = self.button_color().to_class(); - let mut classes = String::from("btn"); - if !size.is_empty() { classes.push(' '); classes.push_str(&size); } - if !color.is_empty() { classes.push(' '); classes.push_str(&color); } + @let mut btn_classes = Classes::new({ + let mut classes = "btn".to_string(); + self.button_size().push_class(&mut classes); + self.button_color().push_class(&mut classes); classes - }; + }); @let pos = self.menu_position(); @let offset = pos.data_offset(); @let reference = pos.data_reference(); @let auto_close = self.auto_close.as_str(); - @let menu_classes = { + @let menu_classes = Classes::new({ let mut classes = "dropdown-menu".to_string(); self.menu_align().push_class(&mut classes); classes - }; + }); // Renderizado en modo split (dos botones) o simple (un botón). @if *self.button_split() { @@ -114,18 +115,18 @@ impl Component for Dropdown { @let btn = html! { button type="button" - class=(&btn_base) + class=[btn_classes.get()] { (title) } }; // Botón *toggle* que abre/cierra el menú asociado. - @let btn_toggle_classes = - util::join!(&btn_base, " dropdown-toggle dropdown-toggle-split"); @let btn_toggle = html! { button type="button" - class=(&btn_toggle_classes) + class=[btn_classes.alter_classes( + ClassesOp::Add, "dropdown-toggle dropdown-toggle-split" + ).get()] data-bs-toggle="dropdown" data-bs-offset=[offset] data-bs-reference=[reference] @@ -141,21 +142,22 @@ impl Component for Dropdown { @match self.direction() { dropdown::Direction::Dropstart => { (btn_toggle) - ul class=(&menu_classes) { (items) } + ul class=[menu_classes.get()] { (items) } (btn) } _ => { (btn) (btn_toggle) - ul class=(&menu_classes) { (items) } + ul class=[menu_classes.get()] { (items) } } } } @else { // Botón único con funcionalidad de *toggle*. - @let btn_toggle_classes = util::join!(&btn_base, " dropdown-toggle"); button type="button" - class=(&btn_toggle_classes) + class=[btn_classes.alter_classes( + ClassesOp::Add, "dropdown-toggle" + ).get()] data-bs-toggle="dropdown" data-bs-offset=[offset] data-bs-reference=[reference] @@ -164,7 +166,7 @@ impl Component for Dropdown { { (title) } - ul class=(&menu_classes) { (items) } + ul class=[menu_classes.get()] { (items) } } } @else { // Sin botón: sólo el listado como menú contextual. @@ -178,17 +180,17 @@ impl Component for Dropdown { impl Dropdown { // **< Dropdown BUILDER >*********************************************************************** - /// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`. + /// Establece el identificador único (`id`) del menú desplegable. #[builder_fn] - pub fn with_id(mut self, id: impl Into) -> Self { - self.props.alter_id(id); + pub fn with_id(mut self, id: impl AsRef) -> Self { + self.id.alter_id(id); self } - /// Modifica identificador, clases CSS o atributos HTML del componente. + /// Modifica la lista de clases CSS aplicadas al menú desplegable. #[builder_fn] - pub fn with_prop(mut self, op: PropsOp) -> Self { - self.props.alter_prop(op); + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef) -> Self { + self.classes.alter_classes(op, classes); self } @@ -201,14 +203,14 @@ impl Dropdown { /// Ajusta el tamaño del botón. #[builder_fn] - pub fn with_button_size(mut self, size: classes::ButtonSize) -> Self { + pub fn with_button_size(mut self, size: ButtonSize) -> Self { self.button_size = size; self } /// Define el color/estilo del botón. #[builder_fn] - pub fn with_button_color(mut self, color: classes::ButtonColor) -> Self { + pub fn with_button_color(mut self, color: ButtonColor) -> Self { self.button_color = color; self } diff --git a/extensions/pagetop-bootsier/src/theme/dropdown/item.rs b/extensions/pagetop-bootsier/src/theme/dropdown/item.rs index 5eaecc00..379a16c9 100644 --- a/extensions/pagetop-bootsier/src/theme/dropdown/item.rs +++ b/extensions/pagetop-bootsier/src/theme/dropdown/item.rs @@ -45,8 +45,10 @@ pub enum ItemKind { /// asociada, manteniendo una interfaz común para renderizar todos los elementos del menú. #[derive(AutoDefault, Clone, Debug, Getters)] pub struct Item { - /// Devuelve identificador, clases CSS y atributos HTML del componente. - props: Props, + #[getters(skip)] + id: AttrId, + /// Devuelve las clases CSS asociadas al elemento. + classes: Classes, /// Devuelve el tipo de elemento representado. item_kind: ItemKind, } @@ -57,7 +59,7 @@ impl Component for Item { } fn id(&self) -> Option { - self.props.get_id() + self.id.get() } fn prepare(&self, cx: &mut Context) -> Result { @@ -65,7 +67,7 @@ impl Component for Item { ItemKind::Void => html! {}, ItemKind::Label(label) => html! { - li (self.props()) { + li id=[self.id()] class=[self.classes().get()] { span class="dropdown-item-text" { (label.using(cx)) } @@ -99,7 +101,7 @@ impl Component for Item { let tabindex = disabled.then_some("-1"); html! { - li (self.props()) { + li id=[self.id()] class=[self.classes().get()] { a class=(classes) href=[href] @@ -125,7 +127,7 @@ impl Component for Item { let disabled_attr = disabled.then_some("disabled"); html! { - li (self.props()) { + li id=[self.id()] class=[self.classes().get()] { button class=(classes) type="button" @@ -139,7 +141,7 @@ impl Component for Item { } ItemKind::Header(label) => html! { - li (self.props()) { + li id=[self.id()] class=[self.classes().get()] { h6 class="dropdown-header" { (label.using(cx)) } @@ -147,7 +149,7 @@ impl Component for Item { }, ItemKind::Divider => html! { - li (self.props()) { hr class="dropdown-divider" {} } + li id=[self.id()] class=[self.classes().get()] { hr class="dropdown-divider" {} } }, }) } @@ -258,17 +260,17 @@ impl Item { // **< Item BUILDER >*************************************************************************** - /// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`. + /// Establece el identificador único (`id`) del elemento. #[builder_fn] - pub fn with_id(mut self, id: impl Into) -> Self { - self.props.alter_id(id); + pub fn with_id(mut self, id: impl AsRef) -> Self { + self.id.alter_id(id); self } - /// Modifica identificador, clases CSS o atributos HTML del componente. + /// Modifica la lista de clases CSS aplicadas al elemento. #[builder_fn] - pub fn with_prop(mut self, op: PropsOp) -> Self { - self.props.alter_prop(op); + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef) -> Self { + self.classes.alter_classes(op, classes); self } } diff --git a/extensions/pagetop-bootsier/src/theme/form.rs b/extensions/pagetop-bootsier/src/theme/form.rs index c088a86c..82c603ef 100644 --- a/extensions/pagetop-bootsier/src/theme/form.rs +++ b/extensions/pagetop-bootsier/src/theme/form.rs @@ -1,24 +1,30 @@ //! Definiciones para crear formularios ([`Form`]). -pub use pagetop::base::component::form::{Autocomplete, AutofillField, CheckboxKind, Method}; +mod props; +pub use props::{Autocomplete, AutofillField, CheckboxKind, Method}; -pub use pagetop::base::component::form::Form; +mod component; +pub use component::Form; -pub use pagetop::base::component::form::Fieldset; +mod fieldset; +pub use fieldset::Fieldset; -pub use pagetop::base::component::form::Checkbox; +mod checkbox; +pub use checkbox::Checkbox; -pub use pagetop::base::component::form::check; +pub mod check; -pub use pagetop::base::component::form::radio; +pub mod radio; pub mod select; pub mod input; -pub mod textarea; +mod textarea; pub use textarea::Textarea; -pub use pagetop::base::component::form::Range; +mod range; +pub use range::Range; -pub use pagetop::base::component::form::Hidden; +mod hidden; +pub use hidden::Hidden; diff --git a/src/base/component/form/check.rs b/extensions/pagetop-bootsier/src/theme/form/check.rs similarity index 83% rename from src/base/component/form/check.rs rename to extensions/pagetop-bootsier/src/theme/form/check.rs index 2bbc1091..434b7e6c 100644 --- a/src/base/component/form/check.rs +++ b/extensions/pagetop-bootsier/src/theme/form/check.rs @@ -1,10 +1,10 @@ //! Definiciones para crear grupos de casillas de verificación (*check buttons*). -use crate::prelude::*; +use pagetop::prelude::*; // **< Item >*************************************************************************************** -/// Casilla de verificación individual de un [`Field`]. +/// Casilla de verificación individual de un [`form::check::Field`](Field). /// /// Representa cada casilla de un grupo de casillas de verificación, con una etiqueta localizable /// visible. Puede marcarse como seleccionada o deshabilitada de forma independiente al resto. @@ -15,9 +15,9 @@ use crate::prelude::*; /// /// # Ejemplo /// -/// ```rust,no_run -/// use pagetop::prelude::*; -/// +/// ```rust +/// # use pagetop::prelude::*; +/// # use pagetop_bootsier::theme::*; /// let item = form::check::Item::new("apple", L10n::n("Apple")).with_checked(true); /// ``` #[derive(AutoDefault, Clone, Debug, Getters)] @@ -65,21 +65,24 @@ impl Item { /// Componente para crear un **grupo de casillas de verificación**. /// -/// Renderiza un conjunto de casillas de verificación donde cada casilla puede marcarse de forma -/// independiente. Las casillas se añaden con [`with_item()`](Field::with_item) usando instancias -/// de [`form::check::Item`]. Si se activa el modo en línea con +/// Renderiza un conjunto de casillas de verificación donde, a diferencia de un grupo de botones +/// [`form::radio::Field`](crate::theme::form::radio::Field), cada casilla puede marcarse de forma +/// independiente. +/// +/// Las casillas se añaden mediante [`with_item()`](Field::with_item) usando instancias de +/// [`form::check::Item`](Item). Si se activa el modo en línea con /// [`with_inline()`](Field::with_inline), las casillas se disponen horizontalmente. /// /// El atributo `name` de cada casilla se construye automáticamente combinando el `name` del grupo -/// y el `name` del [`form::check::Item`] con un guion bajo. Por ejemplo, para el grupo con +/// y el `name` del [`form::check::Item`](Item) con un guion bajo. Por ejemplo, para el grupo con /// `name=interests` y casillas con `name=art` y `name=tech`, se genera `name=interests_art` y /// `name=interests_tech`. /// /// # Ejemplo /// -/// ```rust,no_run -/// use pagetop::prelude::*; -/// +/// ```rust +/// # use pagetop::prelude::*; +/// # use pagetop_bootsier::theme::*; /// let interests = form::check::Field::new() /// .with_name("interests") /// .with_label(L10n::n("Areas of interest")) @@ -105,8 +108,10 @@ impl Item { /// ``` #[derive(AutoDefault, Clone, Debug, Getters)] pub struct Field { - /// Devuelve identificador, clases CSS y atributos HTML del componente. - props: Props, + #[getters(skip)] + id: AttrId, + /// Devuelve las clases CSS del contenedor del grupo. + classes: Classes, /// Devuelve el nombre base compartido por todas las casillas del grupo. name: AttrName, /// Devuelve la etiqueta del grupo. @@ -127,31 +132,21 @@ impl Component for Field { } fn id(&self) -> Option { - self.props.get_id() + self.id.get() } - fn setup(&mut self, cx: &Context) { - // Asegura `name` e `id`. - // Si falta uno se deriva del otro; si faltan ambos se genera un valor único. + fn setup(&mut self, _cx: &Context) { + self.alter_classes(ClassesOp::Prepend, "form-field form-field-checkboxes"); + } + + fn prepare(&self, cx: &mut Context) -> Result { let name = self .name() .get() .unwrap_or_else(|| cx.required_id::(self.id(), 3)); - self.alter_name(&name); let container_id = self.id().unwrap_or_else(|| util::join!("edit-", &name)); - self.alter_prop(PropsOp::ensure_id(container_id)); - - // Clases CSS del contenedor del grupo de casillas. - self.alter_prop(PropsOp::prepend_classes("form-field form-field-checkboxes")); - } - - fn prepare(&self, cx: &mut Context) -> Result { - // En `setup()` se garantiza que `name` e `id` están definidos antes del renderizado. - let name = self.name().get().unwrap(); - let container_id = self.id().unwrap(); - Ok(html! { - div (self.props()) { + div id=(&container_id) class=[self.classes().get()] { @if let Some(label) = self.label().lookup(cx) { label class="form-label" { (label) } } @@ -193,17 +188,17 @@ impl Component for Field { impl Field { // **< Field BUILDER >************************************************************************** - /// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`. + /// Establece el identificador único (`id`) del grupo de casillas. #[builder_fn] - pub fn with_id(mut self, id: impl Into) -> Self { - self.props.alter_id(id); + pub fn with_id(mut self, id: impl AsRef) -> Self { + self.id.alter_id(id); self } - /// Modifica identificador, clases CSS o atributos HTML del componente. + /// Modifica la lista de clases CSS aplicadas al contenedor del grupo de casillas. #[builder_fn] - pub fn with_prop(mut self, op: PropsOp) -> Self { - self.props.alter_prop(op); + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef) -> Self { + self.classes.alter_classes(op, classes); self } diff --git a/src/base/component/form/checkbox.rs b/extensions/pagetop-bootsier/src/theme/form/checkbox.rs similarity index 82% rename from src/base/component/form/checkbox.rs rename to extensions/pagetop-bootsier/src/theme/form/checkbox.rs index cf382e27..18ab908f 100644 --- a/src/base/component/form/checkbox.rs +++ b/extensions/pagetop-bootsier/src/theme/form/checkbox.rs @@ -1,9 +1,13 @@ -use crate::prelude::*; +use pagetop::prelude::*; + +use crate::LOCALES_BOOTSIER; +use crate::theme::form; /// Componente para crear una **casilla de verificación** o un **interruptor** (*toggle switch*). /// -/// Renderiza un control binario (marcado/no marcado) en dos variantes, por defecto como casilla de -/// verificación estándar, y también como interruptor ([`Checkbox::switch()`]). +/// Renderiza un control binario (marcado/no marcado) en dos variantes visuales, por defecto se +/// muestra como una casilla de verificación estándar, pero también puede renderizarse como un +/// interruptor de encendido/apagado ([`Checkbox::switch()`]). /// /// Se puede mostrar en línea con otros controles usando [`with_inline()`](Checkbox::with_inline), o /// justificar a la derecha del contenedor invirtiendo el orden de la etiqueta y el control usando @@ -11,10 +15,10 @@ use crate::prelude::*; /// /// # Ejemplo /// -/// ```rust,no_run -/// use pagetop::prelude::*; -/// -/// let accept_terms = form::Checkbox::new() +/// ```rust +/// # use pagetop::prelude::*; +/// # use pagetop_bootsier::theme::*; +/// let accept_terms = form::Checkbox::check() // También sirve new() o default(). /// .with_name("terms_accepted") /// .with_label(L10n::n("I accept the terms and conditions")) /// .with_required(true); @@ -39,8 +43,10 @@ use crate::prelude::*; /// ``` #[derive(AutoDefault, Clone, Debug, Getters)] pub struct Checkbox { - /// Devuelve identificador, clases CSS y atributos HTML del componente. - props: Props, + #[getters(skip)] + id: AttrId, + /// Devuelve las clases CSS del contenedor del control. + classes: Classes, /// Devuelve la variante visual del control. checkbox_kind: form::CheckboxKind, /// Devuelve el nombre del campo. @@ -67,22 +73,11 @@ impl Component for Checkbox { } fn id(&self) -> Option { - self.props.get_id() + self.id.get() } - fn setup(&mut self, cx: &Context) { - // Asegura `name` e `id`. - // Si falta uno se deriva del otro; si faltan ambos se genera un valor único. - let name = self - .name() - .get() - .unwrap_or_else(|| cx.required_id::(self.id(), 1)); - self.alter_name(&name); - let container_id = self.id().unwrap_or_else(|| util::join!("edit-", &name)); - self.alter_prop(PropsOp::ensure_id(container_id)); - - // Clases CSS del contenedor de la casilla de verificación. - let mut classes = String::from("form-field form-check"); + fn setup(&mut self, _cx: &Context) { + let mut classes = "form-field form-check".to_string(); if *self.checkbox_kind() == form::CheckboxKind::Switch { classes.push_str(" form-switch"); } @@ -92,19 +87,19 @@ impl Component for Checkbox { if *self.reverse() { classes.push_str(" form-check-reverse"); } - self.alter_prop(PropsOp::prepend_classes(classes)); + self.alter_classes(ClassesOp::Prepend, classes); } fn prepare(&self, cx: &mut Context) -> Result { - // En `setup()` se garantiza que `name` e `id` están definidos antes del renderizado. - let name = self.name().get().unwrap(); - let container_id = self.id().unwrap(); - + let name = self + .name() + .get() + .unwrap_or_else(|| cx.required_id::(self.id(), 1)); + let container_id = self.id().unwrap_or_else(|| util::join!("edit-", &name)); let checkbox_id = util::join!(&container_id, "-checkbox"); let is_switch = *self.checkbox_kind() == form::CheckboxKind::Switch; - Ok(html! { - div (self.props()) { + div id=(&container_id) class=[self.classes().get()] { input type="checkbox" role=[is_switch.then_some("switch")] @@ -122,7 +117,7 @@ impl Component for Checkbox { @if *self.required() { span class="form-required" - title=(L10n::l("field_required").using(cx)) + title=(L10n::t("input_required", &LOCALES_BOOTSIER).using(cx)) { "*" } @@ -150,17 +145,17 @@ impl Checkbox { // **< Checkbox BUILDER >*********************************************************************** - /// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`. + /// Establece el identificador único (`id`) del control. #[builder_fn] - pub fn with_id(mut self, id: impl Into) -> Self { - self.props.alter_id(id); + pub fn with_id(mut self, id: impl AsRef) -> Self { + self.id.alter_id(id); self } - /// Modifica identificador, clases CSS o atributos HTML del componente. + /// Modifica la lista de clases CSS aplicadas al contenedor del control. #[builder_fn] - pub fn with_prop(mut self, op: PropsOp) -> Self { - self.props.alter_prop(op); + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef) -> Self { + self.classes.alter_classes(op, classes); self } diff --git a/src/base/component/form/component.rs b/extensions/pagetop-bootsier/src/theme/form/component.rs similarity index 77% rename from src/base/component/form/component.rs rename to extensions/pagetop-bootsier/src/theme/form/component.rs index dc222ee8..80efa941 100644 --- a/src/base/component/form/component.rs +++ b/extensions/pagetop-bootsier/src/theme/form/component.rs @@ -1,22 +1,24 @@ -use crate::prelude::*; +use pagetop::prelude::*; -use crate::base::component::form; +use crate::theme::form; -/// Componente para crear un **formulario** HTML ([`form`]). +/// Componente para crear un **formulario** ([`form`]). /// -/// Renderiza un formulario estándar con soporte para los atributos más habituales: +/// Este componente renderiza un formulario estándar con soporte para los atributos más habituales: /// /// - `id`: identificador opcional del formulario. /// - `classes`: clases CSS adicionales (p. ej. utilidades CSS). /// - `action`: URL/ruta de destino para el envío. -/// - `method`: método usado por el formulario para el envío de los datos (ver [`form::Method`]). +/// - `method`: método usado por el formulario para el envío de los datos (ver explicaciones en +/// [`form::Method`](crate::theme::form::Method)). /// - `accept-charset`: juego de caracteres aceptado (por defecto es `"UTF-8"`). /// - `children`: contenido del formulario. /// /// # Ejemplo /// -/// ```rust,no_run +/// ```rust /// use pagetop::prelude::*; +/// use pagetop_bootsier::theme::*; /// /// let form_login = Form::new() /// .with_id("login") @@ -40,12 +42,15 @@ use crate::base::component::form; /// ) /// .with_child( /// Button::submit(L10n::n("Sign in")) +/// .with_color(ButtonColor::Background(Color::Primary)), /// ); /// ``` #[derive(AutoDefault, Clone, Debug, Getters)] pub struct Form { - /// Devuelve identificador, clases CSS y atributos HTML del componente. - props: Props, + #[getters(skip)] + id: AttrId, + /// Devuelve las clases CSS del formulario. + classes: Classes, /// Devuelve la URL/ruta de destino del formulario. action: AttrValue, /// Devuelve el método para enviar el formulario. @@ -63,11 +68,11 @@ impl Component for Form { } fn id(&self) -> Option { - self.props.get_id() + self.id.get() } fn setup(&mut self, _cx: &Context) { - self.alter_prop(PropsOp::prepend_classes("form")); + self.alter_classes(ClassesOp::Prepend, "form"); } fn prepare(&self, cx: &mut Context) -> Result { @@ -77,7 +82,8 @@ impl Component for Form { }; Ok(html! { form - (self.props()) + id=[self.id()] + class=[self.classes().get()] action=[self.action().get()] method=[method] accept-charset=[self.charset().get()] @@ -91,17 +97,17 @@ impl Component for Form { impl Form { // **< Form BUILDER >*************************************************************************** - /// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`. + /// Establece el identificador único (`id`) del formulario. #[builder_fn] - pub fn with_id(mut self, id: impl Into) -> Self { - self.props.alter_id(id); + pub fn with_id(mut self, id: impl AsRef) -> Self { + self.id.alter_id(id); self } - /// Modifica identificador, clases CSS o atributos HTML del componente. + /// Modifica la lista de clases CSS aplicadas al formulario. #[builder_fn] - pub fn with_prop(mut self, op: PropsOp) -> Self { - self.props.alter_prop(op); + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef) -> Self { + self.classes.alter_classes(op, classes); self } diff --git a/src/base/component/form/fieldset.rs b/extensions/pagetop-bootsier/src/theme/form/fieldset.rs similarity index 82% rename from src/base/component/form/fieldset.rs rename to extensions/pagetop-bootsier/src/theme/form/fieldset.rs index 1e420480..1aacba6c 100644 --- a/src/base/component/form/fieldset.rs +++ b/extensions/pagetop-bootsier/src/theme/form/fieldset.rs @@ -1,4 +1,4 @@ -use crate::prelude::*; +use pagetop::prelude::*; /// Componente para crear un **grupo de controles relacionados** en un formulario. /// @@ -13,9 +13,9 @@ use crate::prelude::*; /// /// # Ejemplo /// -/// ```rust,no_run -/// use pagetop::prelude::*; -/// +/// ```rust +/// # use pagetop::prelude::*; +/// # use pagetop_bootsier::theme::*; /// let personal_data = form::Fieldset::new() /// .with_legend(L10n::n("Personal data")) /// .with_description(L10n::n("Enter your full name and contact email.")) @@ -24,8 +24,10 @@ use crate::prelude::*; /// ``` #[derive(AutoDefault, Clone, Debug, Getters)] pub struct Fieldset { - /// Devuelve identificador, clases CSS y atributos HTML del componente. - props: Props, + #[getters(skip)] + id: AttrId, + /// Devuelve las clases CSS del `fieldset`. + classes: Classes, /// Devuelve la leyenda del `fieldset`. legend: Attr, /// Devuelve la descripción del `fieldset`. @@ -42,7 +44,7 @@ impl Component for Fieldset { } fn id(&self) -> Option { - self.props.get_id() + self.id.get() } fn prepare(&self, cx: &mut Context) -> Result { @@ -53,7 +55,7 @@ impl Component for Fieldset { } Ok(html! { - fieldset (self.props()) disabled[*self.disabled()] { + fieldset id=[self.id()] class=[self.classes().get()] disabled[*self.disabled()] { @if let Some(legend) = self.legend().lookup(cx) { legend { (legend) } } @@ -69,17 +71,17 @@ impl Component for Fieldset { impl Fieldset { // **< Fieldset BUILDER >*********************************************************************** - /// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`. + /// Establece el identificador único (`id`) del `fieldset` (grupo de controles). #[builder_fn] - pub fn with_id(mut self, id: impl Into) -> Self { - self.props.alter_id(id); + pub fn with_id(mut self, id: impl AsRef) -> Self { + self.id.alter_id(id); self } - /// Modifica identificador, clases CSS o atributos HTML del componente. + /// Modifica la lista de clases CSS aplicadas al `fieldset`. #[builder_fn] - pub fn with_prop(mut self, op: PropsOp) -> Self { - self.props.alter_prop(op); + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef) -> Self { + self.classes.alter_classes(op, classes); self } diff --git a/src/base/component/form/hidden.rs b/extensions/pagetop-bootsier/src/theme/form/hidden.rs similarity index 95% rename from src/base/component/form/hidden.rs rename to extensions/pagetop-bootsier/src/theme/form/hidden.rs index 5ad9e3ed..6d367155 100644 --- a/src/base/component/form/hidden.rs +++ b/extensions/pagetop-bootsier/src/theme/form/hidden.rs @@ -1,4 +1,4 @@ -use crate::prelude::*; +use pagetop::prelude::*; /// Componente para crear un **campo oculto** del formulario. /// @@ -10,9 +10,9 @@ use crate::prelude::*; /// /// # Ejemplo /// -/// ```rust,no_run -/// use pagetop::prelude::*; -/// +/// ```rust +/// # use pagetop::prelude::*; +/// # use pagetop_bootsier::theme::*; /// let token = form::Hidden::new() /// .with_name("csrf_token") /// .with_value("a1b2c3d4e5"); diff --git a/extensions/pagetop-bootsier/src/theme/form/input.rs b/extensions/pagetop-bootsier/src/theme/form/input.rs index c0d35dd9..68cce931 100644 --- a/extensions/pagetop-bootsier/src/theme/form/input.rs +++ b/extensions/pagetop-bootsier/src/theme/form/input.rs @@ -2,39 +2,452 @@ use pagetop::prelude::*; -pub use pagetop::base::component::form::input::{Field, Kind, Mode}; +use crate::LOCALES_BOOTSIER; +use crate::theme::form; -/// Extensión de Bootsier para [`form::input::Field`]. +use std::fmt; + +// **< Kind >*************************************************************************************** + +/// Tipo de campo para un [`form::input::Field`]. /// -/// Proporciona soporte para **etiquetas flotantes** (*floating label*). La etiqueta flotante se -/// superpone al control mientras está vacío y permanece flotante cuando tiene contenido o está -/// enfocado. +/// Determina el tipo de entrada que acepta, así como el comportamiento del navegador al interactuar +/// con el campo. Implícitamente se aplica al crear el control: [`text()`](Field::text), +/// [`password()`](Field::password), [`search()`](Field::search), [`email()`](Field::email), +/// [`telephone()`](Field::telephone) o [`url()`](Field::url). +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] +pub enum Kind { + /// Entrada de texto genérico (`type="text"`). Es el tipo por defecto. + #[default] + Text, + /// Entrada de una contraseña (`type="password"`). El contenido aparece enmascarado. + Password, + /// Campo de búsqueda (`type="search"`). Es un tipo semántico para los cuadros de búsqueda. + Search, + /// Entrada de un correo electrónico (`type="email"`). Permite validar el formato del correo. + Email, + /// Entrada de un teléfono (`type="tel"`). Activa el teclado de llamadas en móviles. + Telephone, + /// Entrada de una URL (`type="url"`). Comprueba que la entrada sea una URL bien formada. + Url, +} + +impl fmt::Display for Kind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Kind::Text => "text", + Kind::Password => "password", + Kind::Search => "search", + Kind::Email => "email", + Kind::Telephone => "tel", + Kind::Url => "url", + }) + } +} + +// **< Mode >*************************************************************************************** + +/// Sugerencia para el teclado virtual de un [`form::input::Field`]. /// -/// ```rust,no_run -/// # use pagetop::locale::L10n; -/// use pagetop_bootsier::theme::*; +/// Indica al navegador qué tipo de teclado virtual mostrar en dispositivos móviles o táctiles al +/// editar el campo. A diferencia del atributo `type` ([`form::input::Kind`]), no restringe los +/// valores aceptados ni activa la validación del navegador; es sólo una sugerencia de presentación. /// -/// let nombre = form::input::Field::text() -/// .with_name("name") -/// .with_label(L10n::n("Name")) -/// .with_placeholder(L10n::n("Enter your name")) -/// .with_floating_label(true); +/// Se establece con [`form::input::Field::with_inputmode()`]. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum Mode { + /// Suprime el teclado virtual. Útil en campos con teclado personalizado basado en JavaScript. + None, + /// Teclado de texto genérico. + Text, + /// Teclado decimal, con dígitos y separador decimal. + Decimal, + /// Teclado numérico, con sólo dígitos. + Numeric, + /// Teclado de teléfono, con dígitos y símbolos `+`, `*` y `#`. + Tel, + /// Teclado optimizado para búsquedas (puede incluir tecla de búsqueda). + Search, + /// Teclado optimizado para correo electrónico (incluye `@` y `.`). + Email, + /// Teclado optimizado para URL (incluye `/`, `.` y `.com`). + Url, +} + +impl fmt::Display for Mode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match self { + Mode::None => "none", + Mode::Text => "text", + Mode::Decimal => "decimal", + Mode::Numeric => "numeric", + Mode::Tel => "tel", + Mode::Search => "search", + Mode::Email => "email", + Mode::Url => "url", + }) + } +} + +// **< Field >************************************************************************************** + +/// Componente para crear un **campo de texto de una línea**. +/// +/// Renderiza los tipos más habituales en formularios: +/// +/// - [`form::input::Field::text()`]: campo de texto genérico (`type="text"`, por defecto). +/// - [`form::input::Field::password()`]: contraseña (`type="password"`). +/// - [`form::input::Field::search()`]: búsqueda (`type="search"`). +/// - [`form::input::Field::email()`]: correo electrónico (`type="email"`). +/// - [`form::input::Field::telephone()`]: teléfono (`type="tel"`). +/// - [`form::input::Field::url()`]: URL (`type="url"`). +/// +/// # Ejemplo +/// +/// ```rust +/// # use pagetop::prelude::*; +/// # use pagetop_bootsier::theme::*; +/// let email = form::input::Field::email() +/// .with_name("email") +/// .with_label(L10n::n("Email address")) +/// .with_placeholder(L10n::n("user@example.com")) +/// .with_autocomplete(Some(form::Autocomplete::email())) +/// .with_required(true); /// ``` -pub trait InputBootsier { +/// +/// Al enviar el formulario el navegador transmite `name=valor`. Un campo de texto siempre envía su +/// valor, incluso si está vacío. En el servidor se deserializa como `String`: +/// +/// ```rust,ignore +/// #[derive(serde::Deserialize)] +/// struct FormData { +/// email: String, // Siempre presente; cadena vacía si el usuario no escribió nada. +/// } +/// ``` +#[derive(AutoDefault, Clone, Debug, Getters)] +pub struct Field { + #[getters(skip)] + id: AttrId, + /// Devuelve las clases CSS del contenedor del campo. + classes: Classes, + /// Devuelve el tipo de campo. + kind: Kind, + /// Devuelve el nombre del campo. + name: AttrName, + /// Devuelve el valor inicial del campo. + value: AttrValue, + /// Devuelve la etiqueta del campo. + label: Attr, + /// Devuelve si la etiqueta se muestra flotante sobre el campo. + floating_label: bool, + /// Devuelve el texto de ayuda del campo. + help_text: Attr, + /// Devuelve la longitud mínima permitida en caracteres. + minlength: Attr, + /// Devuelve la longitud máxima permitida en caracteres. + maxlength: Attr, + /// Devuelve el texto indicativo del campo. + placeholder: Attr, + /// Devuelve la configuración de autocompletado del campo. + autocomplete: Attr, + /// Devuelve si el campo recibe el foco automáticamente al cargar la página. + autofocus: bool, + /// Devuelve si el campo es de sólo lectura. + readonly: bool, + /// Devuelve si el campo es obligatorio. + required: bool, + /// Devuelve si el campo está deshabilitado. + disabled: bool, + /// Devuelve si el campo se muestra como texto plano sin bordes ni fondo. + plaintext: bool, + /// Devuelve la sugerencia de teclado virtual para el campo. + inputmode: Attr, +} + +impl Component for Field { + fn new() -> Self { + Self::default() + } + + fn id(&self) -> Option { + self.id.get() + } + + fn setup(&mut self, _cx: &Context) { + if *self.floating_label() { + self.alter_classes(ClassesOp::Prepend, "form-floating"); + } + self.alter_classes( + ClassesOp::Prepend, + util::join!("form-field form-field-", self.kind().to_string()), + ); + } + + fn prepare(&self, cx: &mut Context) -> Result { + let container_id = self + .id() + .or_else(|| self.name().get().map(|n| util::join!("edit-", n))); + let input_id = container_id.as_deref().map(|id| util::join!(id, "-input")); + let input_class = if *self.plaintext() { + "form-control-plaintext" + } else { + "form-control" + }; + // La etiqueta flotante requiere el atributo `placeholder` para detectar cuándo el campo + // está vacío y animar la etiqueta; si no está definido, se fuerza `placeholder=""`. + let placeholder = if *self.floating_label() { + Some(self.placeholder().lookup(cx).unwrap_or_default()) + } else { + self.placeholder().lookup(cx) + }; + let label = match self.label().lookup(cx) { + Some(text) => html! { + label for=[input_id.as_deref()] class="form-label" { + (text) + @if *self.required() { + span + class="form-required" + title=(L10n::t("input_required", &LOCALES_BOOTSIER).using(cx)) + { + "*" + } + } + } + }, + None => html! {}, + }; + Ok(html! { + div id=[container_id.as_deref()] class=[self.classes().get()] { + @if !*self.floating_label() { + (label) + } + input + type=(self.kind()) + id=[input_id.as_deref()] + class=(input_class) + name=[self.name().get()] + value=[self.value().get()] + minlength=[self.minlength().get()] + maxlength=[self.maxlength().get()] + placeholder=[placeholder] + inputmode=[self.inputmode().get()] + autocomplete=[self.autocomplete().get()] + autofocus[*self.autofocus()] + readonly[*self.readonly() || *self.plaintext()] + required[*self.required()] + disabled[*self.disabled()]; + @if *self.floating_label() { + (label) + } + @if let Some(description) = self.help_text().lookup(cx) { + div class="form-text" { (description) } + } + } + }) + } +} + +impl Field { + /// Crea un campo de **texto genérico** (`type="text"`). + /// + /// Es el tipo por defecto. Adecuado para nombres, apellidos, ciudades y cualquier entrada + /// textual sin restricciones de formato específicas. + pub fn text() -> Self { + Self::default() + } + + /// Crea un campo de **contraseña** (`type="password"`). + /// + /// El navegador oculta los caracteres introducidos. Se recomienda usar con + /// [`with_autocomplete()`](Self::with_autocomplete) para permitir autorrellenar con una + /// contraseña guardada o dejar al usuario recibir sugerencias o crear una nueva. + pub fn password() -> Self { + Self { + kind: Kind::Password, + ..Default::default() + } + } + + /// Crea un campo de **búsqueda** (`type="search"`). + /// + /// Semánticamente equivalente a `text` pero optimizado para búsquedas: algunos + /// navegadores añaden un botón para borrar el contenido. + pub fn search() -> Self { + Self { + kind: Kind::Search, + ..Default::default() + } + } + + /// Crea un campo de **correo electrónico** (`type="email"`). + /// + /// El navegador valida el formato de la dirección antes de enviar el formulario. En + /// dispositivos móviles muestra un teclado adaptado para introducir direcciones de correo. + pub fn email() -> Self { + Self { + kind: Kind::Email, + ..Default::default() + } + } + + /// Crea un campo de **teléfono** (`type="tel"`). + /// + /// No impone ninguna restricción de formato (los formatos de teléfono varían por país), pero + /// en dispositivos móviles muestra el teclado numérico de llamadas. + pub fn telephone() -> Self { + Self { + kind: Kind::Telephone, + ..Default::default() + } + } + + /// Crea un campo de **URL** (`type="url"`). + /// + /// El navegador valida que el valor sea una URL bien formada antes de enviar el formulario. + pub fn url() -> Self { + Self { + kind: Kind::Url, + ..Default::default() + } + } + + // **< Field BUILDER >************************************************************************** + + /// Establece el identificador único (`id`) del contenedor del campo. + #[builder_fn] + pub fn with_id(mut self, id: impl AsRef) -> Self { + self.id.alter_id(id); + self + } + + /// Modifica la lista de clases CSS aplicadas al contenedor del campo. + #[builder_fn] + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef) -> Self { + self.classes.alter_classes(op, classes); + self + } + + /// Establece el nombre del campo (atributo `name`). + /// + /// Sin él, el valor del campo no se transmite al servidor al enviar el formulario. Para + /// deserializar el campo en el servidor es recomendable establecer un `name` explícito. + #[builder_fn] + pub fn with_name(mut self, name: impl AsRef) -> Self { + self.name.alter_name(name); + self + } + + /// Establece el valor inicial del campo. + #[builder_fn] + pub fn with_value(mut self, value: impl AsRef) -> Self { + self.value.alter_str(value); + self + } + + /// Establece o elimina la etiqueta visible del campo (basta pasar `None` para quitarla). + #[builder_fn] + pub fn with_label(mut self, label: impl Into>) -> Self { + self.label.alter_opt(label.into()); + self + } + /// Establece si la etiqueta se muestra flotante sobre el campo. /// /// Cuando está activo, la etiqueta se superpone al campo y asciende al enfocarlo o cuando tiene - /// contenido. Requiere que el campo tenga un atributo `placeholder` definido; si no se - /// especifica, se fuerza `placeholder=""` antes del renderizado. - fn with_floating_label(self, floating: bool) -> Self; -} + /// contenido. + #[builder_fn] + pub fn with_floating_label(mut self, floating_label: bool) -> Self { + self.floating_label = floating_label; + self + } -impl InputBootsier for Field { - fn with_floating_label(self, floating: bool) -> Self { - if floating { - self.with_prop(PropsOp::add_classes("form-floating")) - } else { - self.with_prop(PropsOp::remove_classes("form-floating")) - } + /// Establece o elimina el texto de ayuda del campo (basta pasar `None` para quitarlo). + #[builder_fn] + pub fn with_help_text(mut self, help_text: impl Into>) -> Self { + self.help_text.alter_opt(help_text.into()); + self + } + + /// Establece la longitud mínima permitida en caracteres (`None` para no imponer mínimo). + #[builder_fn] + pub fn with_minlength(mut self, minlength: Option) -> Self { + self.minlength.alter_opt(minlength); + self + } + + /// Establece la longitud máxima permitida en caracteres (`None` para no imponer límite). + #[builder_fn] + pub fn with_maxlength(mut self, maxlength: Option) -> Self { + self.maxlength.alter_opt(maxlength); + self + } + + /// Establece o elimina el texto indicativo del campo (`None` para quitarlo). + /// + /// Este texto aparece en el mismo campo y desaparece en cuanto el usuario empieza a escribir. + /// Al ser texto visible para el usuario se acepta [`L10n`] para poder localizarlo. + #[builder_fn] + pub fn with_placeholder(mut self, placeholder: impl Into>) -> Self { + self.placeholder.alter_opt(placeholder.into()); + self + } + + /// Establece la configuración de autocompletado del campo. + /// + /// Usar los métodos de [`form::Autocomplete`] para los valores más habituales (p. ej. + /// [`Autocomplete::email()`](form::Autocomplete::email) o + /// [`Autocomplete::current_password()`](form::Autocomplete::current_password)). + #[builder_fn] + pub fn with_autocomplete(mut self, autocomplete: Option) -> Self { + self.autocomplete.alter_opt(autocomplete); + self + } + + /// Establece si el campo recibe el foco automáticamente al cargar la página. + #[builder_fn] + pub fn with_autofocus(mut self, autofocus: bool) -> Self { + self.autofocus = autofocus; + self + } + + /// Establece si el campo es de sólo lectura. + #[builder_fn] + pub fn with_readonly(mut self, readonly: bool) -> Self { + self.readonly = readonly; + self + } + + /// Establece si el campo es obligatorio. + #[builder_fn] + pub fn with_required(mut self, required: bool) -> Self { + self.required = required; + self + } + + /// Establece si el campo está deshabilitado. + #[builder_fn] + pub fn with_disabled(mut self, disabled: bool) -> Self { + self.disabled = disabled; + self + } + + /// Establece si el campo se muestra como texto plano (sin bordes ni fondo). + /// + /// Útil para mostrar un valor no editable en pantalla que sí se envía al servidor con el + /// formulario. + #[builder_fn] + pub fn with_plaintext(mut self, plaintext: bool) -> Self { + self.plaintext = plaintext; + self + } + + /// Establece el modo de entrada sugerido para el teclado virtual en dispositivos móviles. + /// + /// A diferencia del atributo `type` ([`form::input::Kind`]), no restringe los valores aceptados + /// ni activa la validación del navegador; es sólo una sugerencia de presentación. + #[builder_fn] + pub fn with_inputmode(mut self, inputmode: Option) -> Self { + self.inputmode.alter_opt(inputmode); + self } } diff --git a/src/base/component/form/props.rs b/extensions/pagetop-bootsier/src/theme/form/props.rs similarity index 97% rename from src/base/component/form/props.rs rename to extensions/pagetop-bootsier/src/theme/form/props.rs index 4e69d924..2ef88f3e 100644 --- a/src/base/component/form/props.rs +++ b/extensions/pagetop-bootsier/src/theme/form/props.rs @@ -1,21 +1,20 @@ -use crate::prelude::*; +use pagetop::prelude::*; use std::borrow::Cow; use std::fmt; // **< CheckboxKind >******************************************************************************* -/// Variante visual para un [`form::Checkbox`] en un formulario. +/// Variante visual para [`form::Checkbox`](crate::theme::form::Checkbox) en un formulario. /// /// Determina si el control se renderiza como una casilla de verificación estándar o como un -/// interruptor (*toggle switch*). La variante [`Switch`](Self::Switch) añade la clase `form-switch` -/// al contenedor y el atributo `role="switch"` al control para accesibilidad. +/// interruptor (*toggle switch*). #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum CheckboxKind { /// Casilla de verificación estándar. Es el tipo por defecto. #[default] Check, - /// Interruptor de encendido/apagado (*toggle switch*). + /// Interruptor de encendido/apagado. Switch, // TODO: Añadir variante `NativeSwitch` cuando el atributo `switch` de la propuesta WHATWG // (https://github.com/whatwg/html/issues/9546) sea estándar y tenga soporte amplio. Safari ya @@ -51,9 +50,9 @@ pub enum CheckboxKind { /// /// # Ejemplo /// -/// ```rust,no_run -/// use pagetop::prelude::*; -/// +/// ```rust +/// # use pagetop::prelude::*; +/// # use pagetop_bootsier::theme::*; /// // Correo electrónico con sugerencia semántica del navegador. /// let ac = form::Autocomplete::email(); /// @@ -88,7 +87,7 @@ impl Autocomplete { // --< Secciones >------------------------------------------------------------------------------ /// Construye `autocomplete` con un prefijo de sección y un token o tokens del - /// [`AutofillField`] indicado. + /// [`form::AutofillField`](AutofillField) indicado. /// /// Genera `autocomplete="section- "`. Si `name` no es ASCII o contiene espacios, /// se ignora la sección y se genera sólo el token indicado. @@ -244,9 +243,8 @@ impl fmt::Display for Autocomplete { /// /// # Ejemplo /// -/// ```rust,no_run -/// use pagetop::prelude::*; -/// +/// ```rust +/// # use pagetop_bootsier::theme::*; /// let ac = form::Autocomplete::token(form::AutofillField::Username); /// let ac = form::Autocomplete::shipping(form::AutofillField::StreetAddress); /// let ac = form::Autocomplete::section("job", form::AutofillField::Email); @@ -449,7 +447,7 @@ impl AutofillField { // **< Method >************************************************************************************* -/// Método HTTP usado por un [`Form`](super::Form) para el envío de los datos. +/// Método HTTP usado por un formulario ([`Form`](crate::theme::Form)) para el envío de los datos. /// /// En HTML, el atributo `method` del formulario indica **cómo** se envían los datos: /// diff --git a/src/base/component/form/radio.rs b/extensions/pagetop-bootsier/src/theme/form/radio.rs similarity index 85% rename from src/base/component/form/radio.rs rename to extensions/pagetop-bootsier/src/theme/form/radio.rs index f06dd387..d79c4f44 100644 --- a/src/base/component/form/radio.rs +++ b/extensions/pagetop-bootsier/src/theme/form/radio.rs @@ -1,10 +1,12 @@ //! Definiciones para crear grupos de botones de opción (*radio buttons*). -use crate::prelude::*; +use pagetop::prelude::*; + +use crate::LOCALES_BOOTSIER; // **< Item >*************************************************************************************** -/// Botón de opción individual de un [`Field`]. +/// Botón de opción individual de un [`form::radio::Field`](Field). /// /// Representa cada opción de un grupo de opciones exclusivas entre sí, con un valor (el que se /// envía al servidor), una etiqueta localizable visible y puede marcarse como seleccionada o @@ -12,9 +14,9 @@ use crate::prelude::*; /// /// # Ejemplo /// -/// ```rust,no_run -/// use pagetop::prelude::*; -/// +/// ```rust +/// # use pagetop::prelude::*; +/// # use pagetop_bootsier::theme::*; /// let item = form::radio::Item::new("monthly", L10n::n("Monthly")).with_checked(true); /// ``` #[derive(AutoDefault, Clone, Debug, Getters)] @@ -62,8 +64,8 @@ impl Item { /// Componente para crear un **grupo de botones de opción**. /// -/// Renderiza un grupo de botones de opción [`form::radio::Item`] que comparten el mismo atributo -/// `name`, por lo que sólo puede seleccionarse uno a la vez. Las opciones se añaden con +/// Renderiza un grupo de botones de opción [`form::radio::Item`](Item) que comparten el mismo +/// atributo `name`, por lo que sólo puede seleccionarse uno a la vez. Las opciones se añaden con /// [`with_item()`](Field::with_item). /// /// Si se activa el modo en línea [`with_inline()`](Field::with_inline), los botones se disponen @@ -72,9 +74,9 @@ impl Item { /// /// # Ejemplo /// -/// ```rust,no_run -/// use pagetop::prelude::*; -/// +/// ```rust +/// # use pagetop::prelude::*; +/// # use pagetop_bootsier::theme::*; /// let plan = form::radio::Field::new() /// .with_name("plan") /// .with_label(L10n::n("Subscription plan")) @@ -94,8 +96,10 @@ impl Item { /// ``` #[derive(AutoDefault, Clone, Debug, Getters)] pub struct Field { - /// Devuelve identificador, clases CSS y atributos HTML del componente. - props: Props, + #[getters(skip)] + id: AttrId, + /// Devuelve las clases CSS del contenedor del grupo. + classes: Classes, /// Devuelve el nombre compartido por todos los botones de opción del grupo. name: AttrName, /// Devuelve la etiqueta del grupo. @@ -118,38 +122,28 @@ impl Component for Field { } fn id(&self) -> Option { - self.props.get_id() + self.id.get() } - fn setup(&mut self, cx: &Context) { - // Asegura `name` e `id`. - // Si falta uno se deriva del otro; si faltan ambos se genera un valor único. + fn setup(&mut self, _cx: &Context) { + self.alter_classes(ClassesOp::Prepend, "form-field form-field-radios"); + } + + fn prepare(&self, cx: &mut Context) -> Result { let name = self .name() .get() .unwrap_or_else(|| cx.required_id::(self.id(), 3)); - self.alter_name(&name); let container_id = self.id().unwrap_or_else(|| util::join!("edit-", &name)); - self.alter_prop(PropsOp::ensure_id(container_id)); - - // Clases CSS del contenedor del grupo de opciones. - self.alter_prop(PropsOp::prepend_classes("form-field form-field-radios")); - } - - fn prepare(&self, cx: &mut Context) -> Result { - // En `setup()` se garantiza que `name` e `id` están definidos antes del renderizado. - let name = self.name().get().unwrap(); - let container_id = self.id().unwrap(); - Ok(html! { - div (self.props()) { + div id=(&container_id) class=[self.classes().get()] { @if let Some(label) = self.label().lookup(cx) { label class="form-label" { (label) @if *self.required() { span class="form-required" - title=(L10n::l("field_required").using(cx)) + title=(L10n::t("input_required", &LOCALES_BOOTSIER).using(cx)) { "*" } @@ -196,17 +190,17 @@ impl Component for Field { impl Field { // **< Field BUILDER >************************************************************************** - /// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`. + /// Establece el identificador único (`id`) del grupo de opciones. #[builder_fn] - pub fn with_id(mut self, id: impl Into) -> Self { - self.props.alter_id(id); + pub fn with_id(mut self, id: impl AsRef) -> Self { + self.id.alter_id(id); self } - /// Modifica identificador, clases CSS o atributos HTML del componente. + /// Modifica la lista de clases CSS aplicadas al contenedor del grupo de opciones. #[builder_fn] - pub fn with_prop(mut self, op: PropsOp) -> Self { - self.props.alter_prop(op); + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef) -> Self { + self.classes.alter_classes(op, classes); self } diff --git a/src/base/component/form/range.rs b/extensions/pagetop-bootsier/src/theme/form/range.rs similarity index 86% rename from src/base/component/form/range.rs rename to extensions/pagetop-bootsier/src/theme/form/range.rs index 386768d7..45d07acf 100644 --- a/src/base/component/form/range.rs +++ b/extensions/pagetop-bootsier/src/theme/form/range.rs @@ -1,4 +1,4 @@ -use crate::prelude::*; +use pagetop::prelude::*; /// Componente para crear un **control deslizante** de rango. /// @@ -8,9 +8,9 @@ use crate::prelude::*; /// /// # Ejemplo /// -/// ```rust,no_run -/// use pagetop::prelude::*; -/// +/// ```rust +/// # use pagetop::prelude::*; +/// # use pagetop_bootsier::theme::*; /// let volume = form::Range::new() /// .with_name("volume") /// .with_label(L10n::n("Volume")) @@ -31,8 +31,10 @@ use crate::prelude::*; /// ``` #[derive(AutoDefault, Clone, Debug, Getters)] pub struct Range { - /// Devuelve identificador, clases CSS y atributos HTML del componente. - props: Props, + #[getters(skip)] + id: AttrId, + /// Devuelve las clases CSS del contenedor del control deslizante. + classes: Classes, /// Devuelve el nombre del campo. name: AttrName, /// Devuelve la etiqueta del campo. @@ -59,26 +61,20 @@ impl Component for Range { } fn id(&self) -> Option { - self.props.get_id() + self.id.get() } fn setup(&mut self, _cx: &Context) { - if let Some(container_id) = self - .id() - .or_else(|| self.name().get().map(|n| util::join!("edit-", n))) - { - self.alter_prop(PropsOp::ensure_id(container_id)); - }; - - // Clases CSS del contenedor del control deslizante. - self.alter_prop(PropsOp::prepend_classes("form-field form-field-range")); + self.alter_classes(ClassesOp::Prepend, "form-field form-field-range"); } fn prepare(&self, cx: &mut Context) -> Result { - let container_id = self.id(); + let container_id = self + .id() + .or_else(|| self.name().get().map(|n| util::join!("edit-", n))); let range_id = container_id.as_deref().map(|id| util::join!(id, "-range")); Ok(html! { - div (self.props()) { + div id=[container_id.as_deref()] class=[self.classes().get()] { @if let Some(label) = self.label().lookup(cx) { label for=[range_id.as_deref()] class="form-label" { (label) } } @@ -104,17 +100,17 @@ impl Component for Range { impl Range { // **< Range BUILDER >************************************************************************** - /// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`. + /// Establece el identificador único (`id`) del contenedor del control deslizante. #[builder_fn] - pub fn with_id(mut self, id: impl Into) -> Self { - self.props.alter_id(id); + pub fn with_id(mut self, id: impl AsRef) -> Self { + self.id.alter_id(id); self } - /// Modifica identificador, clases CSS o atributos HTML del componente. + /// Modifica la lista de clases CSS aplicadas al contenedor del control deslizante. #[builder_fn] - pub fn with_prop(mut self, op: PropsOp) -> Self { - self.props.alter_prop(op); + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef) -> Self { + self.classes.alter_classes(op, classes); self } diff --git a/extensions/pagetop-bootsier/src/theme/form/select.rs b/extensions/pagetop-bootsier/src/theme/form/select.rs index 15b4f881..7d51e9c9 100644 --- a/extensions/pagetop-bootsier/src/theme/form/select.rs +++ b/extensions/pagetop-bootsier/src/theme/form/select.rs @@ -2,45 +2,458 @@ use pagetop::prelude::*; -pub use pagetop::base::component::form::select::{Entry, Field, Group, Item}; +use crate::LOCALES_BOOTSIER; +use crate::theme::form; -/// Extensión de Bootsier para [`form::select::Field`]. +// **< Item >*************************************************************************************** + +/// Elemento individual de [`form::select::Field`] o de [`form::select::Group`]. /// -/// Proporciona soporte para **etiquetas flotantes** (*floating label*). La etiqueta flotante se -/// superpone al control mientras no hay ninguna opción seleccionada y permanece flotante cuando hay -/// una selección activa. +/// Representa un elemento dentro de una lista de selección o de un grupo de elementos de la lista. +/// Cada elemento tiene un valor que se envía al servidor y una etiqueta localizable visible para el +/// usuario. /// -/// ```rust,no_run -/// # use pagetop::locale::L10n; -/// # use pagetop::core::component::Component; -/// use pagetop_bootsier::theme::*; +/// Puede marcarse como seleccionado por defecto con [`with_selected()`](Self::with_selected) o +/// deshabilitado de forma independiente al resto usando [`with_disabled()`](Self::with_disabled). /// -/// let language = form::select::Field::new() +/// # Ejemplo +/// +/// ```rust +/// # use pagetop::prelude::*; +/// # use pagetop_bootsier::theme::*; +/// let item = form::select::Item::new("es", L10n::n("Spanish")).with_selected(true); +/// ``` +#[derive(AutoDefault, Clone, Debug, Getters)] +pub struct Item { + /// Devuelve el valor enviado al servidor cuando se selecciona el elemento. + value: AttrValue, + /// Devuelve la etiqueta visible del elemento. + label: L10n, + /// Devuelve si el elemento debe aparecer seleccionado por defecto. + selected: bool, + /// Devuelve si el elemento está deshabilitado. + disabled: bool, +} + +impl Item { + /// Crea un nuevo elemento con el valor y la etiqueta indicados. + pub fn new(value: impl AsRef, label: L10n) -> Self { + Self { + value: AttrValue::new(value), + label, + selected: false, + disabled: false, + } + } + + // **< Item BUILDER >*************************************************************************** + + /// Establece si el elemento aparece seleccionado por defecto. + /// + /// En una lista de selección única, el navegador aplica la selección al último elemento marcado + /// si hay más de uno; mientras que en una lista múltiple se respetan todos los elementos + /// marcados. + pub fn with_selected(mut self, selected: bool) -> Self { + self.selected = selected; + self + } + + /// Establece si el elemento está deshabilitado. + pub fn with_disabled(mut self, disabled: bool) -> Self { + self.disabled = disabled; + self + } +} + +// **< Group >************************************************************************************** + +/// Grupo de elementos dentro de [`form::select::Field`]. +/// +/// Agrupa un conjunto de elementos dentro de una lista de selección con una etiqueta visible. El +/// grupo completo puede deshabilitarse en bloque con [`with_disabled()`](Self::with_disabled). +/// +/// # Ejemplo +/// +/// ```rust +/// # use pagetop::prelude::*; +/// # use pagetop_bootsier::theme::*; +/// let group = form::select::Group::new(L10n::n("Europe")) +/// .with_item(form::select::Item::new("es", L10n::n("Spanish"))) +/// .with_item(form::select::Item::new("fr", L10n::n("French"))); +/// ``` +#[derive(AutoDefault, Clone, Debug, Getters)] +pub struct Group { + /// Devuelve la etiqueta visible del grupo de elementos. + label: L10n, + /// Devuelve los elementos del grupo. + items: Vec, + /// Devuelve si el grupo de elementos está deshabilitado. + disabled: bool, +} + +impl Group { + /// Crea un nuevo grupo con la etiqueta indicada. + pub fn new(label: L10n) -> Self { + Self { + label, + ..Self::default() + } + } + + // **< Group BUILDER >************************************************************************** + + /// Añade un elemento al grupo. Los elementos se muestran en el orden en que se añaden. + pub fn with_item(mut self, item: Item) -> Self { + self.items.push(item); + self + } + + /// Establece si el grupo de elementos está deshabilitado en bloque. + pub fn with_disabled(mut self, disabled: bool) -> Self { + self.disabled = disabled; + self + } +} + +// **< Entry >************************************************************************************** + +/// Entrada de [`form::select::Field`] con un elemento o un grupo de elementos. +/// +/// Cada entrada se crea implícitamente cuando se usa [`form::select::Field::with_item()`] para +/// añadir un elemento individual o [`form::select::Field::with_group()`] para añadir un grupo de +/// elementos a una lista de selección. +/// +/// Con [`form::select::Field::entries()`] se pueden recuperar todas las entradas para su +/// renderizado. +#[derive(Clone, Debug)] +pub enum Entry { + /// Elemento individual. + Item(Item), + /// Grupo de elementos. + Group(Group), +} + +// **< Field >************************************************************************************** + +/// Componente para crear una **lista de selección**. +/// +/// Renderiza un campo para mostrar una lista de elementos con una etiqueta opcional. Permite elegir +/// uno, o más de uno si se activa la selección múltiple con +/// [`with_multiple()`](Self::with_multiple). +/// +/// Los elementos individuales se añaden con [`with_item()`](Self::with_item); los grupos de +/// elementos con un encabezado común se añaden con [`with_group()`](Self::with_group). Ambos +/// métodos pueden combinarse libremente. +/// +/// # Ejemplo +/// +/// ```rust +/// # use pagetop::prelude::*; +/// # use pagetop_bootsier::theme::*; +/// let idioma = form::select::Field::new() /// .with_name("language") /// .with_label(L10n::n("Language")) -/// .with_floating_label(true) /// .with_item(form::select::Item::new("", L10n::n("— Choose —")).with_selected(true)) -/// .with_item(form::select::Item::new("es", L10n::n("Spanish"))) -/// .with_item(form::select::Item::new("en", L10n::n("English"))); +/// .with_group( +/// form::select::Group::new(L10n::n("Europe")) +/// .with_item(form::select::Item::new("es", L10n::n("Spanish"))) +/// .with_item(form::select::Item::new("fr", L10n::n("French"))), +/// ) +/// .with_group( +/// form::select::Group::new(L10n::n("Americas")) +/// .with_item(form::select::Item::new("en", L10n::n("English"))) +/// .with_item(form::select::Item::new("pt", L10n::n("Portuguese"))), +/// ) +/// .with_required(true); /// ``` -pub trait SelectBootsier { +/// +/// Cuando el usuario selecciona un elemento y envía el formulario, el navegador transmite +/// `name=valor`. Si el campo es obligatorio el valor siempre estará presente y puede deserializarse +/// como `String`; si es opcional, usa `Option`: +/// +/// ```rust,ignore +/// #[derive(serde::Deserialize)] +/// struct FormData { +/// language: String, // Siempre presente (campo obligatorio). +/// // language: Option, // None si no se selecciona ninguna opción. +/// } +/// ``` +/// +/// Con selección múltiple activa, el navegador envía un valor por cada elemento marcado; si no se +/// marca ninguno, no envía nada. Usa `Vec` con `#[serde(default)]`: +/// +/// ```rust,ignore +/// #[derive(serde::Deserialize)] +/// struct FormData { +/// #[serde(default)] +/// interests: Vec, // p. ej. ["art", "tech"] o [] si no se marcó ninguna. +/// } +/// ``` +#[derive(AutoDefault, Clone, Debug, Getters)] +pub struct Field { + #[getters(skip)] + id: AttrId, + /// Devuelve las clases CSS del contenedor de la lista de selección. + classes: Classes, + /// Devuelve el nombre del campo. + name: AttrName, + /// Devuelve la etiqueta del campo. + label: Attr, + /// Devuelve si la etiqueta se muestra flotante sobre el campo. + floating_label: bool, + /// Devuelve el texto de ayuda del campo. + help_text: Attr, + /// Devuelve las entradas de la lista (elementos individuales y grupos de elementos). + entries: Vec, + /// Devuelve si la lista permite selección múltiple. + multiple: bool, + /// Devuelve el número de filas visibles de la lista de selección. + rows: Attr, + /// Devuelve la configuración de autocompletado del campo. + autocomplete: Attr, + /// Devuelve si la lista recibe el foco automáticamente al cargar la página. + autofocus: bool, + /// Devuelve si la selección de un elemento es obligatoria. + required: bool, + /// Devuelve si la lista está deshabilitada. + disabled: bool, +} + +impl Component for Field { + fn new() -> Self { + Self::default() + } + + fn id(&self) -> Option { + self.id.get() + } + + fn setup(&mut self, _cx: &Context) { + if *self.floating_label() { + self.multiple = false; + self.rows.alter_opt(None::); + self.alter_classes(ClassesOp::Prepend, "form-floating"); + } + self.alter_classes(ClassesOp::Prepend, "form-field form-field-select"); + } + + fn prepare(&self, cx: &mut Context) -> Result { + let container_id = self + .id() + .or_else(|| self.name().get().map(|n| util::join!("edit-", n))); + let select_id = container_id.as_deref().map(|id| util::join!(id, "-select")); + let label = match self.label().lookup(cx) { + Some(text) => html! { + label for=[select_id.as_deref()] class="form-label" { + (text) + @if *self.required() { + span + class="form-required" + title=(L10n::t("input_required", &LOCALES_BOOTSIER).using(cx)) + { + "*" + } + } + } + }, + None => html! {}, + }; + Ok(html! { + div id=[container_id.as_deref()] class=[self.classes().get()] { + @if !*self.floating_label() { + (label) + } + select + id=[select_id.as_deref()] + class="form-select" + name=[self.name().get()] + multiple[*self.multiple()] + size=[self.rows().get()] + autocomplete=[self.autocomplete().get()] + autofocus[*self.autofocus()] + required[*self.required()] + disabled[*self.disabled()] + { + @for entry in self.entries() { + @match entry { + Entry::Item(opt) => { + option + value=(opt.value().as_str().unwrap_or("")) + selected[*opt.selected()] + disabled[*opt.disabled()] + { + (opt.label().using(cx)) + } + } + Entry::Group(group) => { + optgroup + label=(group.label().using(cx)) + disabled[*group.disabled()] + { + @for opt in group.items() { + option + value=(opt.value().as_str().unwrap_or("")) + selected[*opt.selected()] + disabled[*opt.disabled()] + { + (opt.label().using(cx)) + } + } + } + } + } + } + } + @if *self.floating_label() { + (label) + } + @if let Some(description) = self.help_text().lookup(cx) { + div class="form-text" { (description) } + } + } + }) + } +} + +impl Field { + // **< Field BUILDER >*************************************************************************** + + /// Establece el identificador único (`id`) del contenedor del campo. + #[builder_fn] + pub fn with_id(mut self, id: impl AsRef) -> Self { + self.id.alter_id(id); + self + } + + /// Modifica la lista de clases CSS aplicadas al contenedor de la lista de selección. + #[builder_fn] + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef) -> Self { + self.classes.alter_classes(op, classes); + self + } + + /// Establece el nombre del campo (atributo `name`). + /// + /// Sin él, el valor seleccionado no se transmite al servidor al enviar el formulario. Para + /// deserializar el campo en el servidor es recomendable establecer un `name` explícito. + #[builder_fn] + pub fn with_name(mut self, name: impl AsRef) -> Self { + self.name.alter_name(name); + self + } + + /// Establece o elimina la etiqueta visible del campo (basta pasar `None` para quitarla). + #[builder_fn] + pub fn with_label(mut self, label: impl Into>) -> Self { + self.label.alter_opt(label.into()); + self + } + /// Establece si la etiqueta se muestra flotante sobre el campo. /// /// Cuando está activo, la etiqueta se superpone al control y permanece flotante siempre que /// haya una opción visible. /// - /// Si se usa la etiqueta flotante, se anulan los valores establecidos con - /// [`with_multiple()`](form::select::Field::with_multiple) y - /// [`with_rows()`](form::select::Field::with_rows) antes del renderizado. - fn with_floating_label(self, floating: bool) -> Self; -} + /// Si se usa la etiqueta flotante, el [`setup()`](Self::setup) del componente anulará los + /// valores establecidos con [`with_multiple()`](Self::with_multiple) y + /// [`with_rows()`](Self::with_rows) antes del renderizado. + #[builder_fn] + pub fn with_floating_label(mut self, floating_label: bool) -> Self { + self.floating_label = floating_label; + self + } -impl SelectBootsier for Field { - fn with_floating_label(self, floating: bool) -> Self { - if floating { - self.with_prop(PropsOp::add_classes("form-floating")) - } else { - self.with_prop(PropsOp::remove_classes("form-floating")) - } + /// Establece o elimina el texto de ayuda del campo (basta pasar `None` para quitarlo). + #[builder_fn] + pub fn with_help_text(mut self, help_text: impl Into>) -> Self { + self.help_text.alter_opt(help_text.into()); + self + } + + /// Añade un elemento individual a la lista de selección. + /// + /// Los elementos y grupos se muestran en el orden en que se añaden. + #[builder_fn] + pub fn with_item(mut self, item: Item) -> Self { + self.entries.push(Entry::Item(item)); + self + } + + /// Añade un grupo de elementos a la lista de selección. + /// + /// Los elementos y grupos se muestran en el orden en que se añaden. + #[builder_fn] + pub fn with_group(mut self, group: Group) -> Self { + self.entries.push(Entry::Group(group)); + self + } + + /// Establece si el control permite seleccionar varios elementos. + /// + /// Al activar la selección múltiple, se muestra una lista en lugar de un desplegable. Se + /// recomienda combinar con [`with_rows()`](Self::with_rows) para controlar el número de filas + /// visibles. + /// + /// Para un número reducido de elementos con etiquetas descriptivas considera usar + /// [`form::check::Field`] en su lugar, ofrece una presentación más clara y es más accesible en + /// pantallas pequeñas. + /// + /// Se anula si se usa con [`with_floating_label(true)`](Self::with_floating_label). + #[builder_fn] + pub fn with_multiple(mut self, multiple: bool) -> Self { + self.multiple = multiple; + self + } + + /// Establece el número de filas visibles de la lista de selección. + /// + /// Cuando se establece un valor mayor que 1, el control se muestra como lista en lugar de + /// desplegable, tanto en modo simple como múltiple. Con `None` se omite el atributo y presenta + /// el control como desplegable (comportamiento por defecto). + /// + /// Es especialmente útil con selección múltiple para controlar el número de filas visibles sin + /// necesidad de recurrir al desplazamiento. + /// + /// Se anula si se usa con [`with_floating_label(true)`](Self::with_floating_label). + #[builder_fn] + pub fn with_rows(mut self, rows: Option) -> Self { + self.rows.alter_opt(rows); + self + } + + /// Establece la configuración de autocompletado del campo. + /// + /// Permite al navegador rellenar automáticamente el elemento seleccionado en listas de países + /// (`"country"`), idiomas (`"language"`), sexo (`"sex"`) u otros campos con valores + /// predefinidos. En listas de selección múltiples no es útil en la práctica, ya que los + /// navegadores no gestionan selecciones múltiples con autocompletado. + /// + /// Usa los métodos de [`form::Autocomplete`] para los valores más habituales. Pasa `None` para + /// omitir el atributo. + #[builder_fn] + pub fn with_autocomplete(mut self, autocomplete: Option) -> Self { + self.autocomplete.alter_opt(autocomplete); + self + } + + /// Establece si el campo recibe el foco automáticamente al cargar la página. + #[builder_fn] + pub fn with_autofocus(mut self, autofocus: bool) -> Self { + self.autofocus = autofocus; + self + } + + /// Establece si el campo es obligatorio. + #[builder_fn] + pub fn with_required(mut self, required: bool) -> Self { + self.required = required; + self + } + + /// Establece si el campo está deshabilitado. + #[builder_fn] + pub fn with_disabled(mut self, disabled: bool) -> Self { + self.disabled = disabled; + self } } diff --git a/extensions/pagetop-bootsier/src/theme/form/textarea.rs b/extensions/pagetop-bootsier/src/theme/form/textarea.rs index 7cf9c045..81b32783 100644 --- a/extensions/pagetop-bootsier/src/theme/form/textarea.rs +++ b/extensions/pagetop-bootsier/src/theme/form/textarea.rs @@ -1,44 +1,290 @@ -//! Definiciones para crear áreas de texto en formularios. - use pagetop::prelude::*; -pub use pagetop::base::component::form::Textarea; +use crate::LOCALES_BOOTSIER; +use crate::theme::form; -/// Extensión de Bootsier para [`form::Textarea`]. +/// Componente para crear un **área de texto** de formulario. /// -/// Proporciona soporte para **etiquetas flotantes** (*floating label*). La etiqueta flotante se -/// superpone al control mientras no hay ninguna opción seleccionada y permanece flotante cuando hay -/// una selección activa. +/// Permite escribir en un área de texto de más de una línea, con una etiqueta opcional y atributos +/// como el número de filas a presentar, longitud mínima (`minlength`) y máxima (`maxlength`), texto +/// indicativo (`placeholder`) o autocompletado (`autocomplete`). /// -/// ```rust,no_run -/// # use pagetop::locale::L10n; -/// # use pagetop::core::component::Component; -/// use pagetop_bootsier::theme::*; +/// # Ejemplo /// -/// let comentario = form::Textarea::new() -/// .with_name("comment") -/// .with_label(L10n::n("Comment")) +/// ```rust +/// # use pagetop::prelude::*; +/// # use pagetop_bootsier::theme::*; +/// let descripcion = form::Textarea::new() +/// .with_name("description") +/// .with_label(L10n::n("Description")) +/// .with_rows(Some(8)) +/// .with_maxlength(Some(500)) /// .with_placeholder(L10n::n("Write here...")) -/// .with_floating_label(true); +/// .with_required(true); /// ``` -pub trait TextareaBootsier { +/// +/// Al enviar el formulario el navegador transmite `name=valor`. Un área de texto siempre envía su +/// valor, incluso si está vacía. En el servidor se deserializa como `String`: +/// +/// ```rust,ignore +/// #[derive(serde::Deserialize)] +/// struct FormData { +/// description: String, // Siempre presente; cadena vacía si el usuario no escribió nada. +/// } +/// ``` +#[derive(AutoDefault, Clone, Debug, Getters)] +pub struct Textarea { + #[getters(skip)] + id: AttrId, + /// Devuelve las clases CSS del contenedor del área de texto. + classes: Classes, + /// Devuelve el nombre del campo. + name: AttrName, + /// Devuelve el valor inicial del área de texto. + value: AttrValue, + /// Devuelve la etiqueta del campo. + label: Attr, + /// Devuelve si la etiqueta se muestra flotante sobre el campo. + floating_label: bool, + /// Devuelve el texto de ayuda del campo. + help_text: Attr, + /// Devuelve el número de filas visibles del área de texto. + rows: Attr, + /// Devuelve la longitud mínima permitida en caracteres. + minlength: Attr, + /// Devuelve la longitud máxima permitida en caracteres. + maxlength: Attr, + /// Devuelve el texto indicativo del área de texto. + placeholder: Attr, + /// Devuelve la configuración de autocompletado del campo. + autocomplete: Attr, + /// Devuelve si el campo recibe el foco automáticamente al cargar la página. + autofocus: bool, + /// Devuelve si el campo es de sólo lectura. + readonly: bool, + /// Devuelve si el campo es obligatorio. + required: bool, + /// Devuelve si el campo está deshabilitado. + disabled: bool, +} + +impl Component for Textarea { + fn new() -> Self { + Self::default() + } + + fn id(&self) -> Option { + self.id.get() + } + + fn setup(&mut self, _cx: &Context) { + if *self.floating_label() { + self.rows.alter_opt(None::); + self.alter_classes(ClassesOp::Prepend, "form-floating"); + } + self.alter_classes(ClassesOp::Prepend, "form-field form-field-textarea"); + } + + fn prepare(&self, cx: &mut Context) -> Result { + let container_id = self + .id() + .or_else(|| self.name().get().map(|n| util::join!("edit-", n))); + let textarea_id = container_id + .as_deref() + .map(|id| util::join!(id, "-textarea")); + // La etiqueta flotante requiere el atributo `placeholder` para detectar cuándo el campo + // está vacío y animar la etiqueta; si no está definido, se fuerza `placeholder=""`. + let placeholder = if *self.floating_label() { + Some(self.placeholder().lookup(cx).unwrap_or_default()) + } else { + self.placeholder().lookup(cx) + }; + let label = match self.label().lookup(cx) { + Some(text) => html! { + label for=[textarea_id.as_deref()] class="form-label" { + (text) + @if *self.required() { + span + class="form-required" + title=(L10n::t("input_required", &LOCALES_BOOTSIER).using(cx)) + { + "*" + } + } + } + }, + None => html! {}, + }; + Ok(html! { + div id=[container_id.as_deref()] class=[self.classes().get()] { + @if !*self.floating_label() { + (label) + } + textarea + id=[textarea_id.as_deref()] + class="form-control" + name=[self.name().get()] + rows=[self.rows().get()] + minlength=[self.minlength().get()] + maxlength=[self.maxlength().get()] + placeholder=[placeholder] + autocomplete=[self.autocomplete().get()] + autofocus[*self.autofocus()] + readonly[*self.readonly()] + required[*self.required()] + disabled[*self.disabled()] + { + @if let Some(value) = self.value().get() { + (value) + } + } + @if *self.floating_label() { + (label) + } + @if let Some(description) = self.help_text().lookup(cx) { + div class="form-text" { (description) } + } + } + }) + } +} + +impl Textarea { + // **< Textarea BUILDER >*********************************************************************** + + /// Establece el identificador único (`id`) del contenedor del campo. + #[builder_fn] + pub fn with_id(mut self, id: impl AsRef) -> Self { + self.id.alter_id(id); + self + } + + /// Modifica la lista de clases CSS aplicadas al contenedor del campo. + #[builder_fn] + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef) -> Self { + self.classes.alter_classes(op, classes); + self + } + + /// Establece el nombre del campo (atributo `name`). + /// + /// Sin él, el valor del campo no se transmite al servidor al enviar el formulario. Para + /// deserializar el campo en el servidor es recomendable establecer un `name` explícito. + #[builder_fn] + pub fn with_name(mut self, name: impl AsRef) -> Self { + self.name.alter_name(name); + self + } + + /// Establece el valor inicial del área de texto. + #[builder_fn] + pub fn with_value(mut self, value: impl AsRef) -> Self { + self.value.alter_str(value); + self + } + + /// Establece o elimina la etiqueta visible del campo (basta pasar `None` para quitarla). + #[builder_fn] + pub fn with_label(mut self, label: impl Into>) -> Self { + self.label.alter_opt(label.into()); + self + } + /// Establece si la etiqueta se muestra flotante sobre el campo. /// /// Cuando está activo, la etiqueta se superpone al área de texto y asciende al enfocarlo o - /// cuando tiene contenido. Requiere que el campo tenga un atributo `placeholder` definido; - /// si no se especifica, se fuerza `placeholder=""` antes del renderizado. + /// cuando tiene contenido. /// - /// Si se usa la etiqueta flotante, se anula el valor establecido con - /// [`with_rows()`](form::Textarea::with_rows) antes del renderizado. - fn with_floating_label(self, floating: bool) -> Self; -} + /// Si se usa la etiqueta flotante, el [`setup()`](Self::setup) del componente anulará el valor + /// establecido con [`with_rows()`](Self::with_rows) antes del renderizado. Si es necesario, se + /// puede controlar la altura con estilos aplicados al componente. + #[builder_fn] + pub fn with_floating_label(mut self, floating_label: bool) -> Self { + self.floating_label = floating_label; + self + } -impl TextareaBootsier for Textarea { - fn with_floating_label(self, floating: bool) -> Self { - if floating { - self.with_prop(PropsOp::add_classes("form-floating")) - } else { - self.with_prop(PropsOp::remove_classes("form-floating")) - } + /// Establece o elimina el texto de ayuda del campo (basta pasar `None` para quitarlo). + #[builder_fn] + pub fn with_help_text(mut self, help_text: impl Into>) -> Self { + self.help_text.alter_opt(help_text.into()); + self + } + + /// Establece el número de filas visibles del área de texto. + /// + /// Sin valor o pasando `None`, el área muestra su altura predeterminada, dos filas según el + /// estándar. + /// + /// Se anula si se usa con [`with_floating_label(true)`](Self::with_floating_label). + #[builder_fn] + pub fn with_rows(mut self, rows: Option) -> Self { + self.rows.alter_opt(rows); + self + } + + /// Establece la longitud mínima permitida en caracteres. + #[builder_fn] + pub fn with_minlength(mut self, minlength: Option) -> Self { + self.minlength.alter_opt(minlength); + self + } + + /// Establece la longitud máxima permitida en caracteres. + #[builder_fn] + pub fn with_maxlength(mut self, maxlength: Option) -> Self { + self.maxlength.alter_opt(maxlength); + self + } + + /// Establece o elimina el texto indicativo del área de texto (`None` para quitarlo). + /// + /// Este texto aparece en el área de texto y desaparece en cuanto el usuario empieza a escribir. + /// Al ser texto visible para el usuario se acepta [`L10n`] para poder localizarlo. + #[builder_fn] + pub fn with_placeholder(mut self, placeholder: impl Into>) -> Self { + self.placeholder.alter_opt(placeholder.into()); + self + } + + /// Establece la configuración de autocompletado del campo. + /// + /// Permite al navegador sugerir o rellenar automáticamente el contenido del área de texto + /// con valores guardados. Es especialmente útil en áreas con contenido semántico predefinido. + /// + /// Usa los métodos de [`form::Autocomplete`] para los valores más habituales. Pasa `None` para + /// omitir el atributo. + #[builder_fn] + pub fn with_autocomplete(mut self, autocomplete: Option) -> Self { + self.autocomplete.alter_opt(autocomplete); + self + } + + /// Establece si el campo recibe el foco automáticamente al cargar la página. + #[builder_fn] + pub fn with_autofocus(mut self, autofocus: bool) -> Self { + self.autofocus = autofocus; + self + } + + /// Establece si el campo es de sólo lectura. + #[builder_fn] + pub fn with_readonly(mut self, readonly: bool) -> Self { + self.readonly = readonly; + self + } + + /// Establece si el campo es obligatorio. + #[builder_fn] + pub fn with_required(mut self, required: bool) -> Self { + self.required = required; + self + } + + /// Establece si el campo está deshabilitado. + #[builder_fn] + pub fn with_disabled(mut self, disabled: bool) -> Self { + self.disabled = disabled; + self } } diff --git a/extensions/pagetop-bootsier/src/theme/icon.rs b/extensions/pagetop-bootsier/src/theme/icon.rs index 970a6cd4..9ef6aeee 100644 --- a/extensions/pagetop-bootsier/src/theme/icon.rs +++ b/extensions/pagetop-bootsier/src/theme/icon.rs @@ -15,8 +15,8 @@ pub enum IconKind { #[derive(AutoDefault, Clone, Debug, Getters)] pub struct Icon { - /// Devuelve los atributos HTML y clases CSS del componente. - props: Props, + /// Devuelve las clases CSS asociadas al icono. + classes: Classes, icon_kind: IconKind, aria_label: AttrL10n, } @@ -26,16 +26,12 @@ impl Component for Icon { Self::default() } - fn id(&self) -> Option { - self.props.get_id() - } - fn setup(&mut self, _cx: &Context) { if !matches!(self.icon_kind(), IconKind::None) { - self.alter_prop(PropsOp::prepend_classes("icon")); + self.alter_classes(ClassesOp::Prepend, "icon"); } if let IconKind::Font(font_size) = self.icon_kind() { - self.alter_prop(PropsOp::add_classes(font_size.as_str())); + self.alter_classes(ClassesOp::Add, font_size.as_str()); } } @@ -47,7 +43,7 @@ impl Component for Icon { let has_label = aria_label.is_some(); html! { i - (self.props()) + class=[self.classes().get()] role=[has_label.then_some("img")] aria-label=[aria_label] aria-hidden=[(!has_label).then_some("true")] @@ -64,7 +60,7 @@ impl Component for Icon { viewBox=(viewbox) fill="currentColor" focusable="false" - (self.props()) + class=[self.classes().get()] role=[has_label.then_some("img")] aria-label=[aria_label] aria-hidden=[(!has_label).then_some("true")] @@ -102,17 +98,10 @@ impl Icon { // **< Icon BUILDER >*************************************************************************** - /// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`. + /// Modifica la lista de clases CSS aplicadas al icono. #[builder_fn] - pub fn with_id(mut self, id: impl Into) -> Self { - self.props.alter_id(id); - self - } - - /// Modifica identificador, clases CSS o atributos HTML del componente. - #[builder_fn] - pub fn with_prop(mut self, op: PropsOp) -> Self { - self.props.alter_prop(op); + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef) -> Self { + self.classes.alter_value(op, classes); self } diff --git a/extensions/pagetop-bootsier/src/theme/image/component.rs b/extensions/pagetop-bootsier/src/theme/image/component.rs index 8ab379e7..df2c28a7 100644 --- a/extensions/pagetop-bootsier/src/theme/image/component.rs +++ b/extensions/pagetop-bootsier/src/theme/image/component.rs @@ -13,8 +13,10 @@ use crate::theme::*; /// - Aplicar el texto alternativo `alt` con **localización** mediante [`L10n`]. #[derive(AutoDefault, Clone, Debug, Getters)] pub struct Image { - /// Devuelve identificador, clases CSS y atributos HTML del componente. - props: Props, + #[getters(skip)] + id: AttrId, + /// Devuelve las clases CSS asociadas a la imagen. + classes: Classes, /// Devuelve las dimensiones de la imagen. size: image::Size, /// Devuelve el origen de la imagen. @@ -29,12 +31,11 @@ impl Component for Image { } fn id(&self) -> Option { - self.props.get_id() + self.id.get() } fn setup(&mut self, _cx: &Context) { - // Clases CSS por defecto para la imagen, según el origen seleccionado. - self.alter_prop(PropsOp::prepend_classes(self.source().to_class())); + self.alter_classes(ClassesOp::Prepend, self.source().to_class()); } fn prepare(&self, cx: &mut Context) -> Result { @@ -45,7 +46,8 @@ impl Component for Image { image::Source::Logo(logo) => { return Ok(html! { span - (self.props()) + id=[self.id()] + class=[self.classes().get()] style=[dimensions] role=[(!is_decorative).then_some("img")] aria-label=[(!is_decorative).then_some(alt_text)] @@ -63,7 +65,8 @@ impl Component for Image { img src=[source] alt=(alt_text) - (self.props()) + id=[self.id()] + class=[self.classes().get()] style=[dimensions] {} }) } @@ -77,22 +80,22 @@ impl Image { // **< Image BUILDER >************************************************************************** - /// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`. + /// Establece el identificador único (`id`) de la imagen. #[builder_fn] - pub fn with_id(mut self, id: impl Into) -> Self { - self.props.alter_id(id); + pub fn with_id(mut self, id: impl AsRef) -> Self { + self.id.alter_id(id); self } - /// Modifica identificador, clases CSS o atributos HTML del componente. + /// Modifica la lista de clases CSS aplicadas a la imagen. /// /// También acepta clases predefinidas para: /// /// - Establecer bordes ([`classes::Border`]). /// - Redondear las esquinas ([`classes::Rounded`]). #[builder_fn] - pub fn with_prop(mut self, op: PropsOp) -> Self { - self.props.alter_prop(op); + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef) -> Self { + self.classes.alter_classes(op, classes); self } diff --git a/extensions/pagetop-bootsier/src/theme/nav/component.rs b/extensions/pagetop-bootsier/src/theme/nav/component.rs index 7f792db3..aeb0447e 100644 --- a/extensions/pagetop-bootsier/src/theme/nav/component.rs +++ b/extensions/pagetop-bootsier/src/theme/nav/component.rs @@ -12,7 +12,7 @@ use crate::theme::*; /// /// # Ejemplo /// -/// ```rust,no_run +/// ```rust /// use pagetop::prelude::*; /// use pagetop_bootsier::theme::*; /// @@ -32,8 +32,10 @@ use crate::theme::*; /// ``` #[derive(AutoDefault, Clone, Debug, Getters)] pub struct Nav { - /// Devuelve identificador, clases CSS y atributos HTML del componente. - props: Props, + #[getters(skip)] + id: AttrId, + /// Devuelve las clases CSS asociadas al menú. + classes: Classes, /// Devuelve el estilo visual seleccionado. nav_kind: nav::Kind, /// Devuelve la distribución y orientación seleccionada. @@ -48,17 +50,16 @@ impl Component for Nav { } fn id(&self) -> Option { - self.props.get_id() + self.id.get() } fn setup(&mut self, _cx: &Context) { - // Clases CSS por defecto para el menú, según el estilo y la distribución seleccionados. - self.alter_prop(PropsOp::prepend_classes({ + self.alter_classes(ClassesOp::Prepend, { let mut classes = "nav".to_string(); self.nav_kind().push_class(&mut classes); self.nav_layout().push_class(&mut classes); classes - })); + }); } fn prepare(&self, cx: &mut Context) -> Result { @@ -68,7 +69,7 @@ impl Component for Nav { } Ok(html! { - ul (self.props()) { + ul id=[self.id()] class=[self.classes().get()] { (items) } }) @@ -93,17 +94,17 @@ impl Nav { // **< Nav BUILDER >**************************************************************************** - /// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`. + /// Establece el identificador único (`id`) del menú. #[builder_fn] - pub fn with_id(mut self, id: impl Into) -> Self { - self.props.alter_id(id); + pub fn with_id(mut self, id: impl AsRef) -> Self { + self.id.alter_id(id); self } - /// Modifica identificador, clases CSS o atributos HTML del componente. + /// Modifica la lista de clases CSS aplicadas al menú. #[builder_fn] - pub fn with_prop(mut self, op: PropsOp) -> Self { - self.props.alter_prop(op); + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef) -> Self { + self.classes.alter_classes(op, classes); self } diff --git a/extensions/pagetop-bootsier/src/theme/nav/item.rs b/extensions/pagetop-bootsier/src/theme/nav/item.rs index 9fbbc47d..43386baf 100644 --- a/extensions/pagetop-bootsier/src/theme/nav/item.rs +++ b/extensions/pagetop-bootsier/src/theme/nav/item.rs @@ -78,8 +78,10 @@ impl ItemKind { /// asociada, manteniendo una interfaz común para renderizar todos los elementos del menú. #[derive(AutoDefault, Clone, Debug, Getters)] pub struct Item { - /// Devuelve identificador, clases CSS y atributos HTML del componente. - props: Props, + #[getters(skip)] + id: AttrId, + /// Devuelve las clases CSS asociadas al elemento. + classes: Classes, /// Devuelve el tipo de elemento representado. item_kind: ItemKind, } @@ -90,11 +92,11 @@ impl Component for Item { } fn id(&self) -> Option { - self.props.get_id() + self.id.get() } fn setup(&mut self, _cx: &Context) { - self.alter_prop(PropsOp::prepend_classes(self.item_kind().to_class())); + self.alter_classes(ClassesOp::Prepend, self.item_kind().to_class()); } fn prepare(&self, cx: &mut Context) -> Result { @@ -102,7 +104,7 @@ impl Component for Item { ItemKind::Void => html! {}, ItemKind::Label(label) => html! { - li (self.props()) { + li id=[self.id()] class=[self.classes().get()] { span class="nav-link disabled" aria-disabled="true" { (label.using(cx)) } @@ -135,7 +137,7 @@ impl Component for Item { let aria_disabled = (*disabled).then_some("true"); html! { - li (self.props()) { + li id=[self.id()] class=[self.classes().get()] { a class=(classes) href=[href] @@ -151,7 +153,7 @@ impl Component for Item { } ItemKind::Html(html) => html! { - li (self.props()) { + li id=[self.id()] class=[self.classes().get()] { (html.render(cx)) } }, @@ -168,7 +170,7 @@ impl Component for Item { .unwrap_or_else(|| "Dropdown".to_string()) }); html! { - li (self.props()) { + li id=[self.id()] class=[self.classes().get()] { a class="nav-link dropdown-toggle" data-bs-toggle="dropdown" @@ -281,17 +283,17 @@ impl Item { // **< Item BUILDER >*************************************************************************** - /// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`. + /// Establece el identificador único (`id`) del elemento. #[builder_fn] - pub fn with_id(mut self, id: impl Into) -> Self { - self.props.alter_id(id); + pub fn with_id(mut self, id: impl AsRef) -> Self { + self.id.alter_id(id); self } - /// Modifica identificador, clases CSS o atributos HTML del componente. + /// Modifica la lista de clases CSS aplicadas al elemento. #[builder_fn] - pub fn with_prop(mut self, op: PropsOp) -> Self { - self.props.alter_prop(op); + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef) -> Self { + self.classes.alter_classes(op, classes); self } } diff --git a/extensions/pagetop-bootsier/src/theme/navbar/brand.rs b/extensions/pagetop-bootsier/src/theme/navbar/brand.rs index 511ea805..9e5082b6 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar/brand.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar/brand.rs @@ -13,6 +13,8 @@ use crate::theme::*; /// - El eslogan ([`with_slogan()`](Self::with_slogan)) es opcional; por defecto no tiene contenido. #[derive(AutoDefault, Clone, Debug, Getters)] pub struct Brand { + #[getters(skip)] + id: AttrId, /// Devuelve la imagen de marca (si la hay). image: Embed, /// Devuelve el título de la identidad de marca. @@ -30,6 +32,10 @@ impl Component for Brand { Self::default() } + fn id(&self) -> Option { + self.id.get() + } + fn prepare(&self, cx: &mut Context) -> Result { let image = self.image().render(cx); let title = self.title().using(cx); @@ -50,6 +56,13 @@ impl Component for Brand { impl Brand { // **< Brand BUILDER >************************************************************************** + /// Establece el identificador único (`id`) de la marca. + #[builder_fn] + pub fn with_id(mut self, id: impl AsRef) -> Self { + self.id.alter_id(id); + self + } + /// Asigna o quita la imagen de marca. Si se pasa `None`, no se mostrará. #[builder_fn] pub fn with_image(mut self, image: Option) -> Self { diff --git a/extensions/pagetop-bootsier/src/theme/navbar/component.rs b/extensions/pagetop-bootsier/src/theme/navbar/component.rs index 8709a857..096ec87a 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar/component.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar/component.rs @@ -18,7 +18,7 @@ const TOGGLE_OFFCANVAS: &str = "offcanvas"; /// /// Barra **simple**, sólo con un menú horizontal: /// -/// ```rust,no_run +/// ```rust /// use pagetop::prelude::*; /// use pagetop_bootsier::theme::*; /// @@ -33,7 +33,7 @@ const TOGGLE_OFFCANVAS: &str = "offcanvas"; /// /// Barra **colapsable**, con botón de despliegue y contenido en el desplegable cuando colapsa: /// -/// ```rust,no_run +/// ```rust /// # use pagetop::prelude::*; /// # use pagetop_bootsier::theme::*; /// let navbar = Navbar::simple_toggle() @@ -48,7 +48,7 @@ const TOGGLE_OFFCANVAS: &str = "offcanvas"; /// /// Barra con **marca de identidad a la izquierda** y menú a la derecha, típica de una cabecera: /// -/// ```rust,no_run +/// ```rust /// # use pagetop::prelude::*; /// # use pagetop_bootsier::theme::*; /// let brand = navbar::Brand::new() @@ -75,7 +75,7 @@ const TOGGLE_OFFCANVAS: &str = "offcanvas"; /// /// Barra con **botón de despliegue a la izquierda** y **marca de identidad a la derecha**: /// -/// ```rust,no_run +/// ```rust /// # use pagetop::prelude::*; /// # use pagetop_bootsier::theme::*; /// let brand = navbar::Brand::new() @@ -93,7 +93,7 @@ const TOGGLE_OFFCANVAS: &str = "offcanvas"; /// /// Barra con el **contenido en un *offcanvas***, ideal para dispositivos móviles o menús largos: /// -/// ```rust,no_run +/// ```rust /// # use pagetop::prelude::*; /// # use pagetop_bootsier::theme::*; /// let oc = Offcanvas::new() @@ -118,7 +118,7 @@ const TOGGLE_OFFCANVAS: &str = "offcanvas"; /// /// Barra **fija arriba**: /// -/// ```rust,no_run +/// ```rust /// # use pagetop::prelude::*; /// # use pagetop_bootsier::theme::*; /// let brand = navbar::Brand::new() @@ -136,8 +136,10 @@ const TOGGLE_OFFCANVAS: &str = "offcanvas"; /// ``` #[derive(AutoDefault, Clone, Debug, Getters)] pub struct Navbar { - /// Devuelve identificador, clases CSS y atributos HTML del componente. - props: Props, + #[getters(skip)] + id: AttrId, + /// Devuelve las clases CSS asociadas a la barra de navegación. + classes: Classes, /// Devuelve el punto de ruptura configurado. expand: BreakPoint, /// Devuelve la disposición configurada para la barra de navegación. @@ -154,20 +156,16 @@ impl Component for Navbar { } fn id(&self) -> Option { - self.props.get_id() + self.id.get() } - fn setup(&mut self, cx: &Context) { - // Asegura que la barra de navegación tiene un identificador único. - self.alter_prop(PropsOp::ensure_id(cx.build_id::(1))); - - // Clases CSS por defecto para la barra de navegación. - self.alter_prop(PropsOp::prepend_classes({ + fn setup(&mut self, _cx: &Context) { + self.alter_classes(ClassesOp::Prepend, { let mut classes = "navbar".to_string(); self.expand().push_class(&mut classes, "navbar-expand", ""); self.position().push_class(&mut classes); classes - })); + }); } fn prepare(&self, cx: &mut Context) -> Result { @@ -200,11 +198,11 @@ impl Component for Navbar { return Ok(html! {}); } - // `setup()` garantiza que habrá un `id` antes de renderizar. - let id = self.id().unwrap(); + // Asegura que la barra tiene un `id` para poder asociarlo al colapso/offcanvas. + let id = cx.required_id::(self.id(), 1); Ok(html! { - nav (self.props()) { + nav id=(&id) class=[self.classes().get()] { div class="container-fluid" { @match self.layout() { // Barra más sencilla: sólo contenido. @@ -337,22 +335,22 @@ impl Navbar { // **< Navbar BUILDER >************************************************************************* - /// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`. + /// Establece el identificador único (`id`) de la barra de navegación. #[builder_fn] - pub fn with_id(mut self, id: impl Into) -> Self { - self.props.alter_id(id); + pub fn with_id(mut self, id: impl AsRef) -> Self { + self.id.alter_id(id); self } - /// Modifica identificador, clases CSS o atributos HTML del componente. + /// Modifica la lista de clases CSS aplicadas a la barra de navegación. /// /// También acepta clases predefinidas para: /// /// - Modificar el color de fondo ([`classes::Background`]). /// - Definir la apariencia del texto ([`classes::Text`]). #[builder_fn] - pub fn with_prop(mut self, op: PropsOp) -> Self { - self.props.alter_prop(op); + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef) -> Self { + self.classes.alter_classes(op, classes); self } diff --git a/extensions/pagetop-bootsier/src/theme/navbar/item.rs b/extensions/pagetop-bootsier/src/theme/navbar/item.rs index 24cbd026..9b48adf1 100644 --- a/extensions/pagetop-bootsier/src/theme/navbar/item.rs +++ b/extensions/pagetop-bootsier/src/theme/navbar/item.rs @@ -41,7 +41,7 @@ impl Component for Item { fn setup(&mut self, _cx: &Context) { if let Self::Nav(nav) = self { if let Some(mut nav) = nav.get() { - nav.alter_prop(PropsOp::prepend_classes("navbar-nav")); + nav.alter_classes(ClassesOp::Prepend, "navbar-nav"); } } } @@ -57,7 +57,7 @@ impl Component for Item { return Ok(html! {}); } html! { - ul id=[nav.id()] (nav.props()) { + ul id=[nav.id()] class=[nav.classes().get()] { (items) } } diff --git a/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs b/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs index 1d9f2c48..a2c014b8 100644 --- a/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs +++ b/extensions/pagetop-bootsier/src/theme/offcanvas/component.rs @@ -23,7 +23,7 @@ use crate::theme::*; /// /// # Ejemplo /// -/// ```rust,no_run +/// ```rust /// use pagetop::prelude::*; /// use pagetop_bootsier::theme::*; /// @@ -43,8 +43,10 @@ use crate::theme::*; /// ``` #[derive(AutoDefault, Clone, Debug, Getters)] pub struct Offcanvas { - /// Devuelve identificador, clases CSS y atributos HTML del componente. - props: Props, + #[getters(skip)] + id: AttrId, + /// Devuelve las clases CSS asociadas al panel. + classes: Classes, /// Devuelve el título del panel. title: L10n, /// Devuelve el punto de ruptura configurado para cambiar el comportamiento del panel. @@ -67,21 +69,17 @@ impl Component for Offcanvas { } fn id(&self) -> Option { - self.props.get_id() + self.id.get() } - fn setup(&mut self, cx: &Context) { - // Asegura que el panel tiene un identificador único. - self.alter_prop(PropsOp::ensure_id(cx.build_id::(1))); - - // Clases CSS por defecto para el panel. - self.alter_prop(PropsOp::prepend_classes({ + fn setup(&mut self, _cx: &Context) { + self.alter_classes(ClassesOp::Prepend, { let mut classes = "offcanvas".to_string(); self.breakpoint().push_class(&mut classes, "offcanvas", ""); self.placement().push_class(&mut classes); self.visibility().push_class(&mut classes); classes - })); + }); } fn prepare(&self, cx: &mut Context) -> Result { @@ -92,17 +90,17 @@ impl Component for Offcanvas { impl Offcanvas { // **< Offcanvas BUILDER >********************************************************************** - /// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`. + /// Establece el identificador único (`id`) del panel. #[builder_fn] - pub fn with_id(mut self, id: impl Into) -> Self { - self.props.alter_id(id); + pub fn with_id(mut self, id: impl AsRef) -> Self { + self.id.alter_id(id); self } - /// Modifica identificador, clases CSS o atributos HTML del componente. + /// Modifica la lista de clases CSS aplicadas al panel. #[builder_fn] - pub fn with_prop(mut self, op: PropsOp) -> Self { - self.props.alter_prop(op); + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef) -> Self { + self.classes.alter_classes(op, classes); self } @@ -174,8 +172,7 @@ impl Offcanvas { return html! {}; } - // `setup()` garantiza que habrá un `id` antes de renderizar. - let id = self.id().unwrap(); + let id = cx.required_id::(self.id(), 1); let id_label = util::join!(id, "-label"); let id_target = util::join!("#", id); @@ -194,7 +191,8 @@ impl Offcanvas { html! { div - (self.props()) + id=(&id) + class=[self.classes().get()] tabindex="-1" data-bs-scroll=[body_scroll] data-bs-backdrop=[backdrop] diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/js/bootstrap.bundle.min.js b/extensions/pagetop-bootsier/static/js/bootstrap.bundle.min.js similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/js/bootstrap.bundle.min.js rename to extensions/pagetop-bootsier/static/js/bootstrap.bundle.min.js diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/js/bootstrap.bundle.min.js.map b/extensions/pagetop-bootsier/static/js/bootstrap.bundle.min.js.map similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/js/bootstrap.bundle.min.js.map rename to extensions/pagetop-bootsier/static/js/bootstrap.bundle.min.js.map diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/js/bootstrap.js b/extensions/pagetop-bootsier/static/js/bootstrap.js similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/js/bootstrap.js rename to extensions/pagetop-bootsier/static/js/bootstrap.js diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/js/bootstrap.js.map b/extensions/pagetop-bootsier/static/js/bootstrap.js.map similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/js/bootstrap.js.map rename to extensions/pagetop-bootsier/static/js/bootstrap.js.map diff --git a/extensions/pagetop-bootsier/assets/_bootsier-utilities.scss b/extensions/pagetop-bootsier/static/scss/_customs.scss similarity index 64% rename from extensions/pagetop-bootsier/assets/_bootsier-utilities.scss rename to extensions/pagetop-bootsier/static/scss/_customs.scss index 4192158e..6d0aa4e6 100644 --- a/extensions/pagetop-bootsier/assets/_bootsier-utilities.scss +++ b/extensions/pagetop-bootsier/static/scss/_customs.scss @@ -1,14 +1,80 @@ -// Bootstrap utility extensions. Imported just before utilities/api so variable changes take effect. - -// CSS Grid (Bootstrap 5): disable legacy grid classes and enable native CSS Grid. +// Enable CSS Grid $enable-grid-classes: false; $enable-cssgrid: true; -// Extend the $utilities map with additional classes. +// Opacity +.bg-opacity-0 { + --bs-bg-opacity: 0; +} + +.border-opacity-0 { + --bs-border-opacity: 0; +} + +.text-opacity-0 { + --bs-text-opacity: 0; +} +.text-opacity-10 { + --bs-text-opacity: 0.1; +} + +// FORMS + +// Required field indicator +.form-required { + color: var(--bs-danger); + margin: 0 0.25rem; +} + +// Form fields +.form-field { + margin-bottom: 1rem; + + &:last-child { + margin-bottom: 0; + } +} + +// Fieldset +fieldset { + position: relative; + background-color: var(--bs-body-bg); + border: var(--bs-border-width) solid var(--bs-border-color); + border-radius: var(--bs-border-radius); + padding: 2rem 1rem 1rem; + margin: 2rem 0 1rem; +} +fieldset > legend { + position: absolute; + top: 0; + left: 1rem; + transform: translateY(-50%); + background-color: var(--bs-body-bg); + border: var(--bs-border-width) solid var(--bs-border-color); + border-radius: var(--bs-border-radius); + padding: 0.125rem 0.75rem; + font-size: $font-size-sm; + line-height: 1.25; + width: fit-content; + max-width: 75%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.fieldset-description { + margin-bottom: 1rem; +} + +// Check buttons, gap between label and first inline check +.form-label + .form-check-inline { + margin-left: 1rem; +} + +// Extending utilities $utilities: map-merge( $utilities, ( - // Individual border widths per side. + // Individual border widths "border-top": ( property: border-top-width, class: border-top, @@ -29,7 +95,7 @@ $utilities: map-merge( class: border-start, values: $border-widths ), - // Individual corner radii. + // Individual rounded values "rounded-top-start": ( property: border-top-left-radius, class: rounded-top-start, @@ -90,18 +156,11 @@ $utilities: map-merge( pill: var(--#{$prefix}border-radius-pill) ) ), - // Opacity: add missing values (0 for bg/border/text; 10 for text) not in Bootstrap defaults. - "bg-opacity": map-merge( - map-get($utilities, "bg-opacity"), - (values: map-merge(map-get(map-get($utilities, "bg-opacity"), "values"), (0: 0))) - ), - "border-opacity": map-merge( - map-get($utilities, "border-opacity"), - (values: map-merge(map-get(map-get($utilities, "border-opacity"), "values"), (0: 0))) - ), - "text-opacity": map-merge( - map-get($utilities, "text-opacity"), - (values: map-merge(map-get(map-get($utilities, "text-opacity"), "values"), (0: 0, 10: .1))) - ), ) ); + +// Region Footer +.region-footer { + padding: .75rem 0 3rem; + text-align: center; +} diff --git a/extensions/pagetop-bootsier/static/scss/bootsier.scss b/extensions/pagetop-bootsier/static/scss/bootsier.scss new file mode 100644 index 00000000..0d52046a --- /dev/null +++ b/extensions/pagetop-bootsier/static/scss/bootsier.scss @@ -0,0 +1,55 @@ +@import "bootstrap-5.3.8/mixins/banner"; +@include bsBanner(""); + + +// scss-docs-start import-stack +// Configuration +@import "bootstrap-5.3.8/functions"; +@import "bootstrap-5.3.8/variables"; +@import "bootstrap-5.3.8/variables-dark"; +@import "bootstrap-5.3.8/maps"; +@import "bootstrap-5.3.8/mixins"; +@import "bootstrap-5.3.8/utilities"; + +// Layout & components +@import "bootstrap-5.3.8/root"; +@import "bootstrap-5.3.8/reboot"; +@import "bootstrap-5.3.8/type"; +@import "bootstrap-5.3.8/images"; +@import "bootstrap-5.3.8/containers"; +@import "bootstrap-5.3.8/grid"; +@import "bootstrap-5.3.8/tables"; +@import "bootstrap-5.3.8/forms"; +@import "bootstrap-5.3.8/buttons"; +@import "bootstrap-5.3.8/transitions"; +@import "bootstrap-5.3.8/dropdown"; +@import "bootstrap-5.3.8/button-group"; +@import "bootstrap-5.3.8/nav"; +@import "bootstrap-5.3.8/navbar"; +@import "bootstrap-5.3.8/card"; +@import "bootstrap-5.3.8/accordion"; +@import "bootstrap-5.3.8/breadcrumb"; +@import "bootstrap-5.3.8/pagination"; +@import "bootstrap-5.3.8/badge"; +@import "bootstrap-5.3.8/alert"; +@import "bootstrap-5.3.8/progress"; +@import "bootstrap-5.3.8/list-group"; +@import "bootstrap-5.3.8/close"; +@import "bootstrap-5.3.8/toasts"; +@import "bootstrap-5.3.8/modal"; +@import "bootstrap-5.3.8/tooltip"; +@import "bootstrap-5.3.8/popover"; +@import "bootstrap-5.3.8/carousel"; +@import "bootstrap-5.3.8/spinners"; +@import "bootstrap-5.3.8/offcanvas"; +@import "bootstrap-5.3.8/placeholders"; + +// Helpers +@import "bootstrap-5.3.8/helpers"; + +// Custom definitions +@import "customs"; + +// Utilities +@import "bootstrap-5.3.8/utilities/api"; +// scss-docs-end import-stack diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_accordion.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_accordion.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_accordion.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_accordion.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_alert.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_alert.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_alert.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_alert.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_badge.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_badge.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_badge.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_badge.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_breadcrumb.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_breadcrumb.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_breadcrumb.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_breadcrumb.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_button-group.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_button-group.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_button-group.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_button-group.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_buttons.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_buttons.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_buttons.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_buttons.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_card.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_card.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_card.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_card.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_carousel.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_carousel.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_carousel.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_carousel.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_close.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_close.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_close.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_close.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_containers.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_containers.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_containers.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_containers.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_dropdown.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_dropdown.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_dropdown.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_dropdown.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_forms.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_forms.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_forms.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_forms.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_functions.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_functions.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_functions.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_functions.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_grid.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_grid.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_grid.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_grid.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_helpers.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_helpers.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_helpers.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_helpers.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_images.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_images.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_images.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_images.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_list-group.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_list-group.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_list-group.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_list-group.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_maps.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_maps.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_maps.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_maps.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_mixins.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_mixins.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_mixins.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_mixins.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_modal.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_modal.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_modal.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_modal.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_nav.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_nav.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_nav.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_nav.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_navbar.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_navbar.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_navbar.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_navbar.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_offcanvas.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_offcanvas.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_offcanvas.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_offcanvas.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_pagination.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_pagination.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_pagination.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_pagination.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_placeholders.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_placeholders.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_placeholders.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_placeholders.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_popover.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_popover.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_popover.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_popover.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_progress.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_progress.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_progress.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_progress.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_reboot.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_reboot.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_reboot.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_reboot.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_root.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_root.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_root.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_root.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_spinners.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_spinners.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_spinners.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_spinners.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_tables.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_tables.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_tables.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_tables.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_toasts.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_toasts.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_toasts.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_toasts.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_tooltip.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_tooltip.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_tooltip.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_tooltip.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_transitions.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_transitions.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_transitions.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_transitions.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_type.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_type.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_type.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_type.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_utilities.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_utilities.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_utilities.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_utilities.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_variables-dark.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_variables-dark.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_variables-dark.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_variables-dark.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_variables.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_variables.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/_variables.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/_variables.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/bootstrap-grid.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/bootstrap-grid.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/bootstrap-grid.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/bootstrap-grid.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/bootstrap-reboot.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/bootstrap-reboot.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/bootstrap-reboot.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/bootstrap-reboot.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/bootstrap-utilities.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/bootstrap-utilities.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/bootstrap-utilities.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/bootstrap-utilities.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/bootstrap.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/bootstrap.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/bootstrap.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/bootstrap.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/forms/_floating-labels.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_floating-labels.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/forms/_floating-labels.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_floating-labels.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/forms/_form-check.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_form-check.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/forms/_form-check.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_form-check.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/forms/_form-control.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_form-control.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/forms/_form-control.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_form-control.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/forms/_form-range.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_form-range.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/forms/_form-range.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_form-range.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/forms/_form-select.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_form-select.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/forms/_form-select.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_form-select.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/forms/_form-text.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_form-text.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/forms/_form-text.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_form-text.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/forms/_input-group.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_input-group.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/forms/_input-group.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_input-group.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/forms/_labels.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_labels.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/forms/_labels.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_labels.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/forms/_validation.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_validation.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/forms/_validation.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/forms/_validation.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/helpers/_clearfix.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_clearfix.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/helpers/_clearfix.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_clearfix.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/helpers/_color-bg.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_color-bg.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/helpers/_color-bg.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_color-bg.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/helpers/_colored-links.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_colored-links.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/helpers/_colored-links.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_colored-links.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/helpers/_focus-ring.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_focus-ring.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/helpers/_focus-ring.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_focus-ring.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/helpers/_icon-link.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_icon-link.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/helpers/_icon-link.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_icon-link.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/helpers/_position.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_position.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/helpers/_position.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_position.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/helpers/_ratio.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_ratio.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/helpers/_ratio.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_ratio.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/helpers/_stacks.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_stacks.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/helpers/_stacks.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_stacks.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/helpers/_stretched-link.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_stretched-link.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/helpers/_stretched-link.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_stretched-link.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/helpers/_text-truncation.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_text-truncation.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/helpers/_text-truncation.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_text-truncation.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/helpers/_visually-hidden.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_visually-hidden.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/helpers/_visually-hidden.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_visually-hidden.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/helpers/_vr.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_vr.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/helpers/_vr.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/helpers/_vr.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_alert.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_alert.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_alert.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_alert.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_backdrop.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_backdrop.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_backdrop.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_backdrop.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_banner.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_banner.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_banner.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_banner.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_border-radius.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_border-radius.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_border-radius.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_border-radius.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_box-shadow.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_box-shadow.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_box-shadow.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_box-shadow.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_breakpoints.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_breakpoints.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_breakpoints.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_breakpoints.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_buttons.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_buttons.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_buttons.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_buttons.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_caret.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_caret.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_caret.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_caret.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_clearfix.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_clearfix.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_clearfix.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_clearfix.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_color-mode.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_color-mode.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_color-mode.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_color-mode.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_color-scheme.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_color-scheme.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_color-scheme.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_color-scheme.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_container.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_container.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_container.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_container.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_deprecate.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_deprecate.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_deprecate.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_deprecate.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_forms.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_forms.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_forms.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_forms.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_gradients.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_gradients.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_gradients.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_gradients.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_grid.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_grid.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_grid.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_grid.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_image.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_image.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_image.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_image.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_list-group.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_list-group.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_list-group.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_list-group.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_lists.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_lists.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_lists.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_lists.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_pagination.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_pagination.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_pagination.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_pagination.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_reset-text.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_reset-text.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_reset-text.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_reset-text.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_resize.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_resize.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_resize.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_resize.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_table-variants.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_table-variants.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_table-variants.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_table-variants.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_text-truncate.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_text-truncate.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_text-truncate.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_text-truncate.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_transition.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_transition.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_transition.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_transition.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_utilities.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_utilities.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_utilities.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_utilities.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_visually-hidden.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_visually-hidden.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/mixins/_visually-hidden.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/mixins/_visually-hidden.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/tests/jasmine.js b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/jasmine.js similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/tests/jasmine.js rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/jasmine.js diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/tests/mixins/_auto-import-of-variables-dark.test.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/mixins/_auto-import-of-variables-dark.test.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/tests/mixins/_auto-import-of-variables-dark.test.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/mixins/_auto-import-of-variables-dark.test.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/tests/mixins/_box-shadow.test.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/mixins/_box-shadow.test.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/tests/mixins/_box-shadow.test.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/mixins/_box-shadow.test.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/tests/mixins/_color-contrast.test.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/mixins/_color-contrast.test.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/tests/mixins/_color-contrast.test.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/mixins/_color-contrast.test.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/tests/mixins/_color-modes.test.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/mixins/_color-modes.test.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/tests/mixins/_color-modes.test.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/mixins/_color-modes.test.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/tests/mixins/_media-query-color-mode-full.test.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/mixins/_media-query-color-mode-full.test.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/tests/mixins/_media-query-color-mode-full.test.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/mixins/_media-query-color-mode-full.test.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/tests/mixins/_utilities.test.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/mixins/_utilities.test.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/tests/mixins/_utilities.test.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/mixins/_utilities.test.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/tests/sass-true/register.js b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/sass-true/register.js similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/tests/sass-true/register.js rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/sass-true/register.js diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/tests/sass-true/runner.js b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/sass-true/runner.js similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/tests/sass-true/runner.js rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/sass-true/runner.js diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/tests/utilities/_api.test.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/utilities/_api.test.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/tests/utilities/_api.test.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/tests/utilities/_api.test.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/utilities/_api.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/utilities/_api.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/utilities/_api.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/utilities/_api.scss diff --git a/extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/vendor/_rfs.scss b/extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/vendor/_rfs.scss similarity index 100% rename from extensions/pagetop-bootsier/assets/bootstrap-5.3.8/scss/vendor/_rfs.scss rename to extensions/pagetop-bootsier/static/scss/bootstrap-5.3.8/vendor/_rfs.scss diff --git a/extensions/pagetop-htmx/Cargo.toml b/extensions/pagetop-htmx/Cargo.toml deleted file mode 100644 index 5a1c254f..00000000 --- a/extensions/pagetop-htmx/Cargo.toml +++ /dev/null @@ -1,21 +0,0 @@ -[package] -name = "pagetop-htmx" -version = "0.1.0" - -description = """ - Extensión de PageTop que integra HTMX para enriquecer las páginas con interacciones dinámicas. -""" -categories = ["web-programming"] -keywords = ["pagetop", "htmx", "ajax", "ssr"] - -repository.workspace = true -homepage.workspace = true -edition.workspace = true -license.workspace = true -authors.workspace = true - -[dependencies] -pagetop.workspace = true - -[build-dependencies] -pagetop-build.workspace = true diff --git a/extensions/pagetop-htmx/LICENSE-APACHE b/extensions/pagetop-htmx/LICENSE-APACHE deleted file mode 100644 index 56cfd2db..00000000 --- a/extensions/pagetop-htmx/LICENSE-APACHE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2026 Manuel Cillero - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. \ No newline at end of file diff --git a/extensions/pagetop-htmx/LICENSE-MIT b/extensions/pagetop-htmx/LICENSE-MIT deleted file mode 100644 index 4227455e..00000000 --- a/extensions/pagetop-htmx/LICENSE-MIT +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2026 Manuel Cillero - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. \ No newline at end of file diff --git a/extensions/pagetop-htmx/README.md b/extensions/pagetop-htmx/README.md deleted file mode 100644 index 1bf1c623..00000000 --- a/extensions/pagetop-htmx/README.md +++ /dev/null @@ -1,88 +0,0 @@ -
- -

PageTop HTMX

- -

Extensión para PageTop que integra HTMX para enriquecer las páginas con interacciones dinámicas.

- -[![Doc API](https://img.shields.io/docsrs/pagetop-htmx?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-htmx) -[![Crates.io](https://img.shields.io/crates/v/pagetop-htmx.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-htmx) -[![Descargas](https://img.shields.io/crates/d/pagetop-htmx.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-htmx) -[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/extensions/pagetop-htmx#licencia) - -
- -## Sobre PageTop - -[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web -clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y -configurables, basadas en HTML, CSS y JavaScript. - -## Guía rápida - -**Añade la dependencia** a tu `Cargo.toml`: - -```toml -[dependencies] -pagetop-htmx = { ... } -``` - -**Declara la extensión** en tu aplicación (o extensión que la requiera). Recuerda que el orden en -`dependencies()` determina la prioridad relativa frente a las otras extensiones: - -```rust -use pagetop::prelude::*; - -struct MyApp; - -impl Extension for MyApp { - fn dependencies(&self) -> Vec { - vec![ - // ... - &pagetop_htmx::Htmx - // ... - ] - } -} -``` - -A partir de ese momento, todas las páginas de la aplicación incluirán automáticamente el script de -HTMX 2. Puedes usar los atributos `hx-*` directamente en tus componentes o el código HTML generado: - -```rust -use pagetop::prelude::*; - -async fn homepage(request: HttpRequest) -> Result { - Page::new(request) - .with_child(Html::with(|_| html! { - button hx-get="/api/hello" hx-target="#result" { - "Say hello" - } - div #result {} - })) - .render() -} -``` - -## Créditos - -Este *crate* integra la biblioteca [HTMX 2.0.10](https://htmx.org), distribuida bajo licencia -[BSD 2-Clause](https://github.com/bigskysoftware/htmx/blob/master/LICENSE). - -## Advertencia - -**PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su -ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos -hasta que se libere la versión **1.0.0**. - -## Licencia - -El código está disponible bajo una doble licencia: - - * **Licencia MIT** - ([LICENSE-MIT](LICENSE-MIT) o también https://opensource.org/licenses/MIT) - - * **Licencia Apache, Versión 2.0** - ([LICENSE-APACHE](LICENSE-APACHE) o también https://www.apache.org/licenses/LICENSE-2.0) - -Puedes elegir la licencia que prefieras. Este enfoque de doble licencia es el estándar de facto en -el ecosistema Rust. diff --git a/extensions/pagetop-htmx/assets/js/htmx.min.js b/extensions/pagetop-htmx/assets/js/htmx.min.js deleted file mode 100644 index 3b7ac1ac..00000000 --- a/extensions/pagetop-htmx/assets/js/htmx.min.js +++ /dev/null @@ -1 +0,0 @@ -var htmx=function(){"use strict";const Q={onLoad:null,process:null,on:null,off:null,trigger:null,ajax:null,find:null,findAll:null,closest:null,values:function(e,t){const n=dn(e,t||"post");return n.values},remove:null,addClass:null,removeClass:null,toggleClass:null,takeClass:null,swap:null,defineExtension:null,removeExtension:null,logAll:null,logNone:null,logger:null,config:{historyEnabled:true,historyCacheSize:10,refreshOnHistoryMiss:false,defaultSwapStyle:"innerHTML",defaultSwapDelay:0,defaultSettleDelay:20,includeIndicatorStyles:true,indicatorClass:"htmx-indicator",requestClass:"htmx-request",addedClass:"htmx-added",settlingClass:"htmx-settling",swappingClass:"htmx-swapping",allowEval:true,allowScriptTags:true,inlineScriptNonce:"",inlineStyleNonce:"",attributesToSettle:["class","style","width","height"],withCredentials:false,timeout:0,wsReconnectDelay:"full-jitter",wsBinaryType:"blob",disableSelector:"[hx-disable], [data-hx-disable]",scrollBehavior:"instant",defaultFocusScroll:false,getCacheBusterParam:false,globalViewTransitions:false,methodsThatUseUrlParams:["get","delete"],selfRequestsOnly:true,ignoreTitle:false,scrollIntoViewOnBoost:true,triggerSpecsCache:null,disableInheritance:false,responseHandling:[{code:"204",swap:false},{code:"[23]..",swap:true},{code:"[45]..",swap:false,error:true}],allowNestedOobSwaps:true,historyRestoreAsHxRequest:true,reportValidityOfForms:false},parseInterval:null,location:location,_:null,version:"2.0.10"};Q.onLoad=j;Q.process=Ft;Q.on=ye;Q.off=xe;Q.trigger=ae;Q.ajax=Nn;Q.find=f;Q.findAll=y;Q.closest=g;Q.remove=z;Q.addClass=w;Q.removeClass=b;Q.toggleClass=G;Q.takeClass=W;Q.swap=_e;Q.defineExtension=_n;Q.removeExtension=zn;Q.logAll=$;Q.logNone=_;Q.parseInterval=d;Q._=e;const n={addTriggerHandler:St,bodyContains:se,canAccessLocalStorage:U,findThisElement:we,filterValues:yn,swap:_e,hasAttribute:s,getAttributeValue:a,getClosestAttributeValue:ne,getClosestMatch:A,getExpressionVars:Rn,getHeaders:mn,getInputValues:dn,getInternalData:oe,getSwapSpecification:bn,getTriggerSpecs:st,getTarget:Se,makeFragment:P,mergeObjects:le,makeSettleInfo:Sn,oobSwap:He,querySelectorExt:ce,settleImmediately:Yt,shouldCancel:ht,triggerEvent:ae,triggerErrorEvent:fe,withExtensions:Vt};const de=["get","post","put","delete","patch"];const R=de.map(function(e){return"[hx-"+e+"], [data-hx-"+e+"]"}).join(", ");function d(e){if(e==undefined){return undefined}let t=NaN;if(e.slice(-2)=="ms"){t=parseFloat(e.slice(0,-2))}else if(e.slice(-1)=="s"){t=parseFloat(e.slice(0,-1))*1e3}else if(e.slice(-1)=="m"){t=parseFloat(e.slice(0,-1))*1e3*60}else{t=parseFloat(e)}return isNaN(t)?undefined:t}function ee(e,t){return e instanceof Element&&e.getAttribute(t)}function s(e,t){return!!e.hasAttribute&&(e.hasAttribute(t)||e.hasAttribute("data-"+t))}function a(e,t){return ee(e,t)||ee(e,"data-"+t)}function c(e){const t=e.parentElement;if(!t&&e.parentNode instanceof ShadowRoot)return e.parentNode;return t}function te(){return document}function q(e,t){return e.getRootNode?e.getRootNode({composed:t}):te()}function A(e,t){while(e&&!t(e)){e=c(e)}return e||null}function o(e,t,n){const r=a(t,n);const o=a(t,"hx-disinherit");var i=a(t,"hx-inherit");if(e!==t){if(Q.config.disableInheritance){if(i&&(i==="*"||i.split(" ").indexOf(n)>=0)){return r}else{return null}}if(o&&(o==="*"||o.split(" ").indexOf(n)>=0)){return"unset"}}return r}function ne(t,n){let r=null;A(t,function(e){return!!(r=o(t,ue(e),n))});if(r!=="unset"){return r}}function h(e,t){return e instanceof Element&&e.matches(t)}function N(e){const t=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i;const n=t.exec(e);if(n){return n[1].toLowerCase()}else{return""}}function I(e){if("parseHTMLUnsafe"in Document){return Document.parseHTMLUnsafe(e)}const t=new DOMParser;return t.parseFromString(e,"text/html")}function L(e,t){while(t.childNodes.length>0){e.append(t.childNodes[0])}}function r(e){const t=te().createElement("script");ie(e.attributes,function(e){t.setAttribute(e.name,e.value)});t.textContent=e.textContent;t.async=false;if(Q.config.inlineScriptNonce){t.nonce=Q.config.inlineScriptNonce}return t}function i(e){return e.matches("script")&&(e.type==="text/javascript"||e.type==="module"||e.type==="")}function D(e){Array.from(e.querySelectorAll("script")).forEach(e=>{if(i(e)){const t=r(e);const n=e.parentNode;try{n.insertBefore(t,e)}catch(e){H(e)}finally{e.remove()}}})}function P(e){const t=e.replace(/]*)?>[\s\S]*?<\/head>/i,"");const n=N(t);let r;if(n==="html"){r=new DocumentFragment;const i=I(e);L(r,i.body);r.title=i.title}else if(n==="body"){r=new DocumentFragment;const i=I(t);L(r,i.body);r.title=i.title}else{const i=I('");r=i.querySelector("template").content;r.title=i.title;var o=r.querySelector("title");if(o&&o.parentNode===r){o.remove();r.title=o.innerText}}if(r){if(Q.config.allowScriptTags){D(r)}else{r.querySelectorAll("script").forEach(e=>e.remove())}}return r}function re(e){if(e){e()}}function t(e,t){return Object.prototype.toString.call(e)==="[object "+t+"]"}function k(e){return typeof e==="function"}function M(e){return t(e,"Object")}function oe(e){const t="htmx-internal-data";let n=e[t];if(!n){n=e[t]={}}return n}function F(t){const n=[];if(t){for(let e=0;e=0}function se(e){return e.getRootNode({composed:true})===document}function X(e){return e.trim().split(/\s+/)}function le(e,t){for(const n in t){if(t.hasOwnProperty(n)){e[n]=t[n]}}return e}function v(e){try{return JSON.parse(e)}catch(e){H(e);return null}}function U(){const e="htmx:sessionStorageTest";try{sessionStorage.setItem(e,e);sessionStorage.removeItem(e);return true}catch(e){return false}}function V(e){try{const t=new URL(e,window.location.href);e=t.pathname+t.search}catch(e){}if(e!="/"){e=e.replace(/\/+$/,"")}return e}function e(e){return On(te().body,function(){return eval(e)})}function j(t){const e=Q.on("htmx:load",function(e){t(e.detail.elt)});return e}function $(){Q.logger=function(e,t,n){if(console){console.log(t,e,n)}}}function _(){Q.logger=null}function f(e,t){if(typeof e!=="string"){return e.querySelector(t)}else{return f(te(),e)}}function y(e,t){if(typeof e!=="string"){return e.querySelectorAll(t)}else{return y(te(),e)}}function x(){return window}function z(e,t){e=S(e);if(t){x().setTimeout(function(){z(e);e=null},t)}else{c(e).removeChild(e)}}function ue(e){return e instanceof Element?e:null}function J(e){return e instanceof HTMLElement?e:null}function K(e){return typeof e==="string"?e:null}function p(e){return e instanceof Element||e instanceof Document||e instanceof DocumentFragment?e:null}function w(e,t,n){e=ue(S(e));if(!e){return}if(n){x().setTimeout(function(){w(e,t);e=null},n)}else{e.classList&&e.classList.add(t)}}function b(e,t,n){let r=ue(S(e));if(!r){return}if(n){x().setTimeout(function(){b(r,t);r=null},n)}else{if(r.classList){r.classList.remove(t);if(r.classList.length===0){r.removeAttribute("class")}}}}function G(e,t){e=S(e);e.classList.toggle(t)}function W(e,t){e=S(e);ie(e.parentElement.children,function(e){b(e,t)});w(ue(e),t)}function g(e,t){e=ue(S(e));if(e){return e.closest(t)}return null}function l(e,t){return e.substring(0,t.length)===t}function Z(e,t){return e.substring(e.length-t.length)===t}function Y(e){const t=e.trim();if(l(t,"<")&&Z(t,"/>")){return t.substring(1,t.length-2)}else{return t}}function m(t,r,n){if(r.indexOf("global ")===0){return m(t,r.slice(7),true)}t=S(t);const o=[];{let t=0;let n=0;for(let e=0;e"){t--}}if(n0){const r=Y(o.shift());let e;if(r.indexOf("closest ")===0){e=g(ue(t),Y(r.slice(8)))}else if(r.indexOf("find ")===0){e=f(p(t),Y(r.slice(5)))}else if(r==="next"||r==="nextElementSibling"){e=ue(t).nextElementSibling}else if(r.indexOf("next ")===0){e=pe(t,Y(r.slice(5)),!!n)}else if(r==="previous"||r==="previousElementSibling"){e=ue(t).previousElementSibling}else if(r.indexOf("previous ")===0){e=ge(t,Y(r.slice(9)),!!n)}else if(r==="document"){e=document}else if(r==="window"){e=window}else if(r==="body"){e=document.body}else if(r==="root"){e=q(t,!!n)}else if(r==="host"){e=t.getRootNode().host}else{s.push(r)}if(e){i.push(e)}}if(s.length>0){const e=s.join(",");const u=p(q(t,!!n));i.push(...F(u.querySelectorAll(e)))}return i}var pe=function(t,e,n){const r=p(q(t,n)).querySelectorAll(e);for(let e=0;e=0;e--){const o=r[e];if(o.compareDocumentPosition(t)===Node.DOCUMENT_POSITION_FOLLOWING){return o}}};function ce(e,t){if(typeof e!=="string"){return m(e,t)[0]}else{return m(te().body,e)[0]}}function S(e,t){if(typeof e==="string"){return f(p(t)||document,e)}else{return e}}function me(e,t,n,r){if(k(t)){return{target:te().body,event:K(e),listener:t,options:n}}else{return{target:S(e),event:K(t),listener:n,options:r}}}function ye(t,n,r,o){Gn(function(){const e=me(t,n,r,o);e.target.addEventListener(e.event,e.listener,e.options)});const e=k(n);return e?n:r}function xe(t,n,r){Gn(function(){const e=me(t,n,r);e.target.removeEventListener(e.event,e.listener)});return k(n)?n:r}const be=te().createElement("output");function ve(t,n){const e=ne(t,n);if(e){if(e==="this"){return[we(t,n)]}else{const r=m(t,e);const o=/(^|,)(\s*)inherit(\s*)($|,)/.test(e);if(o){const i=ue(A(t,function(e){return e!==t&&s(ue(e),n)}));if(i){r.push(...ve(i,n))}}if(r.length===0){H('The selector "'+e+'" on '+n+" returned no matches!");return[be]}else{return r}}}}function we(e,t){return ue(A(e,function(e){return a(ue(e),t)!=null}))}function Se(e){const t=ne(e,"hx-target");if(t){if(t==="this"){return we(e,"hx-target")}else{return ce(e,t)}}else{const n=oe(e);if(n.boosted){return te().body}else{return e}}}function Ee(e){return Q.config.attributesToSettle.includes(e)}function Ce(t,n){ie(Array.from(t.attributes),function(e){if(!n.hasAttribute(e.name)&&Ee(e.name)){t.removeAttribute(e.name)}});ie(n.attributes,function(e){if(Ee(e.name)){t.setAttribute(e.name,e.value)}})}function Oe(t,e){const n=Jn(e);for(let e=0;e0){s=e.substring(0,e.indexOf(":"));n=e.substring(e.indexOf(":")+1)}else{s=e}o.removeAttribute("hx-swap-oob");o.removeAttribute("data-hx-swap-oob");const r=m(t,n,false);if(r.length){ie(r,function(e){let t;const n=o.cloneNode(true);t=te().createDocumentFragment();t.appendChild(n);if(!Oe(s,e)){t=p(n)}const r={shouldSwap:true,target:e,fragment:t};if(!ae(e,"htmx:oobBeforeSwap",r))return;e=r.target;if(r.shouldSwap){Re(t);je(s,e,e,t,i);Te()}ie(i.elts,function(e){ae(e,"htmx:oobAfterSwap",r)})});o.parentNode.removeChild(o)}else{o.parentNode.removeChild(o);fe(te().body,"htmx:oobErrorNoTarget",{content:o,target:n})}return e}function Te(){const e=f("#--htmx-preserve-pantry--");if(e){for(const t of[...e.children]){const n=f("#"+t.id);n.parentNode.moveBefore(t,n);n.remove()}e.remove()}}function Re(e){ie(y(e,"[hx-preserve], [data-hx-preserve]"),function(e){const t=a(e,"id");const n=te().getElementById(t);if(n!=null){if(e.moveBefore){let e=f("#--htmx-preserve-pantry--");if(e==null){te().body.insertAdjacentHTML("afterend","
");e=f("#--htmx-preserve-pantry--")}e.moveBefore(n,null)}else{e.parentNode.replaceChild(n,e)}}})}function qe(i,e,s){ie(e.querySelectorAll("[id]"),function(t){const n=ee(t,"id");if(n&&n.length>0){const e=p(i);const r=e&&e.querySelector(CSS.escape(t.tagName)+"#"+CSS.escape(n));if(r&&r!==e){const o=t.cloneNode();Ce(t,r);s.tasks.push(function(){Ce(t,o)})}}})}function Ae(e){return function(){b(e,Q.config.addedClass);Ft(ue(e));Ne(p(e));ae(e,"htmx:load")}}function Ne(e){const t="[autofocus]";const n=J(h(e,t)?e:e.querySelector(t));if(n!=null){n.focus()}}function u(e,t,n,r){qe(e,n,r);while(n.childNodes.length>0){const o=n.firstChild;w(ue(o),Q.config.addedClass);e.insertBefore(o,t);if(o.nodeType!==Node.TEXT_NODE&&o.nodeType!==Node.COMMENT_NODE){r.tasks.push(Ae(o))}}}function Ie(e,t){let n=0;while(n0}function _e(h,d,p,g){if(!g){g={}}let m=null;let n=null;let e=function(){re(g.beforeSwapCallback);h=S(h);const r=g.contextElement?q(g.contextElement,false):te();const e=document.activeElement;let t={};t={elt:e,start:e?e.selectionStart:null,end:e?e.selectionEnd:null};const o=Sn(h);if(p.swapStyle==="textContent"){h.textContent=d}else{let n=P(d);o.title=g.title||n.title;if(g.historyRequest){n=n.querySelector("[hx-history-elt],[data-hx-history-elt]")||n}if(g.selectOOB){const i=g.selectOOB.split(",");for(let t=0;t0){x().setTimeout(n,p.settleDelay)}else{n()}};let t=Q.config.globalViewTransitions;if(p.hasOwnProperty("transition")){t=p.transition}const r=g.contextElement||te();if(t&&ae(r,"htmx:beforeTransition",g.eventInfo)&&typeof Promise!=="undefined"&&document.startViewTransition){const o=new Promise(function(e,t){m=e;n=t});const i=e;e=function(){document.startViewTransition(function(){i();return o})}}try{if(p?.swapDelay&&p.swapDelay>0){x().setTimeout(e,p.swapDelay)}else{e()}}catch(e){fe(r,"htmx:swapError",g.eventInfo);re(n);throw e}}function ze(e,t,n){const r=e.getResponseHeader(t);if(r.indexOf("{")===0){const o=v(r);for(const i in o){if(o.hasOwnProperty(i)){let e=o[i];if(M(e)){n=e.target!==undefined?e.target:n}else{e={value:e}}ae(n,i,e)}}}else{const s=r.split(",");for(let e=0;e0){const s=o[0];if(s==="]"){e--;if(e===0){if(n===null){t=t+"true"}o.shift();t+=")})";try{const l=On(r,function(){return Function(t)()},function(){return true});l.source=t;return l}catch(e){fe(te().body,"htmx:syntax:error",{error:e,source:t});return null}}}else if(s==="["){e++}if(tt(s,n,i)){t+="(("+i+"."+s+") ? ("+i+"."+s+") : (window."+s+"))"}else{t=t+s}n=o.shift()}}}function O(e,t){let n="";while(e.length>0&&!t.test(e[0])){n+=e.shift()}return n}function rt(e){let t;if(e.length>0&&Ye.test(e[0])){e.shift();t=O(e,Qe).trim();e.shift()}else{t=O(e,C)}return t}const ot="input, textarea, select";function it(e,t,n){const r=[];const o=et(t);do{O(o,Ze);const l=o.length;const u=O(o,/[,\[\s]/);if(u!==""){if(u==="every"){const c={trigger:"every"};O(o,Ze);c.pollInterval=d(O(o,/[,\[\s]/));O(o,Ze);var i=nt(e,o,"event");if(i){c.eventFilter=i}r.push(c)}else{const f={trigger:u};var i=nt(e,o,"event");if(i){f.eventFilter=i}O(o,Ze);while(o.length>0&&o[0]!==","){const a=o.shift();if(a==="changed"){f.changed=true}else if(a==="once"){f.once=true}else if(a==="consume"){f.consume=true}else if(a==="delay"&&o[0]===":"){o.shift();f.delay=d(O(o,C))}else if(a==="from"&&o[0]===":"){o.shift();if(Ye.test(o[0])){var s=rt(o)}else{var s=O(o,C);if(s==="closest"||s==="find"||s==="next"||s==="previous"){o.shift();const h=rt(o);if(h.length>0){s+=" "+h}}}f.from=s}else if(a==="target"&&o[0]===":"){o.shift();f.target=rt(o)}else if(a==="throttle"&&o[0]===":"){o.shift();f.throttle=d(O(o,C))}else if(a==="queue"&&o[0]===":"){o.shift();f.queue=O(o,C)}else if(a==="root"&&o[0]===":"){o.shift();f[a]=rt(o)}else if(a==="threshold"&&o[0]===":"){o.shift();f[a]=O(o,C)}else{fe(e,"htmx:syntax:error",{token:o.shift()})}O(o,Ze)}r.push(f)}}if(o.length===l){fe(e,"htmx:syntax:error",{token:o.shift()})}O(o,Ze)}while(o[0]===","&&o.shift());if(n){n[t]=r}return r}function st(e){const t=a(e,"hx-trigger");let n=[];if(t){const r=Q.config.triggerSpecsCache;n=r&&r[t]||it(e,t,r)}if(n.length>0){return n}else if(h(e,"form")){return[{trigger:"submit"}]}else if(h(e,'input[type="button"], input[type="submit"]')){return[{trigger:"click"}]}else if(h(e,ot)){return[{trigger:"change"}]}else{return[{trigger:"click"}]}}function lt(e){oe(e).cancelled=true}function ut(e,t,n){const r=oe(e);r.timeout=x().setTimeout(function(){if(se(e)&&r.cancelled!==true){if(!pt(n,e,Xt("hx:poll:trigger",{triggerSpec:n,target:e}))){t(e)}ut(e,t,n)}},n.pollInterval)}function ct(e){return location.hostname===e.hostname&&ee(e,"href")&&ee(e,"href").indexOf("#")!==0}function ft(e){return g(e,Q.config.disableSelector)}function at(t,n,e){if(t instanceof HTMLAnchorElement&&ct(t)&&(t.target===""||t.target==="_self")||t.tagName==="FORM"&&String(ee(t,"method")).toLowerCase()!=="dialog"){n.boosted=true;let r,o;if(t.tagName==="A"){r="get";o=ee(t,"href")}else{const i=ee(t,"method");r=i?i.toLowerCase():"get";o=ee(t,"action");if(o==null||o===""){o=location.href}if(r==="get"&&o.includes("?")){o=o.replace(/\?[^#]+/,"")}}e.forEach(function(e){gt(t,function(e,t){const n=ue(e);if(ft(n)){E(n);return}he(r,o,n,t)},n,e,true)})}}function ht(e,t){if(e.type==="submit"&&t.tagName==="FORM"){return true}else if(e.type==="click"){const n=t.closest('input[type="submit"], button');if(n&&n.form&&n.type==="submit"){return true}const r=t.closest("a");const o=/^#.+/;if(r&&r.href&&!o.test(r.getAttribute("href"))){return true}}return false}function dt(e,t){return oe(e).boosted&&e instanceof HTMLAnchorElement&&t.type==="click"&&(t.ctrlKey||t.metaKey)}function pt(e,t,n){const r=e.eventFilter;if(r){try{return r.call(t,n)!==true}catch(e){const o=r.source;fe(te().body,"htmx:eventFilter:error",{error:e,source:o});return true}}return false}function gt(l,u,e,c,f){const a=oe(l);let t;if(c.from){t=m(l,c.from)}else{t=[l]}if(c.changed){if(!("lastValue"in a)){a.lastValue=new WeakMap}t.forEach(function(e){if(!a.lastValue.has(c)){a.lastValue.set(c,new WeakMap)}a.lastValue.get(c).set(e,e.value)})}ie(t,function(i){const s=function(e){if(!se(l)){i.removeEventListener(c.trigger,s);return}if(dt(l,e)){return}if(f||ht(e,i)){e.preventDefault()}if(pt(c,l,e)){return}const t=oe(e);t.triggerSpec=c;if(t.handledFor==null){t.handledFor=[]}if(t.handledFor.indexOf(l)<0){t.handledFor.push(l);if(c.consume){e.stopPropagation()}if(c.target&&e.target){if(!h(ue(e.target),c.target)){return}}if(c.once){if(a.triggeredOnce){return}else{a.triggeredOnce=true}}if(c.changed){const n=e.target;const r=n.value;const o=a.lastValue.get(c);if(o.has(n)&&o.get(n)===r){return}o.set(n,r)}if(a.delayed){clearTimeout(a.delayed)}if(a.throttle){return}if(c.throttle>0){if(!a.throttle){ae(l,"htmx:trigger");u(l,e);a.throttle=x().setTimeout(function(){a.throttle=null},c.throttle)}}else if(c.delay>0){a.delayed=x().setTimeout(function(){ae(l,"htmx:trigger");u(l,e)},c.delay)}else{ae(l,"htmx:trigger");u(l,e)}}};if(e.listenerInfos==null){e.listenerInfos=[]}e.listenerInfos.push({trigger:c.trigger,listener:s,on:i});i.addEventListener(c.trigger,s)})}let mt=false;let yt=null;function xt(){if(!yt){yt=function(){mt=true};window.addEventListener("scroll",yt);window.addEventListener("resize",yt);setInterval(function(){if(mt){mt=false;ie(te().querySelectorAll("[hx-trigger*='revealed'],[data-hx-trigger*='revealed']"),function(e){bt(e)})}},200)}}function bt(e){if(!s(e,"data-hx-revealed")&&B(e)){e.setAttribute("data-hx-revealed","true");const t=oe(e);if(t.initHash){ae(e,"revealed")}else{e.addEventListener("htmx:afterProcessNode",function(){ae(e,"revealed")},{once:true})}}}function vt(e,t,n,r){const o=function(){if(!n.loaded){n.loaded=true;ae(e,"htmx:trigger");t(e)}};if(r>0){x().setTimeout(o,r)}else{o()}}function wt(t,n,e){let i=false;ie(de,function(r){if(s(t,"hx-"+r)){const o=a(t,"hx-"+r);i=true;n.path=o;n.verb=r;e.forEach(function(e){St(t,e,n,function(e,t){const n=ue(e);if(ft(n)){E(n);return}he(r,o,n,t)})})}});return i}function St(r,e,t,n){if(e.trigger==="revealed"){xt();gt(r,n,t,e);bt(ue(r))}else if(e.trigger==="intersect"){const o={};if(e.root){o.root=ce(r,e.root)}if(e.threshold){o.threshold=parseFloat(e.threshold)}const i=new IntersectionObserver(function(t){for(let e=0;e0){t.polling=true;ut(ue(r),n,e)}else{gt(r,n,t,e)}}function Et(e){const t=ue(e);if(!t){return false}const n=t.attributes;for(let e=0;e", "+e).join(""));return o}else{return[]}}function Rt(e){const t=At(e.target);const n=It(e);if(n){n.lastButtonClicked=t}}function qt(e){const t=It(e);if(t){t.lastButtonClicked=null}}function At(e){return g(ue(e),"button, input[type='submit']")}function Nt(e){return e.form||g(e,"form")}function It(e){const t=At(e.target);if(!t){return}const n=Nt(t);if(!n){return}return oe(n)}function Lt(e){e.addEventListener("click",Rt);e.addEventListener("focusin",Rt);e.addEventListener("focusout",qt)}function Dt(t,e,n){const r=oe(t);if(!Array.isArray(r.onHandlers)){r.onHandlers=[]}let o;const i=function(e){On(t,function(){if(ft(t)){return}if(!o){o=new Function("event",n)}o.call(t,e)})};t.addEventListener(e,i);r.onHandlers.push({event:e,listener:i})}function Pt(t){De(t);for(let e=0;eQ.config.historyCacheSize){i.shift()}while(i.length>0){try{sessionStorage.setItem("htmx-history-cache",JSON.stringify(i));break}catch(e){fe(te().body,"htmx:historyCacheError",{cause:e,cache:i});i.shift()}}}function Jt(t){if(!U()){return null}t=V(t);const n=v(sessionStorage.getItem("htmx-history-cache"))||[];for(let e=0;e=200&&this.status<400){r.response=this.response;ae(te().body,"htmx:historyCacheMissLoad",r);_e(r.historyElt,r.response,n,{contextElement:r.historyElt,historyRequest:true});$t(r.path);ae(te().body,"htmx:historyRestore",{path:e,cacheMiss:true,serverResponse:r.response})}else{fe(te().body,"htmx:historyCacheMissLoadError",r)}};if(ae(te().body,"htmx:historyCacheMiss",r)){t.send()}}function en(e){Gt();e=e||location.pathname+location.search;const t=Jt(e);if(t){const n={swapStyle:"innerHTML",swapDelay:0,settleDelay:0,scroll:t.scroll};const r={path:e,item:t,historyElt:_t(),swapSpec:n};if(ae(te().body,"htmx:historyCacheHit",r)){_e(r.historyElt,t.content,n,{contextElement:r.historyElt,title:t.title});$t(r.path);ae(te().body,"htmx:historyRestore",r)}}else{if(Q.config.refreshOnHistoryMiss){Q.location.reload(true)}else{Qt(e)}}}function tn(e){let t=ve(e,"hx-indicator");if(t==null){t=[e]}ie(t,function(e){const t=oe(e);t.requestCount=(t.requestCount||0)+1;w(e,Q.config.requestClass)});return t}function nn(e){let t=ve(e,"hx-disabled-elt");if(t==null){t=[]}ie(t,function(e){const t=oe(e);t.requestCount=(t.requestCount||0)+1;if(!e.hasAttribute("disabled")){e.setAttribute("disabled","");e.setAttribute("data-disabled-by-htmx","")}});return t}function rn(e,t){ie(e.concat(t),function(e){const t=oe(e);t.requestCount=(t.requestCount||1)-1});ie(e,function(e){const t=oe(e);if(t.requestCount===0){b(e,Q.config.requestClass)}});ie(t,function(e){const t=oe(e);if(t.requestCount===0&&e.hasAttribute("data-disabled-by-htmx")){e.removeAttribute("disabled");e.removeAttribute("data-disabled-by-htmx")}})}function on(t,n){for(let e=0;en.indexOf(e)<0)}else{e=e.filter(e=>e!==n)}r.delete(t);ie(e,e=>r.append(t,e))}}function cn(e){if(e instanceof HTMLSelectElement&&e.multiple){return F(e.querySelectorAll("option:checked")).map(function(e){return e.value})}if(e instanceof HTMLInputElement&&e.files){return F(e.files)}return e.value}function fn(t,n,r,e,o){if(e==null||on(t,e)){return}else{t.push(e)}if(sn(e)){const i=ee(e,"name");ln(i,cn(e),n);if(o){an(e,r)}}if(e instanceof HTMLFormElement){ie(e.elements,function(e){if(t.indexOf(e)>=0){un(e.name,cn(e),n)}else{t.push(e)}if(o){an(e,r)}});new FormData(e).forEach(function(e,t){if(e instanceof File&&e.name===""){return}ln(t,e,n)})}}function an(e,t){const n=e;if(n.willValidate){ae(n,"htmx:validation:validate");if(!n.checkValidity()){if(ae(n,"htmx:validation:failed",{message:n.validationMessage,validity:n.validity})&&!t.length&&Q.config.reportValidityOfForms){n.reportValidity()}t.push({elt:n,message:n.validationMessage,validity:n.validity})}}}function hn(n,e){for(const t of e.keys()){n.delete(t)}e.forEach(function(e,t){n.append(t,e)});return n}function dn(e,t){const n=[];const r=new FormData;const o=new FormData;const i=[];const s=oe(e);if(s.lastButtonClicked&&!se(s.lastButtonClicked)){s.lastButtonClicked=null}let l=e instanceof HTMLFormElement&&e.noValidate!==true||a(e,"hx-validate")==="true";if(s.lastButtonClicked){l=l&&s.lastButtonClicked.formNoValidate!==true}if(t!=="get"){fn(n,o,i,Nt(e),l)}fn(n,r,i,e,l);if(s.lastButtonClicked||e.tagName==="BUTTON"||e.tagName==="INPUT"&&ee(e,"type")==="submit"){const c=s.lastButtonClicked||e;const f=ee(c,"name");ln(f,c.value,o)}const u=ve(e,"hx-include");ie(u,function(e){fn(n,r,i,ue(e),l);if(!h(e,"form")){ie(p(e).querySelectorAll(ot),function(e){fn(n,r,i,e,l)})}});hn(r,o);return{errors:i,formData:r,values:kn(r)}}function pn(e,t,n){if(e!==""){e+="&"}if(String(n)==="[object Object]"){n=JSON.stringify(n)}const r=encodeURIComponent(n);e+=encodeURIComponent(t)+"="+r;return e}function gn(e){e=Dn(e);let n="";e.forEach(function(e,t){n=pn(n,t,e)});return n}function mn(e,t,n){const r={"HX-Request":"true","HX-Trigger":ee(e,"id"),"HX-Trigger-Name":ee(e,"name"),"HX-Target":a(t,"id"),"HX-Current-URL":location.href};Cn(e,"hx-headers",false,r);if(n!==undefined){r["HX-Prompt"]=n}if(oe(e).boosted){r["HX-Boosted"]="true"}return r}function yn(n,e){const t=ne(e,"hx-params");if(t){if(t==="none"){return new FormData}else if(t==="*"){return n}else if(t.indexOf("not ")===0){ie(t.slice(4).split(","),function(e){e=e.trim();n.delete(e)});return n}else{const r=new FormData;ie(t.split(","),function(t){t=t.trim();if(n.has(t)){n.getAll(t).forEach(function(e){r.append(t,e)})}});return r}}else{return n}}function xn(e){return!!ee(e,"href")&&ee(e,"href").indexOf("#")>=0}function bn(e,t){const n=t||ne(e,"hx-swap");const r={swapStyle:oe(e).boosted?"innerHTML":Q.config.defaultSwapStyle,swapDelay:Q.config.defaultSwapDelay,settleDelay:Q.config.defaultSettleDelay};if(Q.config.scrollIntoViewOnBoost&&oe(e).boosted&&!xn(e)){r.show="top"}if(n){const s=X(n);if(s.length>0){for(let e=0;e0?o.join(":"):null;r.scroll=c;r.scrollTarget=i}else if(l.indexOf("show:")===0){const f=l.slice(5);var o=f.split(":");const a=o.pop();var i=o.length>0?o.join(":"):null;r.show=a;r.showTarget=i}else if(l.indexOf("focus-scroll:")===0){const h=l.slice("focus-scroll:".length);r.focusScroll=h=="true"}else if(e==0){r.swapStyle=l}else{H("Unknown modifier in hx-swap: "+l)}}}}return r}function vn(e){return ne(e,"hx-encoding")==="multipart/form-data"||h(e,"form")&&ee(e,"enctype")==="multipart/form-data"}function wn(t,n,r){let o=null;Vt(n,function(e){if(o==null){o=e.encodeParameters(t,r,n)}});if(o!=null){return o}else{if(vn(n)){return hn(new FormData,Dn(r))}else{return gn(r)}}}function Sn(e){return{tasks:[],elts:[e]}}function En(e,t){const n=e[0];const r=e[e.length-1];if(t.scroll){var o=null;if(t.scrollTarget){o=ue(ce(n,t.scrollTarget))}if(t.scroll==="top"&&(n||o)){o=o||n;o.scrollTop=0}if(t.scroll==="bottom"&&(r||o)){o=o||r;o.scrollTop=o.scrollHeight}if(typeof t.scroll==="number"){x().setTimeout(function(){window.scrollTo(0,t.scroll)},0)}}if(t.show){var o=null;if(t.showTarget){let e=t.showTarget;if(t.showTarget==="window"){e="body"}o=ue(ce(n,e))}if(t.show==="top"&&(n||o)){o=o||n;o.scrollIntoView({block:"start",behavior:Q.config.scrollBehavior})}if(t.show==="bottom"&&(r||o)){o=o||r;o.scrollIntoView({block:"end",behavior:Q.config.scrollBehavior})}}}function Cn(r,e,o,i,s){if(i==null){i={}}if(r==null){return i}const l=a(r,e);if(l){let e=l.trim();let t=o;if(e==="unset"){return null}if(e.indexOf("javascript:")===0){e=e.slice(11);t=true}else if(e.indexOf("js:")===0){e=e.slice(3);t=true}if(e.indexOf("{")!==0){e="{"+e+"}"}let n;if(t){n=On(r,function(){if(s){return Function("event","return ("+e+")").call(r,s)}else{return Function("return ("+e+")").call(r)}},{})}else{n=v(e)}for(const u in n){if(n.hasOwnProperty(u)){if(i[u]==null){i[u]=n[u]}}}}return Cn(ue(c(r)),e,o,i,s)}function On(e,t,n){if(Q.config.allowEval){return t()}else{fe(e,"htmx:evalDisallowedError");return n}}function Hn(e,t,n){return Cn(e,"hx-vars",true,n,t)}function Tn(e,t,n){return Cn(e,"hx-vals",false,n,t)}function Rn(e,t){return le(Hn(e,t),Tn(e,t))}function qn(t,n,r){if(r!==null){try{t.setRequestHeader(n,r)}catch(e){t.setRequestHeader(n,encodeURIComponent(r));t.setRequestHeader(n+"-URI-AutoEncoded","true")}}}function An(t){if(t.responseURL){try{const e=new URL(t.responseURL);return e.pathname+e.search}catch(e){fe(te().body,"htmx:badResponseUrl",{url:t.responseURL})}}}function T(e,t){return t.test(e.getAllResponseHeaders())}function Nn(t,n,r){t=t.toLowerCase();if(r){if(r instanceof Element||typeof r==="string"){return he(t,n,null,null,{targetOverride:S(r)||be,returnPromise:true})}else{let e=S(r.target);if(r.target&&!e||r.source&&!e&&!S(r.source)){e=be}return he(t,n,S(r.source),r.event,{handler:r.handler,headers:r.headers,values:r.values,targetOverride:e,swapOverride:r.swap,select:r.select,returnPromise:true,push:r.push,replace:r.replace,selectOOB:r.selectOOB})}}else{return he(t,n,null,null,{returnPromise:true})}}function In(e){const t=[];while(e){t.push(e);e=e.parentElement}return t}function Ln(e,t,n){const r=new URL(t,location.protocol!=="about:"?location.href:window.origin);const o=location.protocol!=="about:"?location.origin:window.origin;const i=o===r.origin;if(Q.config.selfRequestsOnly){if(!i){return false}}return ae(e,"htmx:validateUrl",le({url:r,sameHost:i},n))}function Dn(e){if(e instanceof FormData)return e;const t=new FormData;for(const n in e){if(e.hasOwnProperty(n)){if(e[n]&&typeof e[n].forEach==="function"){e[n].forEach(function(e){t.append(n,e)})}else if(typeof e[n]==="object"&&!(e[n]instanceof Blob)){t.append(n,JSON.stringify(e[n]))}else{t.append(n,e[n])}}}return t}function Pn(r,o,e){return new Proxy(e,{get:function(t,e){if(typeof e==="number")return t[e];if(e==="length")return t.length;if(e==="push"){return function(e){t.push(e);r.append(o,e)}}if(typeof t[e]==="function"){return function(){t[e].apply(t,arguments);r.delete(o);t.forEach(function(e){r.append(o,e)})}}if(t[e]&&t[e].length===1){return t[e][0]}else{return t[e]}},set:function(e,t,n){e[t]=n;r.delete(o);e.forEach(function(e){r.append(o,e)});return true}})}function kn(o){return new Proxy(o,{get:function(e,t){if(typeof t==="symbol"){const r=Reflect.get(e,t);if(typeof r==="function"){return function(){return r.apply(o,arguments)}}else{return r}}if(t==="toJSON"){return()=>Object.fromEntries(o)}if(t in e){if(typeof e[t]==="function"){return function(){return o[t].apply(o,arguments)}}}const n=o.getAll(t);if(n.length===0){return undefined}else if(n.length===1){return n[0]}else{return Pn(e,t,n)}},set:function(t,n,e){if(typeof n!=="string"){return false}t.delete(n);if(e&&typeof e.forEach==="function"){e.forEach(function(e){t.append(n,e)})}else if(typeof e==="object"&&!(e instanceof Blob)){t.append(n,JSON.stringify(e))}else{t.append(n,e)}return true},deleteProperty:function(e,t){if(typeof t==="string"){e.delete(t)}return true},ownKeys:function(e){return Reflect.ownKeys(Object.fromEntries(e))},getOwnPropertyDescriptor:function(e,t){return Reflect.getOwnPropertyDescriptor(Object.fromEntries(e),t)}})}function he(t,n,r,o,i,k){let s=null;let l=null;i=i!=null?i:{};if(i.returnPromise&&typeof Promise!=="undefined"){var e=new Promise(function(e,t){s=e;l=t})}if(r==null){r=te().body}const M=i.handler||Vn;const F=i.select||null;if(!se(r)){re(s);return e}const u=i.targetOverride||ue(Se(r));if(u==null||u==be){fe(r,"htmx:targetError",{target:ne(r,"hx-target")});re(l);return e}let c=oe(r);const f=c.lastButtonClicked;if(f){const A=ee(f,"formaction");if(A!=null){n=A}const N=ee(f,"formmethod");if(N!=null){if(de.includes(N.toLowerCase())){t=N}else{re(s);return e}}}const a=ne(r,"hx-confirm");if(k===undefined){const K=function(e){return he(t,n,r,o,i,!!e)};const G={target:u,elt:r,path:n,verb:t,triggeringEvent:o,etc:i,issueRequest:K,question:a};if(ae(r,"htmx:confirm",G)===false){re(s);return e}}let h=r;let d=ne(r,"hx-sync");let p=null;let B=false;if(d){const I=d.split(":");const L=I[0].trim();if(L==="this"){h=we(r,"hx-sync")}else{h=ue(ce(r,L))}d=(I[1]||"drop").trim();c=oe(h);if(d==="drop"&&c.xhr&&c.abortable!==true){re(s);return e}else if(d==="abort"){if(c.xhr){re(s);return e}else{B=true}}else if(d==="replace"){ae(h,"htmx:abort")}else if(d.indexOf("queue")===0){const W=d.split(" ");p=(W[1]||"last").trim()}}if(c.xhr){if(c.abortable){ae(h,"htmx:abort")}else{if(p==null){if(o){const D=oe(o);if(D&&D.triggerSpec&&D.triggerSpec.queue){p=D.triggerSpec.queue}}if(p==null){p="last"}}if(c.queuedRequests==null){c.queuedRequests=[]}if(p==="first"&&c.queuedRequests.length===0){c.queuedRequests.push(function(){he(t,n,r,o,i)})}else if(p==="all"){c.queuedRequests.push(function(){he(t,n,r,o,i)})}else if(p==="last"){c.queuedRequests=[];c.queuedRequests.push(function(){he(t,n,r,o,i)})}re(s);return e}}const g=new XMLHttpRequest;c.xhr=g;c.abortable=B;const m=function(){c.xhr=null;c.abortable=false;if(c.queuedRequests!=null&&c.queuedRequests.length>0){const e=c.queuedRequests.shift();e()}};const X=ne(r,"hx-prompt");if(X){var y=prompt(X);if(y===null||!ae(r,"htmx:prompt",{prompt:y,target:u})){re(s);m();return e}}if(a&&!k){if(!confirm(a)){re(s);m();return e}}let x=mn(r,u,y);if(t!=="get"&&!vn(r)){x["Content-Type"]="application/x-www-form-urlencoded"}if(i.headers){x=le(x,i.headers)}const U=dn(r,t);let b=U.errors;const V=U.formData;if(i.values){hn(V,Dn(i.values))}const j=Dn(Rn(r,o));const v=hn(V,j);let w=yn(v,r);if(Q.config.getCacheBusterParam&&t==="get"){w.set("org.htmx.cache-buster",ee(u,"id")||"true")}if(n==null||n===""){n=location.href}const S=Cn(r,"hx-request");const $=oe(r).boosted;let E=Q.config.methodsThatUseUrlParams.indexOf(t)>=0;const C={boosted:$,useUrlParams:E,formData:w,parameters:kn(w),unfilteredFormData:v,unfilteredParameters:kn(v),headers:x,elt:r,target:u,verb:t,errors:b,withCredentials:i.credentials||S.credentials||Q.config.withCredentials,timeout:i.timeout||S.timeout||Q.config.timeout,path:n,triggeringEvent:o};if(!ae(r,"htmx:configRequest",C)){re(s);m();return e}n=C.path;t=C.verb;x=C.headers;w=Dn(C.parameters);b=C.errors;E=C.useUrlParams;if(b&&b.length>0){ae(r,"htmx:validation:halted",C);re(s);m();return e}const _=n.split("#");const z=_[0];const O=_[1];let H=n;if(E){H=z;const Z=!w.keys().next().done;if(Z){if(H.indexOf("?")<0){H+="?"}else{H+="&"}H+=gn(w);if(O){H+="#"+O}}}if(!Ln(r,H,C)){fe(r,"htmx:invalidPath",C);re(l);m();return e}g.open(t.toUpperCase(),H,true);g.overrideMimeType("text/html");g.withCredentials=C.withCredentials;g.timeout=C.timeout;if(S.noHeaders){}else{for(const P in x){if(x.hasOwnProperty(P)){const Y=x[P];qn(g,P,Y)}}}const T={xhr:g,target:u,requestConfig:C,etc:i,boosted:$,select:F,pathInfo:{requestPath:n,finalRequestPath:H,responsePath:null,anchor:O}};g.onload=function(){try{const t=In(r);T.pathInfo.responsePath=An(g);M(r,T);if(T.keepIndicators!==true){rn(R,q)}ae(r,"htmx:afterRequest",T);ae(r,"htmx:afterOnLoad",T);if(!se(r)){let e=null;while(t.length>0&&e==null){const n=t.shift();if(se(n)){e=n}}if(e){ae(e,"htmx:afterRequest",T);ae(e,"htmx:afterOnLoad",T)}}re(s)}catch(e){fe(r,"htmx:onLoadError",le({error:e},T));throw e}finally{m()}};g.onerror=function(){rn(R,q);fe(r,"htmx:afterRequest",T);fe(r,"htmx:sendError",T);re(l);m()};g.onabort=function(){rn(R,q);fe(r,"htmx:afterRequest",T);fe(r,"htmx:sendAbort",T);re(l);m()};g.ontimeout=function(){rn(R,q);fe(r,"htmx:afterRequest",T);fe(r,"htmx:timeout",T);re(l);m()};if(!ae(r,"htmx:beforeRequest",T)){re(s);m();return e}var R=tn(r);var q=nn(r);ie(["loadstart","loadend","progress","abort"],function(t){ie([g,g.upload],function(e){e.addEventListener(t,function(e){ae(r,"htmx:xhr:"+t,{lengthComputable:e.lengthComputable,loaded:e.loaded,total:e.total})})})});ae(r,"htmx:beforeSend",T);const J=E?null:wn(g,r,w);g.send(J);return e}function Mn(e,t){const n=t.xhr;let r=null;let o=null;if(T(n,/HX-Push:/i)){r=n.getResponseHeader("HX-Push");o="push"}else if(T(n,/HX-Push-Url:/i)){r=n.getResponseHeader("HX-Push-Url");o="push"}else if(T(n,/HX-Replace-Url:/i)){r=n.getResponseHeader("HX-Replace-Url");o="replace"}if(r){if(r==="false"){return{}}else{return{type:o,path:r}}}const i=t.pathInfo.finalRequestPath;const s=t.pathInfo.responsePath;let l=t.etc.push||ne(e,"hx-push-url");let u=t.etc.replace||ne(e,"hx-replace-url");if(l==="false")l=null;if(u==="false")u=null;const c=oe(e).boosted;let f=null;let a=null;if(l){f="push";a=l}else if(u){f="replace";a=u}else if(c){f="push";a=s||i}if(a){if(a==="true"){a=s||i}if(t.pathInfo.anchor&&a.indexOf("#")===-1){a=a+"#"+t.pathInfo.anchor}return{type:f,path:a}}else{return{}}}function Fn(e,t){var n=new RegExp(e.code);return n.test(t.toString(10))}function Bn(e){for(var t=0;t`+`.${t}{opacity:0;visibility: hidden} `+`.${n} .${t}, .${n}.${t}{opacity:1;visibility: visible;transition: opacity 200ms ease-in}`+"")}}function Zn(){const e=te().querySelector('meta[name="htmx-config"]');if(e){return v(e.content)}else{return null}}function Yn(){const e=Zn();if(e){Q.config=le(Q.config,e)}}Gn(function(){Yn();Wn();let e=te().body;Ft(e);const t=te().querySelectorAll("[hx-trigger='restored'],[data-hx-trigger='restored']");e.addEventListener("htmx:abort",function(e){const t=e.detail.elt||e.target;const n=oe(t);if(n&&n.xhr){n.xhr.abort()}});const n=window.onpopstate?window.onpopstate.bind(window):null;window.onpopstate=function(e){if(e.state&&e.state.htmx){en();ie(t,function(e){ae(e,"htmx:restored",{document:te(),triggerEvent:ae})})}else{if(n){n(e)}}};x().setTimeout(function(){ae(e,"htmx:load",{});e=null},0)});return Q}(); \ No newline at end of file diff --git a/extensions/pagetop-htmx/build.rs b/extensions/pagetop-htmx/build.rs deleted file mode 100644 index bbf6de4b..00000000 --- a/extensions/pagetop-htmx/build.rs +++ /dev/null @@ -1,13 +0,0 @@ -use pagetop_build::{StaticFilesBundle, copy_dir}; - -fn main() -> std::io::Result<()> { - // Regenera `static/` desde cero sólo si hay cambios en `assets/`. - println!("cargo:rerun-if-changed=assets"); - let _ = std::fs::remove_dir_all("static"); - - copy_dir("assets", "static")?; - - StaticFilesBundle::from_dir("./static", None) - .with_name("htmx") - .build() -} diff --git a/extensions/pagetop-htmx/src/hx.rs b/extensions/pagetop-htmx/src/hx.rs deleted file mode 100644 index f1f1f666..00000000 --- a/extensions/pagetop-htmx/src/hx.rs +++ /dev/null @@ -1,549 +0,0 @@ -//! Constantes para los atributos y valores de HTMX 2. -//! -//! Usar estas constantes en lugar de literales evita errores tipográficos en tiempo de compilación. -//! Una mala declaración como `"hx-gte"` no genera ningún error en tiempo de ejecución, falla -//! silenciosamente. Con constantes, el compilador lo detecta de inmediato. -//! -//! # Atributos estáticos en `html!` -//! -//! Para valores conocidos en tiempo de compilación, los atributos `hx-*` pueden escribirse -//! directamente en la macro `html!` sin necesidad de [`Props`](pagetop::html::Props): -//! -//! ```rust,no_run -//! use pagetop::prelude::*; -//! -//! let markup = html! { -//! button hx-get="/api/items" hx-target="#list" hx-swap="outerHTML" { "Load" } -//! }; -//! ``` -//! -//! # Atributos dinámicos con [`Props`](pagetop::html::Props) -//! -//! Cuando los valores se construyen en tiempo de ejecución o se inyectan desde una extensión, -//! puedes usar [`Props`](pagetop::html::Props) combinado con las constantes de este módulo: -//! -//! ```rust,no_run -//! use pagetop::prelude::*; -//! use pagetop_htmx::hx; -//! -//! let endpoint = "/api/items"; // Calculado en tiempo de ejecución. -//! -//! let props = Props::new(hx::GET, endpoint) -//! .with_prop(PropsOp::set(hx::TARGET, "#list")) -//! .with_prop(PropsOp::set(hx::SWAP, hx::swap::OUTER_HTML)); -//! -//! let markup = html! { -//! button (props) { "Load" } -//! }; -//! ``` -//! -//! # Integración en componentes -//! -//! El patrón recomendado es añadir un campo `props: Props` al componente y exponerlo con -//! `with_prop()`. Cualquier extensión puede entonces inyectar atributos HTMX sin que el componente -//! ni el tema necesiten conocer HTMX: -//! -//! ```rust,no_run -//! use pagetop::prelude::*; -//! use pagetop_htmx::hx; -//! -//! #[derive(AutoDefault, Getters)] -//! pub struct MyButton { -//! props: Props, -//! } -//! -//! impl MyButton { -//! pub fn new() -> Self { Self::default() } -//! -//! #[builder_fn] -//! pub fn with_prop(mut self, op: PropsOp) -> Self { -//! self.props.alter_prop(op); -//! self -//! } -//! } -//! -//! MyButton::new() -//! .with_prop(PropsOp::set(hx::POST, "/api/save")) -//! .with_prop(PropsOp::set(hx::TARGET, "#message")) -//! .with_prop(PropsOp::set(hx::SWAP, hx::swap::INNER_HTML)); -//! ``` -//! -//! # Eventos en línea -//! -//! Para el atributo `hx-on:*` (cuyo nombre incluye el evento y no puede ser una constante) usa las -//! funciones [`on()`] y [`on_htmx()`]: -//! -//! ```rust,no_run -//! use pagetop::prelude::*; -//! use pagetop_htmx::hx; -//! -//! // Evento nativo del DOM: hx-on:click="..." -//! // Evento propio de HTMX: hx-on::after-swap="..." -//! let props = Props::new(hx::on("click"), "this.classList.toggle('active')") -//! .with_prop(PropsOp::set(hx::on_htmx("after-swap"), "console.log('done')")); -//! ``` - -// **< HTTP Methods >******************************************************************************* - -/// Realiza una petición GET al servidor y aplica la respuesta al objetivo. -/// -/// Es el atributo HTMX más común: carga contenido desde el servidor sin recargar la página. -/// -/// ```rust,no_run -/// # use pagetop::prelude::*; -/// # use pagetop_htmx::hx; -/// let props = Props::new(hx::GET, "/api/search") -/// .with_prop(PropsOp::set(hx::TARGET, "#results")); -/// ``` -pub const GET: &str = "hx-get"; - -/// Realiza una petición POST al servidor. -/// -/// Se usa habitualmente para enviar datos de formulario o acciones que modifican el estado del -/// servidor. Para subida de ficheros, combinar con [`ENCODING`] = `"multipart/form-data"`. -pub const POST: &str = "hx-post"; - -/// Realiza una petición PUT al servidor. -pub const PUT: &str = "hx-put"; - -/// Realiza una petición PATCH al servidor. -pub const PATCH: &str = "hx-patch"; - -/// Realiza una petición DELETE al servidor. -/// -/// ```rust,no_run -/// # use pagetop::prelude::*; -/// # use pagetop_htmx::hx; -/// // Al eliminar un elemento, reemplazarlo con respuesta vacía borra el nodo del DOM. -/// let props = Props::new(hx::DELETE, "/api/item/42") -/// .with_prop(PropsOp::set(hx::TARGET, "closest li")) -/// .with_prop(PropsOp::set(hx::SWAP, hx::swap::OUTER_HTML)); -/// ``` -pub const DELETE: &str = "hx-delete"; - -// **< Target and Swap >**************************************************************************** - -/// Selector CSS del elemento que recibirá la respuesta. Por defecto, el elemento mismo. -/// -/// Además de selectores CSS estándar, HTMX acepta `"this"` (el elemento), `"closest X"` (ancestro -/// más próximo), `"find X"` (descendiente) y `"next X"` / `"previous X"`. -/// -/// ```rust,no_run -/// # use pagetop::prelude::*; -/// # use pagetop_htmx::hx; -/// let props = Props::new(hx::GET, "/api/detalles") -/// .with_prop(PropsOp::set(hx::TARGET, "closest article")); -/// ``` -pub const TARGET: &str = "hx-target"; - -/// Cómo se inserta la respuesta en el DOM. Por defecto, `innerHTML`. -/// -/// Los valores estándar están disponibles en el sub-módulo [`swap`]. Además del modo base, se -/// pueden añadir modificadores separados por espacio: retrasos (`swap:200ms`), tiempo de -/// asentamiento (`settle:300ms`), scroll (`scroll:top`) y foco (`focus-scroll:true`). -/// -/// ```rust,no_run -/// # use pagetop::prelude::*; -/// # use pagetop_htmx::hx; -/// // Reemplaza el elemento completo con una transición de 300 ms. -/// let props = Props::new(hx::SWAP, "outerHTML swap:300ms"); -/// // O usando la constante tipada más los modificadores: -/// let props = Props::new(hx::SWAP, format!("{} swap:300ms", hx::swap::OUTER_HTML)); -/// ``` -pub const SWAP: &str = "hx-swap"; - -/// Sustituye fuera de banda el elemento del DOM cuyo `id` coincida con el indicado. -/// -/// La respuesta del servidor puede incluir elementos marcados con `hx-swap-oob="true"`: HTMX los -/// extrae y actualiza el DOM en la posición correcta, independientemente del objetivo principal. -pub const SWAP_OOB: &str = "hx-swap-oob"; - -/// Selector CSS del fragmento de la respuesta que se insertará en el objetivo. -/// -/// Permite devolver una página completa desde el servidor y que HTMX extraiga sólo la parte -/// relevante, facilitando la reutilización de rutas existentes. -pub const SELECT: &str = "hx-select"; - -/// Selector CSS de fragmentos de la respuesta que se sustituyen fuera de banda. -pub const SELECT_OOB: &str = "hx-select-oob"; - -// **< Trigger >************************************************************************************ - -/// Evento que activa la petición. Por defecto, `click` en botones y links, `change` en inputs. -/// -/// Los valores simples están disponibles en el sub-módulo [`trigger`]. Las expresiones de disparo -/// compuestas se escriben como literales de cadena: -/// -/// ```rust,no_run -/// # use pagetop::prelude::*; -/// # use pagetop_htmx::hx; -/// // Buscar mientras se escribe, con 400 ms de espera y sólo si el valor cambia: -/// let props = Props::new(hx::GET, "/api/search") -/// .with_prop(PropsOp::set(hx::TRIGGER, "keyup changed delay:400ms")) -/// .with_prop(PropsOp::set(hx::TARGET, "#results")); -/// -/// // Disparar una vez al cargar la página: -/// let lazy = Props::new(hx::GET, "/api/stats") -/// .with_prop(PropsOp::set(hx::TRIGGER, "load once")); -/// -/// // Polling cada 5 segundos: -/// let poll = Props::new(hx::GET, "/api/estado") -/// .with_prop(PropsOp::set(hx::TRIGGER, "every 5s")); -/// ``` -pub const TRIGGER: &str = "hx-trigger"; - -/// Convierte en AJAX todos los enlaces y formularios del elemento y sus descendientes. -/// -/// El valor debe ser `"true"` o `"false"`. Cuando vale `"true"`, las respuestas se aplican al -/// `` por defecto; se puede combinar con [`TARGET`] para redirigirlas. -pub const BOOST: &str = "hx-boost"; - -/// Empuja la URL de la respuesta al historial del navegador. -/// -/// Acepta `"true"` (usa la URL de la petición), `"false"` (desactiva) o una URL concreta. Permite -/// navegación con el botón atrás manteniendo el comportamiento SPA. -pub const PUSH_URL: &str = "hx-push-url"; - -/// Reemplaza la URL actual en el historial sin añadir una nueva entrada. -/// -/// Acepta `"true"`, `"false"` o una URL concreta. Útil cuando la petición refina la vista sin que -/// deba ser un paso independiente en el historial. -pub const REPLACE_URL: &str = "hx-replace-url"; - -/// Sincroniza las peticiones del elemento con las de otros elementos. -/// -/// Formato: `"selector:estrategia"`. Estrategias disponibles: `drop` (descarta la nueva si hay una -/// en curso), `abort` (cancela la nueva), `replace` (cancela la anterior), `queue first|last|all` -/// (encola). Ejemplo: `"#form:abort"`. -pub const SYNC: &str = "hx-sync"; - -// **< Request Data >******************************************************************************* - -/// Selector CSS de elementos adicionales cuyos valores se incluyen en la petición. -/// -/// Acepta selectores CSS estándar más las extensiones de HTMX: `"this"`, `"closest X"`, `"find X"`. -/// Útil para incluir campos de un formulario padre en una petición de detalle. -pub const INCLUDE: &str = "hx-include"; - -/// Controla qué parámetros del formulario se envían en la petición. -/// -/// - `"*"` - todos (valor por defecto). -/// - `"none"` - ninguno. -/// - Lista de nombres: `"name surname"` - sólo esos. -/// - Exclusión: `"not surname"` - todos excepto los indicados. -pub const PARAMS: &str = "hx-params"; - -/// Valores extra en JSON que se añaden a los parámetros de la petición. -/// -/// Formato: objeto JSON. Los valores sobreescriben parámetros del formulario con el mismo nombre. -/// Admite JavaScript con el prefijo `js:`: `"js:{date: new Date().toISOString()}"`. -pub const VALS: &str = "hx-vals"; - -/// Cabeceras extra en JSON que se añaden a la petición. -/// -/// Formato: objeto JSON. Permiten enviar contexto (token de sesión, versión de API, etc.) sin -/// exponerlo en los parámetros visibles del formulario. -pub const HEADERS: &str = "hx-headers"; - -/// Codificación de la petición. Por defecto, `"application/x-www-form-urlencoded"`. -/// -/// Usar `"multipart/form-data"` para peticiones que incluyan campos de tipo `file`. -pub const ENCODING: &str = "hx-encoding"; - -// **< Element Behavior >*************************************************************************** - -/// Selector CSS del indicador de carga que se muestra mientras dura la petición. -/// -/// El elemento indicado recibe la clase `htmx-request` durante la petición. Por defecto, si no se -/// especifica, la recibe el propio elemento que realiza la petición. -pub const INDICATOR: &str = "hx-indicator"; - -/// Selector CSS de elementos que se deshabilitan mientras dura la petición. -/// -/// Añade el atributo `disabled` durante la petición y lo elimina al terminar. Evita envíos -/// duplicados al hacer clic varias veces. -pub const DISABLED_ELT: &str = "hx-disabled-elt"; - -/// Muestra un diálogo de confirmación (`window.confirm`) antes de enviar la petición. -/// -/// Si el usuario cancela, la petición no se realiza. El valor es el texto del mensaje. -pub const CONFIRM: &str = "hx-confirm"; - -/// Muestra un `prompt` de texto y envía el resultado como cabecera `HX-Prompt`. -/// -/// Si el usuario cancela el prompt, la petición no se realiza. -pub const PROMPT: &str = "hx-prompt"; - -/// Activa la validación HTML5 del formulario antes de enviar la petición. -/// -/// Si algún campo no supera la validación nativa del navegador, la petición se cancela. -pub const VALIDATE: &str = "hx-validate"; - -/// Preserva el elemento entre respuestas HTMX. -/// -/// El elemento debe tener un `id` único. HTMX no lo destruye ni lo recrea al aplicar la respuesta, -/// manteniendo su estado interno (p. ej. posición de reproducción de un vídeo). -pub const PRESERVE: &str = "hx-preserve"; - -// **< Config and Extensions >********************************************************************** - -/// Activa una o varias extensiones HTMX en el elemento y sus descendientes. -/// -/// Las extensiones se identifican por nombre y se separan con comas. Extensiones comunes: -/// - `"ws"` - soporte WebSocket. -/// - `"sse"` - soporte Server-Sent Events. -/// - `"json-enc"` - codifica la petición como JSON en lugar de form-urlencoded. -/// - `"loading-states"` - gestión avanzada de estados de carga. -pub const EXT: &str = "hx-ext"; - -/// Atributos HTMX que los elementos descendientes NO heredarán de este elemento. -/// -/// Acepta una lista de atributos separados por comas o `"*"` para bloquear toda herencia. -pub const DISINHERIT: &str = "hx-disinherit"; - -/// Atributos HTMX que los elementos descendientes SÍ heredarán (anula [`DISINHERIT`]). -pub const INHERIT: &str = "hx-inherit"; - -/// Opciones de configuración de la petición en JSON. -/// -/// Claves disponibles: `timeout` (ms), `credentials` (`"include"`, `"omit"`...), `noHeaders` -/// (bool), `getWithBody` (bool). -pub const REQUEST: &str = "hx-request"; - -/// Controla si este elemento participa en el historial del navegador. -/// -/// Con el valor `"false"`, las peticiones de este elemento no se guardan en el historial aunque -/// [`PUSH_URL`] esté activo en un elemento padre. -pub const HISTORY: &str = "hx-history"; - -/// Designa el elemento como contenedor del historial del navegador. -/// -/// HTMX guarda y restaura el contenido de este elemento al navegar hacia atrás/adelante. Sólo debe -/// haber un elemento con este atributo en la página. -pub const HISTORY_ELT: &str = "hx-history-elt"; - -/// Desactiva el procesamiento HTMX en el elemento y todos sus descendientes. -/// -/// Útil para aislar zonas del DOM gestionadas por otra librería o para desactivar HTMX en secciones -/// de contenido generado dinámicamente donde no debe intervenir. -pub const DISABLE: &str = "hx-disable"; - -// **< Inline Events (hx-on) >********************************************************************** - -/// Genera `hx-on:{event}` para escuchar eventos nativos del DOM en línea. -/// -/// Es la alternativa de HTMX a los manejadores `on*` de HTML (`onclick`, `onmouseenter`, ...). La -/// diferencia clave está en cómo los trata el navegador bajo una política CSP (*Content Security -/// Policy*): los atributos `on*` son JavaScript en línea y quedan bloqueados si la CSP no incluye -/// `'unsafe-inline'`; en cambio, `hx-on:*` es un atributo de datos que HTMX lee e interpreta desde -/// su propio código ya autorizado, por lo que la CSP no lo bloquea. -/// -/// La CSP puede definirla el servidor en la cabecera HTTP `Content-Security-Policy`, o la -/// aplicación en una etiqueta `` en el `` del -/// documento. En la práctica, pocas aplicaciones configuran una CSP estricta, pero es una buena -/// práctica de seguridad que conviene tener en cuenta. -/// -/// El valor es código JavaScript que se ejecuta cuando el evento se dispara; `event` contiene el -/// objeto del evento. -/// -/// ```rust,no_run -/// # use pagetop::prelude::*; -/// # use pagetop_htmx::hx; -/// let props = Props::new(hx::on("click"), "this.classList.toggle('active')") -/// .with_prop(PropsOp::set(hx::on("mouseenter"), "this.style.opacity='0.8'")); -/// ``` -pub fn on(event: &str) -> String { - format!("hx-on:{event}") -} - -/// Genera `hx-on::{event}` para escuchar eventos propios de HTMX en línea. -/// -/// Los eventos de HTMX usan un doble carácter dos-puntos (`hx-on::evento`). El ciclo de vida -/// completo incluye `before-request`, `after-request`, `before-swap`, `after-swap`, -/// `before-settle`, `after-settle`, `after-on-load`, `history-restore`, entre otros. -/// -/// ```rust,no_run -/// # use pagetop::prelude::*; -/// # use pagetop_htmx::hx; -/// let props = Props::new(hx::on_htmx("before-request"), "console.log('enviando...')") -/// .with_prop(PropsOp::set(hx::on_htmx("after-swap"), "initTooltips()")); -/// ``` -pub fn on_htmx(event: &str) -> String { - format!("hx-on::{event}") -} - -// **< HTMX Request Headers >*********************************************************************** - -/// Nombres de las cabeceras que HTMX envía con cada petición AJAX. -/// -/// Están en minúsculas porque así las normaliza el módulo `http`. Se pueden usar con -/// [`HttpRequest::headers()`](pagetop::web::HttpRequest::headers) para leer sus valores -/// directamente, aunque lo habitual es usar el trait [`HtmxRequestExt`](crate::HtmxRequestExt). -/// -/// ```rust,no_run -/// use pagetop::prelude::*; -/// use pagetop_htmx::hx; -/// -/// async fn handler(request: HttpRequest) { -/// if let Some(target) = request.headers().get(hx::request::TARGET) { -/// // El elemento objetivo tenía este id. -/// } -/// } -/// ``` -pub mod request { - /// Siempre `"true"` en peticiones HTMX. Permite distinguirlas de navegaciones directas. - pub const REQUEST: &str = "hx-request"; - /// `"true"` si la petición viene de un enlace o formulario con `hx-boost`. - pub const BOOSTED: &str = "hx-boosted"; - /// URL de la página activa en el navegador cuando se realizó la petición. - pub const CURRENT_URL: &str = "hx-current-url"; - /// `"true"` si la petición es una restauración del historial del navegador. - pub const HISTORY_RESTORE_REQUEST: &str = "hx-history-restore-request"; - /// Texto introducido por el usuario en un diálogo `hx-prompt`. - pub const PROMPT: &str = "hx-prompt"; - /// Valor del atributo `id` del elemento objetivo de la petición. - pub const TARGET: &str = "hx-target"; - /// Valor del atributo `id` del elemento que disparó la petición. - pub const TRIGGER: &str = "hx-trigger"; - /// Valor del atributo `name` del elemento que disparó la petición. - pub const TRIGGER_NAME: &str = "hx-trigger-name"; -} - -// **< HTMX Response Headers >********************************************************************** - -/// Cabeceras de respuesta HTTP para HTMX. -/// -/// Se pueden usar con [`HeaderMap`](pagetop::web::http::HeaderMap) para construir respuestas -/// manualmente, aunque lo habitual es usar el constructor [`HtmxResponse`](crate::HtmxResponse). -/// -/// ```rust,no_run -/// use pagetop_htmx::hx; -/// use pagetop::web::http::{HeaderMap, HeaderName, HeaderValue}; -/// -/// let mut headers = HeaderMap::new(); -/// headers.insert( -/// hx::response::TRIGGER.parse::().unwrap(), -/// HeaderValue::from_static("itemAdded"), -/// ); -/// ``` -pub mod response { - /// Redirige mediante AJAX a la URL o configuración JSON indicada. Ver - /// [`HtmxResponse::location()`](crate::HtmxResponse::location). - pub const LOCATION: &str = "HX-Location"; - /// Empuja la URL indicada al historial del navegador. Ver - /// [`HtmxResponse::push_url()`](crate::HtmxResponse::push_url). - pub const PUSH_URL: &str = "HX-Push-Url"; - /// Provoca una redirección completa del navegador. Ver - /// [`HtmxResponse::redirect()`](crate::HtmxResponse::redirect). - pub const REDIRECT: &str = "HX-Redirect"; - /// Provoca una recarga completa de la página. Ver - /// [`HtmxResponse::refresh()`](crate::HtmxResponse::refresh). - pub const REFRESH: &str = "HX-Refresh"; - /// Reemplaza la URL actual en el historial. Ver - /// [`HtmxResponse::replace_url()`](crate::HtmxResponse::replace_url). - pub const REPLACE_URL: &str = "HX-Replace-Url"; - /// Anula el `hx-swap` del elemento. Ver - /// [`HtmxResponse::reswap()`](crate::HtmxResponse::reswap). - pub const RESWAP: &str = "HX-Reswap"; - /// Anula el `hx-target` del elemento. Ver - /// [`HtmxResponse::retarget()`](crate::HtmxResponse::retarget). - pub const RETARGET: &str = "HX-Retarget"; - /// Anula el `hx-select` del elemento. Ver - /// [`HtmxResponse::reselect()`](crate::HtmxResponse::reselect). - pub const RESELECT: &str = "HX-Reselect"; - /// Dispara eventos JavaScript al completar la respuesta. Ver - /// [`HtmxResponse::trigger()`](crate::HtmxResponse::trigger). - pub const TRIGGER: &str = "HX-Trigger"; - /// Dispara eventos tras la fase *settle*. Ver - /// [`HtmxResponse::trigger_after_settle()`](crate::HtmxResponse::trigger_after_settle). - pub const TRIGGER_AFTER_SETTLE: &str = "HX-Trigger-After-Settle"; - /// Dispara eventos tras el *swap*. Ver - /// [`HtmxResponse::trigger_after_swap()`](crate::HtmxResponse::trigger_after_swap). - pub const TRIGGER_AFTER_SWAP: &str = "HX-Trigger-After-Swap"; -} - -// **< hx-swap Values >***************************************************************************** - -/// Valores estándar del atributo [`SWAP`] (`hx-swap`). -/// -/// Se pueden combinar con modificadores separados por espacio: -/// - `swap:Xms` - tiempo de espera antes de realizar el intercambio. -/// - `settle:Xms` - tiempo de espera antes de quitar las clases de transición. -/// - `scroll:top` / `scroll:bottom` - desplaza el objetivo tras el intercambio. -/// - `show:top` / `show:bottom` - hace visible el objetivo tras el intercambio. -/// - `focus-scroll:true` - sigue al elemento enfocado. -/// -/// ```rust,no_run -/// # use pagetop::prelude::*; -/// # use pagetop_htmx::hx; -/// // Reemplaza el elemento con una transición de 200 ms y desplaza al inicio: -/// let props = Props::new(hx::SWAP, format!("{} swap:200ms scroll:top", hx::swap::OUTER_HTML)); -/// ``` -pub mod swap { - /// Reemplaza el contenido interior del objetivo (valor por defecto de HTMX). - pub const INNER_HTML: &str = "innerHTML"; - /// Reemplaza el elemento objetivo completo. - pub const OUTER_HTML: &str = "outerHTML"; - /// Inserta la respuesta antes de la etiqueta de apertura del objetivo. - pub const BEFORE_BEGIN: &str = "beforebegin"; - /// Inserta la respuesta al inicio del contenido del objetivo. - pub const AFTER_BEGIN: &str = "afterbegin"; - /// Inserta la respuesta al final del contenido del objetivo. - pub const BEFORE_END: &str = "beforeend"; - /// Inserta la respuesta después de la etiqueta de cierre del objetivo. - pub const AFTER_END: &str = "afterend"; - /// Elimina el elemento objetivo independientemente de la respuesta. - pub const DELETE: &str = "delete"; - /// No realiza ningún intercambio; útil cuando sólo importan las cabeceras de respuesta. - pub const NONE: &str = "none"; -} - -// **< hx-trigger Values >************************************************************************** - -/// Eventos comunes del atributo [`TRIGGER`] (`hx-trigger`). -/// -/// Estos valores cubren los disparadores más simples. Las expresiones de disparo compuestas deben -/// escribirse como literales de cadena. Modificadores disponibles: -/// - `once` - se dispara sólo la primera vez. -/// - `changed` - sólo si el valor del elemento ha cambiado. -/// - `delay:Xms` - espera antes de disparar (se cancela si el evento vuelve a ocurrir). -/// - `throttle:Xms` - limita la frecuencia máxima de disparo. -/// - `from:selector` - escucha el evento en otro elemento. -/// - `target:selector` - sólo si el evento viene del selector indicado. -/// - `consume` - evita que el evento se propague a otros elementos HTMX. -/// - `queue:first|last|all|none` - política de cola cuando llegan eventos consecutivos. -/// -/// ```rust,no_run -/// # use pagetop::prelude::*; -/// # use pagetop_htmx::hx; -/// // Búsqueda progresiva: petición 400 ms después de que el usuario deje de escribir. -/// let search = Props::new(hx::TRIGGER, "keyup changed delay:400ms"); -/// -/// // Carga diferida al entrar en el viewport, una sola vez. -/// let lazy = Props::new(hx::TRIGGER, "intersect once"); -/// -/// // Polling: actualiza cada 10 segundos mientras el elemento esté en el DOM. -/// let poll = Props::new(hx::TRIGGER, "every 10s"); -/// -/// // Múltiples eventos: clic o pulsación de Enter en el campo. -/// let multi = Props::new(hx::TRIGGER, "click, keyup[key=='Enter']"); -/// -/// // Escucha un evento personalizado emitido desde otro elemento. -/// let custom = Props::new(hx::TRIGGER, "itemAdded from:body"); -/// ``` -pub mod trigger { - /// Se dispara al hacer clic (valor por defecto en la mayoría de elementos interactivos). - pub const CLICK: &str = "click"; - /// Se dispara cuando el valor del elemento cambia (valor por defecto en `input`/`select`). - pub const CHANGE: &str = "change"; - /// Se dispara al enviar un formulario. - pub const SUBMIT: &str = "submit"; - /// Se dispara al soltar una tecla. - pub const KEYUP: &str = "keyup"; - /// Se dispara cuando la página termina de cargarse. - pub const LOAD: &str = "load"; - /// Se dispara cuando el elemento entra en el área visible del *viewport* al hacer scroll. - pub const REVEALED: &str = "revealed"; - /// Se dispara cuando el elemento intersecta con el *viewport* (Intersection Observer API). - pub const INTERSECT: &str = "intersect"; -} diff --git a/extensions/pagetop-htmx/src/lib.rs b/extensions/pagetop-htmx/src/lib.rs deleted file mode 100644 index e65f310c..00000000 --- a/extensions/pagetop-htmx/src/lib.rs +++ /dev/null @@ -1,129 +0,0 @@ -/*! -
- -

PageTop HTMX

- -

Extensión para PageTop que integra HTMX para enriquecer las páginas con interacciones dinámicas.

- -[![Doc API](https://img.shields.io/docsrs/pagetop-htmx?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-htmx) -[![Crates.io](https://img.shields.io/crates/v/pagetop-htmx.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-htmx) -[![Descargas](https://img.shields.io/crates/d/pagetop-htmx.svg?label=Descargas&style=for-the-badge&logo=transmission)](https://crates.io/crates/pagetop-htmx) -[![Licencia](https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=Licencia&style=for-the-badge)](https://git.cillero.es/manuelcillero/pagetop/src/branch/main/extensions/pagetop-htmx#licencia) - -
- -## Sobre PageTop - -[PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web -clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y -configurables, basadas en HTML, CSS y JavaScript. - -## Guía rápida - -**Añade la dependencia** a tu `Cargo.toml`: - -```toml -[dependencies] -pagetop-htmx = { ... } -``` - -**Declara la extensión** en tu aplicación (o extensión que la requiera). Recuerda que el orden en -`dependencies()` determina la prioridad relativa frente a las otras extensiones: - -```rust -use pagetop::prelude::*; - -struct MyApp; - -impl Extension for MyApp { - fn dependencies(&self) -> Vec { - vec![ - // ... - &pagetop_htmx::Htmx - // ... - ] - } -} -``` - -A partir de ese momento, todas las páginas de la aplicación incluirán automáticamente el script de -HTMX 2. Puedes usar los atributos `hx-*` directamente en tus componentes o el código HTML generado: - -```rust -use pagetop::prelude::*; - -async fn homepage(request: HttpRequest) -> Result { - Page::new(request) - .with_child(Html::with(|_| html! { - button hx-get="/api/hello" hx-target="#result" { - "Say hello" - } - div #result {} - })) - .render() -} -``` -*/ - -use pagetop::prelude::*; - -pub mod hx; - -mod request; -pub use request::HtmxRequestExt; - -mod response; -pub use response::HtmxResponse; - -include_locales!(LOCALES_HTMX); - -/// Integra HTMX 2 en cualquier aplicación PageTop. -/// -/// Poner esta extensión en [`dependencies()`](pagetop::core::extension::Extension::dependencies) -/// hace que todas las páginas de la aplicación incluyan automáticamente el script de HTMX mediante -/// un atributo [`defer`](pagetop::html::JavaScript::defer). No es necesaria ninguna configuración -/// adicional. -/// -/// # Ejemplo -/// -/// ```rust,no_run -/// use pagetop::prelude::*; -/// -/// struct MyApp; -/// -/// impl Extension for MyApp { -/// fn dependencies(&self) -> Vec { -/// vec![ -/// // ... -/// &pagetop_htmx::Htmx -/// // ... -/// ] -/// } -/// } -/// ``` -pub struct Htmx; - -impl Extension for Htmx { - fn name(&self) -> L10n { - L10n::t("extension_name", &LOCALES_HTMX) - } - - fn description(&self) -> L10n { - L10n::t("extension_description", &LOCALES_HTMX) - } - - fn actions(&self) -> Vec { - actions![action::page::BeforeRenderBody::new(add_htmx_script)] - } - - fn configure_router(&self, router: Router) -> Router { - serve_static_files!(router, [htmx] => "/htmx"); - router - } -} - -fn add_htmx_script(page: &mut Page) { - page.alter_assets(AssetsOp::AddJavaScript( - JavaScript::defer("/htmx/js/htmx.min.js").with_version("2.0.10"), - )); -} diff --git a/extensions/pagetop-htmx/src/locale/en-US/extension.ftl b/extensions/pagetop-htmx/src/locale/en-US/extension.ftl deleted file mode 100644 index c6580f02..00000000 --- a/extensions/pagetop-htmx/src/locale/en-US/extension.ftl +++ /dev/null @@ -1,2 +0,0 @@ -extension_name = HTMX -extension_description = Integrates HTMX to enrich pages with dynamic interactions. diff --git a/extensions/pagetop-htmx/src/locale/es-ES/extension.ftl b/extensions/pagetop-htmx/src/locale/es-ES/extension.ftl deleted file mode 100644 index 1a32e44a..00000000 --- a/extensions/pagetop-htmx/src/locale/es-ES/extension.ftl +++ /dev/null @@ -1,2 +0,0 @@ -extension_name = HTMX -extension_description = Integra HTMX para enriquecer las páginas con interacciones dinámicas. \ No newline at end of file diff --git a/extensions/pagetop-htmx/src/request.rs b/extensions/pagetop-htmx/src/request.rs deleted file mode 100644 index 32b68f86..00000000 --- a/extensions/pagetop-htmx/src/request.rs +++ /dev/null @@ -1,132 +0,0 @@ -//! Implementación de [`HtmxRequestExt`] para [`pagetop::web::HttpRequest`]. - -use pagetop::prelude::*; - -// **< HtmxRequestExt >***************************************************************************** - -/// Extiende [`HttpRequest`](pagetop::web::HttpRequest) con métodos para detectar y leer peticiones -/// HTMX. -/// -/// HTMX añade cabeceras especiales a cada petición AJAX. Este trait permite acceder a ellas de -/// forma expresiva, sin manipular [`pagetop::web::http::HeaderMap`] directamente. -/// -/// El patrón más común es devolver una respuesta distinta según si la petición viene de HTMX -/// (fragmento parcial) o de una navegación directa (página completa). Cuando las dos ramas retornan -/// tipos distintos, se usa [`pagetop::web::Response`] como tipo común: -/// -/// ```rust,no_run -/// use pagetop::prelude::*; -/// use pagetop_htmx::{HtmxRequestExt, HtmxResponse}; -/// -/// async fn list_items(request: HttpRequest) -> Response { -/// if request.is_htmx() { -/// // Fragmento parcial con cabeceras HTMX opcionales. -/// HtmxResponse::new(html! { ul { li { "Item 1" } li { "Item 2" } } }) -/// .into_response() -/// } else { -/// // Página completa para navegación directa. -/// Page::new(request) -/// .with_child(Html::with(|_| html! { -/// ul { li { "Item 1" } li { "Item 2" } } -/// })) -/// .render() -/// .into_response() -/// } -/// } -/// ``` -/// -/// Los nombres de cabecera como constantes están en [`crate::hx::request`]. -pub trait HtmxRequestExt { - /// Devuelve `true` si la petición proviene de HTMX (`HX-Request: true`). - /// - /// Es la comprobación principal para distinguir una petición HTMX de una navegación directa del - /// navegador. - fn is_htmx(&self) -> bool; - - /// Devuelve `true` si la petición proviene de un enlace o formulario con `hx-boost`. - /// - /// Las peticiones boosted son HTMX pero conservan la semántica de navegación completa: el - /// objetivo por defecto es ``. Puede ser útil para no devolver un fragmento parcial en - /// este caso. - fn is_boosted(&self) -> bool; - - /// Devuelve `true` si la petición es una restauración del historial del navegador. - /// - /// Ocurre cuando el usuario navega hacia atrás o adelante y HTMX necesita restaurar el estado - /// de la página. En este caso conviene devolver la página completa. - fn is_history_restore(&self) -> bool; - - /// URL de la página activa en el navegador en el momento de la petición (`HX-Current-URL`). - /// - /// Útil para redirigir o actualizar la URL del historial en función de dónde estaba el usuario. - fn hx_current_url(&self) -> Option<&str>; - - /// Valor del atributo `id` del elemento objetivo de la petición (`HX-Target`). - /// - /// Si el elemento objetivo no tiene `id`, esta cabecera no se envía. - fn hx_target(&self) -> Option<&str>; - - /// Valor del atributo `id` del elemento que disparó la petición (`HX-Trigger`). - /// - /// Si el elemento disparador no tiene `id`, esta cabecera no se envía. Ver también - /// [`hx_trigger_name()`](Self::hx_trigger_name). - fn hx_trigger_id(&self) -> Option<&str>; - - /// Valor del atributo `name` del elemento que disparó la petición (`HX-Trigger-Name`). - /// - /// Especialmente útil en formularios, donde el elemento disparador puede tener `name` pero no - /// `id`. - fn hx_trigger_name(&self) -> Option<&str>; - - /// Texto introducido por el usuario en un diálogo `hx-prompt` (`HX-Prompt`). - /// - /// Sólo presente si el elemento tiene el atributo `hx-prompt` y el usuario no canceló el - /// diálogo. - fn hx_prompt(&self) -> Option<&str>; -} - -impl HtmxRequestExt for HttpRequest { - fn is_htmx(&self) -> bool { - header_equals(self.headers(), "hx-request", "true") - } - - fn is_boosted(&self) -> bool { - header_equals(self.headers(), "hx-boosted", "true") - } - - fn is_history_restore(&self) -> bool { - header_equals(self.headers(), "hx-history-restore-request", "true") - } - - fn hx_current_url(&self) -> Option<&str> { - header_str(self.headers(), "hx-current-url") - } - - fn hx_target(&self) -> Option<&str> { - header_str(self.headers(), "hx-target") - } - - fn hx_trigger_id(&self) -> Option<&str> { - header_str(self.headers(), "hx-trigger") - } - - fn hx_trigger_name(&self) -> Option<&str> { - header_str(self.headers(), "hx-trigger-name") - } - - fn hx_prompt(&self) -> Option<&str> { - header_str(self.headers(), "hx-prompt") - } -} - -fn header_equals(headers: &web::http::HeaderMap, name: &str, expected: &str) -> bool { - headers - .get(name) - .and_then(|v| v.to_str().ok()) - .map(|v| v == expected) - .unwrap_or(false) -} - -fn header_str<'a>(headers: &'a web::http::HeaderMap, name: &str) -> Option<&'a str> { - headers.get(name).and_then(|v| v.to_str().ok()) -} diff --git a/extensions/pagetop-htmx/src/response.rs b/extensions/pagetop-htmx/src/response.rs deleted file mode 100644 index 66550cf2..00000000 --- a/extensions/pagetop-htmx/src/response.rs +++ /dev/null @@ -1,228 +0,0 @@ -//! Implementación de [`HtmxResponse`] e [`IntoResponse`](pagetop::web::IntoResponse) para HTMX. - -use pagetop::prelude::*; - -// **< HtmxResponse >******************************************************************************* - -/// Generador de respuestas HTML parciales con cabeceras HTMX. -/// -/// En una aplicación HTMX, los *handlers* del servidor devuelven con frecuencia fragmentos HTML -/// parciales acompañados de cabeceras especiales que instruyen al cliente sobre qué hacer con la -/// respuesta: actualizar la URL del historial, disparar eventos JavaScript, redirigir, etc. -/// -/// Implementa [`IntoResponse`](pagetop::web::IntoResponse), por lo que puede devolverse -/// directamente desde cualquier *handler*. -/// -/// # Ejemplo -/// -/// ```rust,no_run -/// use pagetop::prelude::*; -/// use pagetop_htmx::{HtmxResponse, hx}; -/// -/// async fn add_item(request: HttpRequest) -> impl IntoResponse { -/// let new_item = html! { li #item-42 { "New item" } }; -/// -/// HtmxResponse::new(new_item) -/// .retarget("#list") -/// .reswap(hx::swap::BEFORE_END) -/// .push_url("/items") -/// .trigger("itemAdded") -/// } -/// ``` -/// -/// # Respuestas de sólo cabeceras -/// -/// Cuando la respuesta no lleva cuerpo HTML (por ejemplo, una redirección o un refresco), usa -/// [`HtmxResponse::empty()`](Self::empty): -/// -/// ```rust,no_run -/// use pagetop::prelude::*; -/// use pagetop_htmx::HtmxResponse; -/// -/// async fn delete_item() -> impl IntoResponse { -/// HtmxResponse::empty().redirect("/items") -/// } -/// ``` -/// -/// # Construcción -/// -/// - [`HtmxResponse::new(markup)`](Self::new), con el fragmento HTML. -/// - [`HtmxResponse::empty()`](Self::empty), sin cuerpo, sólo cabeceras. -/// -/// # Cabeceras disponibles -/// -/// Los nombres de cabecera como constantes están en [`crate::hx::response`]. -/// -/// # Múltiples eventos en `trigger` -/// -/// Para disparar varios eventos en una sola llamada, pasa una cadena con comas o un objeto JSON: -/// -/// ```rust,no_run -/// use pagetop::prelude::*; -/// use pagetop_htmx::HtmxResponse; -/// -/// // Dos eventos sin datos: -/// HtmxResponse::empty().trigger("itemAdded, listUpdated"); -/// -/// // Evento con datos en JSON: -/// HtmxResponse::empty().trigger(r#"{"itemAdded": {"id": 42}}"#); -/// ``` -pub struct HtmxResponse { - markup: Markup, - headers: web::http::HeaderMap, -} - -impl HtmxResponse { - /// Crea una respuesta con el fragmento HTML indicado. - pub fn new(markup: Markup) -> Self { - Self { - markup, - headers: web::http::HeaderMap::new(), - } - } - - /// Crea una respuesta sin cuerpo HTML, útil para respuestas de sólo cabeceras. - pub fn empty() -> Self { - Self::new(html! {}) - } - - // **< HtmxResponse BUILDER >******************************************************************* - - /// Hace que HTMX realice una navegación AJAX a la URL indicada sin recargar la página. - /// - /// A diferencia de [`redirect()`](Self::redirect), la navegación usa HTMX y actualiza sólo el - /// objetivo definido por el destino. Acepta una URL o un objeto JSON con claves `path`, - /// `target`, `swap`, `select` y `values` para personalizar la navegación: - /// - /// ```rust,no_run - /// use pagetop::prelude::*; - /// use pagetop_htmx::HtmxResponse; - /// - /// // Navegación simple: - /// HtmxResponse::empty().location("/items"); - /// - /// // Navegación con destino personalizado: - /// HtmxResponse::empty() - /// .location(r##"{"path": "/items", "target": "#content"}"##); - /// ``` - pub fn location(self, url: impl Into) -> Self { - self.set_header(b"hx-location", url) - } - - /// Empuja la URL indicada al historial del navegador. - /// - /// El usuario podrá navegar hacia atrás hasta esa URL. Usar `"false"` para desactivar el empuje - /// aunque esté habilitado por el atributo `hx-push-url` del elemento. - pub fn push_url(self, url: impl Into) -> Self { - self.set_header(b"hx-push-url", url) - } - - /// Reemplaza la URL actual en el historial sin añadir una nueva entrada. - /// - /// Usar `"false"` para desactivar el reemplazo. - pub fn replace_url(self, url: impl Into) -> Self { - self.set_header(b"hx-replace-url", url) - } - - /// Provoca una redirección completa del navegador a la URL indicada. - /// - /// A diferencia de [`location()`](Self::location), esta redirección recarga la página por - /// completo, como un `window.location.href = url` en JavaScript. - pub fn redirect(self, url: impl Into) -> Self { - self.set_header(b"hx-redirect", url) - } - - /// Provoca una recarga completa de la página actual. - /// - /// Equivale a `window.location.reload()` en JavaScript. - pub fn refresh(self) -> Self { - self.set_header(b"hx-refresh", "true") - } - - /// Anula el `hx-target` del elemento y redirige la respuesta al selector CSS indicado. - /// - /// Útil cuando el servidor necesita actualizar un elemento distinto al que realizó la petición, - /// sin modificar el HTML del cliente. - pub fn retarget(self, selector: impl Into) -> Self { - self.set_header(b"hx-retarget", selector) - } - - /// Anula el `hx-swap` del elemento e impone la estrategia de sustitución indicada. - /// - /// Acepta los mismos valores que el atributo `hx-swap`, incluidos modificadores (`swap:200ms`, - /// `scroll:top`, ...). Los valores tipados están en [`crate::hx::swap`]. - pub fn reswap(self, strategy: impl Into) -> Self { - self.set_header(b"hx-reswap", strategy) - } - - /// Anula el `hx-select` del elemento y selecciona el fragmento CSS indicado de la respuesta - /// para insertarlo en el objetivo. - pub fn reselect(self, selector: impl Into) -> Self { - self.set_header(b"hx-reselect", selector) - } - - /// Dispara uno o varios eventos JavaScript en el cliente al completar la respuesta. - /// - /// Los eventos se disparan inmediatamente tras procesar la respuesta. Para disparar eventos con - /// datos o después de otras fases del ciclo HTMX, ver - /// [`trigger_after_settle()`](Self::trigger_after_settle) y - /// [`trigger_after_swap()`](Self::trigger_after_swap). - /// - /// ```rust,no_run - /// use pagetop::prelude::*; - /// use pagetop_htmx::HtmxResponse; - /// - /// // Evento simple: - /// HtmxResponse::empty().trigger("itemAdded"); - /// - /// // Múltiples eventos sin datos: - /// HtmxResponse::empty().trigger("itemAdded, listUpdated"); - /// - /// // Evento con datos en JSON: - /// HtmxResponse::empty().trigger(r#"{"itemAdded": {"id": 42, "name": "Example"}}"#); - /// ``` - pub fn trigger(self, event: impl Into) -> Self { - self.set_header(b"hx-trigger", event) - } - - /// Dispara eventos JavaScript después de que HTMX haya aplicado la respuesta al DOM y haya - /// completado la fase de *settle* (animaciones CSS). - /// - /// Acepta los mismos formatos que [`trigger()`](Self::trigger). - pub fn trigger_after_settle(self, event: impl Into) -> Self { - self.set_header(b"hx-trigger-after-settle", event) - } - - /// Dispara eventos JavaScript después de que HTMX haya aplicado la respuesta al DOM, pero antes - /// de la fase de *settle*. - /// - /// Acepta los mismos formatos que [`trigger()`](Self::trigger). - pub fn trigger_after_swap(self, event: impl Into) -> Self { - self.set_header(b"hx-trigger-after-swap", event) - } - - // Inserta o reemplaza una cabecera. Los nombres deben ser bytes ASCII en minúsculas. - fn set_header(mut self, name: &[u8], value: impl Into) -> Self { - let value = value.into(); - if let (Ok(n), Ok(v)) = ( - web::http::HeaderName::from_bytes(name), - web::http::HeaderValue::from_str(&value), - ) { - self.headers.insert(n, v); - } else { - trace::warn!(value = %value, "HtmxResponse: invalid header value, header discarded"); - } - self - } -} - -impl web::IntoResponse for HtmxResponse { - fn into_response(self) -> Response { - let mut headers = self.headers; - headers.insert( - web::http::header::CONTENT_TYPE, - web::http::HeaderValue::from_static("text/html; charset=utf-8"), - ); - (headers, self.markup.into_string()).into_response() - } -} diff --git a/extensions/pagetop-seaorm/README.md b/extensions/pagetop-seaorm/README.md index b9b7e1a4..23a4ce40 100644 --- a/extensions/pagetop-seaorm/README.md +++ b/extensions/pagetop-seaorm/README.md @@ -11,13 +11,14 @@ -## Sobre PageTop +## 🧭 Sobre PageTop [PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y configurables, basadas en HTML, CSS y JavaScript. -## Guía rápida + +## ⚡️ Guía rápida **Añade la dependencia** a tu `Cargo.toml` activando el motor de base de datos que necesites: @@ -58,7 +59,7 @@ impl Extension for MyApp { } fn initialize(&self) { - install_migrations!(m20240101_000001_create_users); + install_migrations!(m20240101_000001_create_users_table); } } @@ -145,7 +146,8 @@ async fn example() -> Result<(), DbErr> { } ``` -## Créditos + +## 📚 Créditos Este *crate* se apoya en bibliotecas del ecosistema [SeaQL](https://github.com/SeaQL) como: @@ -177,13 +179,15 @@ extensión. Los ficheros adaptados del original son: | `schema.rs` | Integra con ajustes, adaptado de [loco](https://github.com/loco-rs/loco) | | `seaql_migrations.rs` | Integración completa | -## Advertencia + +## 🚧 Advertencia **PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos hasta que se libere la versión **1.0.0**. -## Licencia + +## 📜 Licencia El código está disponible bajo una doble licencia: diff --git a/extensions/pagetop-seaorm/src/db.rs b/extensions/pagetop-seaorm/src/db.rs index 3076d834..a2b5dd4d 100644 --- a/extensions/pagetop-seaorm/src/db.rs +++ b/extensions/pagetop-seaorm/src/db.rs @@ -3,7 +3,7 @@ //! Agrupa los *traits*, macros y tipos del sistema de entidades de SeaORM, junto con las funciones //! [`dbconn`], [`execute`], [`fetch_all`] y [`fetch_one`], en una sola importación: //! -//! ```rust,no_run +//! ```rust //! use pagetop_seaorm::db::*; //! ``` //! @@ -34,7 +34,7 @@ //! //! ## Definir una entidad //! -//! ```rust,no_run +//! ```rust //! use pagetop_seaorm::db::*; //! //! #[derive(Clone, Debug, PartialEq, DeriveEntityModel)] @@ -111,7 +111,7 @@ //! El módulo [`api`] re-exporta el crate `sea_orm` íntegro bajo ese alias. Úsalo cuando necesites //! un tipo o función que no esté expuesto directamente en `db::*`: //! -//! ```rust,no_run +//! ```rust //! use pagetop_seaorm::db::api; //! //! // Tipos o utilidades no incluidos en db::*: @@ -123,7 +123,7 @@ //! El módulo [`query`] re-exporta `sea_query` para construir las sentencias SQL que se pasan a //! [`fetch_all`] y [`fetch_one`]. Es el compañero natural de esas funciones dentro del módulo `db`: //! -//! ```rust,no_run +//! ```rust //! use pagetop_seaorm::db::*; //! use pagetop_seaorm::db::query::*; //! @@ -193,7 +193,7 @@ pub fn dbconn() -> &'static DatabaseConnection { /// > **Advertencia:** nunca interpoles valores externos en la cadena SQL directamente. Para /// > sentencias con parámetros de usuario usa el sistema de entidades. /// -/// ```rust,no_run +/// ```rust /// use pagetop_seaorm::db::*; /// /// async fn example() -> Result<(), DbErr> { @@ -224,7 +224,7 @@ pub async fn execute(stmt: impl Into) -> Result { /// Los valores se integran como literales escapados, no como parámetros de base de datos. Para /// datos procedentes del usuario, el sistema de entidades es más robusto. /// -/// ```rust,no_run +/// ```rust /// use pagetop_seaorm::db::*; /// use pagetop_seaorm::db::query::*; /// @@ -274,7 +274,7 @@ pub async fn fetch_all( /// Los valores se integran como literales escapados, no como parámetros de base de datos. Para /// datos procedentes del usuario, el sistema de entidades es más robusto. /// -/// ```rust,no_run +/// ```rust /// use pagetop_seaorm::db::*; /// use pagetop_seaorm::db::query::*; /// diff --git a/extensions/pagetop-seaorm/src/lib.rs b/extensions/pagetop-seaorm/src/lib.rs index 0d47dc1a..ef056f64 100644 --- a/extensions/pagetop-seaorm/src/lib.rs +++ b/extensions/pagetop-seaorm/src/lib.rs @@ -12,13 +12,14 @@ -## Sobre PageTop +## 🧭 Sobre PageTop [PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y configurables, basadas en HTML, CSS y JavaScript. -## Guía rápida + +## ⚡️ Guía rápida **Añade la dependencia** a tu `Cargo.toml` activando el motor de base de datos que necesites: @@ -148,7 +149,7 @@ async fn example() -> Result<(), DbErr> { */ #![doc( - html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/assets/favicon.ico" + html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/static/favicon.ico" )] use pagetop::prelude::*; diff --git a/extensions/pagetop-seaorm/src/migration.rs b/extensions/pagetop-seaorm/src/migration.rs index 2e267e8d..f3544f7f 100644 --- a/extensions/pagetop-seaorm/src/migration.rs +++ b/extensions/pagetop-seaorm/src/migration.rs @@ -6,7 +6,7 @@ //! //! Con una sola importación tienes todo lo necesario: //! -//! ```rust,no_run +//! ```rust //! use pagetop_seaorm::migration::*; //! ``` //! diff --git a/helpers/pagetop-build/Cargo.toml b/helpers/pagetop-build/Cargo.toml index 0e2dd844..a06bc9ca 100644 --- a/helpers/pagetop-build/Cargo.toml +++ b/helpers/pagetop-build/Cargo.toml @@ -3,7 +3,8 @@ name = "pagetop-build" version = "0.3.2" description = """ - Genera o prepara archivos estáticos para servirlos o incluirlos en un proyecto PageTop. + Prepara un conjunto de archivos estáticos o archivos SCSS compilados para ser incluidos en el + binario de un proyecto PageTop. """ categories = ["development-tools::build-utils"] keywords = ["pagetop", "build", "assets", "resources", "static"] @@ -16,5 +17,4 @@ authors.workspace = true [dependencies] grass.workspace = true -minify-js.workspace = true pagetop-statics.workspace = true diff --git a/helpers/pagetop-build/README.md b/helpers/pagetop-build/README.md index e33bf8f0..bb7d3bfa 100644 --- a/helpers/pagetop-build/README.md +++ b/helpers/pagetop-build/README.md @@ -2,7 +2,7 @@

PageTop Build

-

Genera o prepara archivos estáticos para servirlos o incluirlos en un proyecto PageTop.

+

Prepara un conjunto de archivos estáticos o archivos SCSS compilados para ser incluidos en el binario de un proyecto PageTop.

[![Doc API](https://img.shields.io/docsrs/pagetop-build?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-build) [![Crates.io](https://img.shields.io/crates/v/pagetop-build.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-build) @@ -11,85 +11,91 @@ -## Sobre PageTop +## 🧭 Sobre PageTop [PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y configurables, basadas en HTML, CSS y JavaScript. -## Guía rápida -La convención recomendada para extensiones, temas o aplicaciones basadas en **PageTop** es separar -los archivos fuente de los generados siguiendo el patrón `assets/` -> `static/`: +## ⚡️ Guía rápida -- **`assets/`** - archivos versionados en el repositorio, por ejemplo archivos SCSS, JavaScript de - terceros, fuentes, etc. Todo lo que hay aquí se sube al repositorio y será la fuente para generar - el directorio final `static/`. -- **`static/`** - archivos generados en tiempo de compilación a partir de `assets/`. Se añade a - `.gitignore` y nunca se sube al repositorio. -- **`build.rs`** - orquesta la transformación: genera `static/` desde `assets/` para servirlos o - incluirlos en el proyecto. +Añadir en el archivo `Cargo.toml` del proyecto: -Durante el desarrollo, `static/` existe en disco y los archivos se sirven desde ahí. En producción, -el directorio no existe y los recursos salen del binario. La macro -[`serve_static_files!`](https://docs.rs/pagetop/latest/pagetop/macro.serve_static_files.html) -gestiona esta dualidad de forma transparente. +```toml +[build-dependencies] +pagetop-build = { ... } +``` -### Funciones de transformación +Y crear un archivo `build.rs` a la altura de `Cargo.toml` para indicar cómo se van a incluir los +archivos estáticos o cómo se van a compilar los archivos SCSS para el proyecto. Casos de uso: -Estas funciones se usan en el `build.rs` de cada proyecto para generar `static/` a partir de -`assets/`. Todas crean el directorio padre del destino si no existe y devuelven `io::Result<()>` -para poder propagarse con `?` en caso de error. +### Incluir archivos estáticos desde un directorio -- `compile_scss()` - compila un archivo SCSS a CSS minificado. -- `copy_dir()` - copia recursivamente un directorio completo. Útil para copiar todos los archivos de - `assets/` o de un subdirectorio a `static/` sin transformación. -- `copy_file()` - copia un archivo al destino. -- `copy_file_replacing()` - copia un archivo aplicando una lista de sustituciones de texto en su - contenido; útil para actualizar referencias internas (p.ej. `sourceMappingURL`) al renombrar - archivos. -- `minify_js()` - minifica un archivo JavaScript. - -### Incluir los archivos estáticos en el proyecto - -Una vez generado `static/`, usaremos `StaticFilesBundle` para incluir su contenido en el binario. Se -pueden crear tantos paquetes de recursos como sea necesario, siempre que tengan nombres distintos: +Hay que preparar una carpeta en el proyecto con todos los archivos que se quieren incluir, por +ejemplo `static`, y añadir el siguiente código en `build.rs` para crear el conjunto de recursos: ```rust,no_run use pagetop_build::StaticFilesBundle; fn main() -> std::io::Result<()> { - StaticFilesBundle::from_dir("./static/css", None) - .with_name("app_css") - .build()?; - StaticFilesBundle::from_dir("./static/fonts", None) - .with_name("app_fonts") + StaticFilesBundle::from_dir("./static", None) + .with_name("guides") .build() } ``` -Si es necesario excluir algunos archivos del paquete de recursos (p. ej. los archivos `.map` que no -son necesarios en producción), se puede pasar una función de filtro: +Si es necesario, se puede añadir un filtro para seleccionar archivos específicos de la carpeta, por +ejemplo: ```rust,no_run use pagetop_build::StaticFilesBundle; use std::path::Path; fn main() -> std::io::Result<()> { - StaticFilesBundle::from_dir("./static/js", Some(only_js)) - .with_name("app_js") - .build() -} + fn only_pdf_files(path: &Path) -> bool { + // Selecciona únicamente los archivos con extensión `.pdf`. + path.extension().map_or(false, |ext| ext == "pdf") + } -fn only_js(path: &Path) -> bool { - path.extension().map_or(false, |ext| ext == "js") + StaticFilesBundle::from_dir("./static", Some(only_pdf_files)) + .with_name("guides") + .build() } ``` -Cada paquete de recursos genera un archivo `.rs` en -[OUT_DIR](https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts). -No es necesario acceder a él directamente: el nombre asignado con `.with_name()` se usa como -identificador en `serve_static_files!` para configurar la ruta del servicio: +### Compilar archivos SCSS a CSS + +Se puede compilar un archivo SCSS, que podría importar otros a su vez, para preparar un recurso con +el archivo CSS minificado obtenido. Por ejemplo: + +```rust,no_run +use pagetop_build::StaticFilesBundle; + +fn main() -> std::io::Result<()> { + StaticFilesBundle::from_scss("./styles/main.scss", "styles.min.css") + .with_name("main_styles") + .build() +} +``` + +Este código compila el archivo `main.scss` de la carpeta `static` del proyecto, y prepara un recurso +llamado `main_styles` que contiene el archivo `styles.min.css` obtenido. + + +## 📦 Archivos generados + +Cada conjunto de recursos [`StaticFilesBundle`] genera un archivo en el directorio estándar +[OUT_DIR](https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts) +donde se incluye el código necesario para compilar el proyecto. Por ejemplo, para +`with_name("guides")` se genera un archivo llamado `guides.rs`. + +No hay ningún problema en generar más de un conjunto de recursos para cada proyecto siempre que se +usen nombres diferentes. + +Normalmente no habrá que acceder a estos módulos; sólo declarar el nombre del conjunto de recursos +en [`serve_static_files!`](https://docs.rs/pagetop/latest/pagetop/macro.serve_static_files.html) +para configurar un servicio web que sirva los archivos desde la ruta indicada. Por ejemplo: ```rust,ignore use pagetop::prelude::*; @@ -97,54 +103,22 @@ use pagetop::prelude::*; pub struct MyExtension; impl Extension for MyExtension { - fn configure_router(&self, mut router: Router) -> Router { - serve_static_files!(router, ["./static/css", app_css] => "/public/css"); - router + // Servicio web que publica los recursos de `guides` en `/ruta/a/guides`. + fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { + serve_static_files!(scfg, guides => "/ruta/a/guides"); } } ``` -## Ejemplo completo -```rust,no_run -use pagetop_build::StaticFilesBundle; -use pagetop_build::{compile_scss, copy_file, copy_file_replacing, minify_js}; -use std::path::Path; - -fn main() -> std::io::Result<()> { - // Regenera `static/` desde cero sólo si hay cambios en `assets/`. - println!("cargo:rerun-if-changed=assets"); - let _ = std::fs::remove_dir_all("static"); - - // Genera `static/` a partir de `assets/`. - compile_scss("assets/main.scss", "static/css/main.min.css")?; - copy_file("assets/fonts/icon.woff2", "static/fonts/icon.woff2")?; - copy_file_replacing( - "assets/lib.min.js", - "static/js/app.min.js", - &[("lib.min.js.map", "app.min.js.map")], - )?; - minify_js("assets/shell.js", "static/js/shell.min.js")?; - - // Prepara los paquetes de recursos para incluir en el proyecto. - StaticFilesBundle::from_dir("./static/css", None).with_name("app_css").build()?; - StaticFilesBundle::from_dir("./static/js", Some(only_js)).with_name("app_js").build()?; - StaticFilesBundle::from_dir("./static/fonts", None).with_name("app_fonts").build() -} - -// Los `.map` no se incluyen, se servirán desde disco durante el desarrollo. -fn only_js(path: &Path) -> bool { - path.extension().map_or(false, |ext| ext == "js") -} -``` - -## Advertencia +## 🚧 Advertencia **PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos hasta que se libere la versión **1.0.0**. -## Licencia + +## 📜 Licencia El código está disponible bajo una doble licencia: diff --git a/helpers/pagetop-build/src/lib.rs b/helpers/pagetop-build/src/lib.rs index 5a9d24bb..eacb6180 100644 --- a/helpers/pagetop-build/src/lib.rs +++ b/helpers/pagetop-build/src/lib.rs @@ -3,7 +3,7 @@

PageTop Build

-

Genera o prepara archivos estáticos para servirlos o incluirlos en un proyecto PageTop.

+

Prepara un conjunto de archivos estáticos o archivos SCSS compilados para ser incluidos en el binario de un proyecto PageTop.

[![Doc API](https://img.shields.io/docsrs/pagetop-build?label=Doc%20API&style=for-the-badge&logo=Docs.rs)](https://docs.rs/pagetop-build) [![Crates.io](https://img.shields.io/crates/v/pagetop-build.svg?style=for-the-badge&logo=ipfs)](https://crates.io/crates/pagetop-build) @@ -18,79 +18,85 @@ clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y configurables, basadas en HTML, CSS y JavaScript. -## Guía rápida -La convención recomendada para extensiones, temas o aplicaciones basadas en **PageTop** es separar -los archivos fuente de los generados siguiendo el patrón `assets/` -> `static/`: +# ⚡️ Guía rápida -- **`assets/`** - archivos versionados en el repositorio, por ejemplo archivos SCSS, JavaScript de - terceros, fuentes, etc. Todo lo que hay aquí se sube al repositorio y será la fuente para generar - el directorio final `static/`. -- **`static/`** - archivos generados en tiempo de compilación a partir de `assets/`. Se añade a - `.gitignore` y nunca se sube al repositorio. -- **`build.rs`** - orquesta la transformación: genera `static/` desde `assets/` para servirlos o - incluirlos en el proyecto. +Añadir en el archivo `Cargo.toml` del proyecto: -Durante el desarrollo, `static/` existe en disco y los archivos se sirven desde ahí. En producción, -el directorio no existe y los recursos salen del binario. La macro -[`serve_static_files!`](https://docs.rs/pagetop/latest/pagetop/macro.serve_static_files.html) -gestiona esta dualidad de forma transparente. +```toml +[build-dependencies] +pagetop-build = { ... } +``` -### Funciones de transformación +Y crear un archivo `build.rs` a la altura de `Cargo.toml` para indicar cómo se van a incluir los +archivos estáticos o cómo se van a compilar los archivos SCSS para el proyecto. Casos de uso: -Estas funciones se usan en el `build.rs` de cada proyecto para generar `static/` a partir de -`assets/`. Todas crean el directorio padre del destino si no existe y devuelven `io::Result<()>` -para poder propagarse con `?` en caso de error. +## Incluir archivos estáticos desde un directorio -- `compile_scss()` - compila un archivo SCSS a CSS minificado. -- `copy_dir()` - copia recursivamente un directorio completo. Útil para copiar todos los archivos de - `assets/` o de un subdirectorio a `static/` sin transformación. -- `copy_file()` - copia un archivo al destino. -- `copy_file_replacing()` - copia un archivo aplicando una lista de sustituciones de texto en su - contenido; útil para actualizar referencias internas (p.ej. `sourceMappingURL`) al renombrar - archivos. -- `minify_js()` - minifica un archivo JavaScript. - -### Incluir los archivos estáticos en el proyecto - -Una vez generado `static/`, usaremos `StaticFilesBundle` para incluir su contenido en el binario. Se -pueden crear tantos paquetes de recursos como sea necesario, siempre que tengan nombres distintos: +Hay que preparar una carpeta en el proyecto con todos los archivos que se quieren incluir, por +ejemplo `static`, y añadir el siguiente código en `build.rs` para crear el conjunto de recursos: ```rust,no_run use pagetop_build::StaticFilesBundle; fn main() -> std::io::Result<()> { - StaticFilesBundle::from_dir("./static/css", None) - .with_name("app_css") - .build()?; - StaticFilesBundle::from_dir("./static/fonts", None) - .with_name("app_fonts") + StaticFilesBundle::from_dir("./static", None) + .with_name("guides") .build() } ``` -Si es necesario excluir algunos archivos del paquete de recursos (p. ej. los archivos `.map` que no -son necesarios en producción), se puede pasar una función de filtro: +Si es necesario, se puede añadir un filtro para seleccionar archivos específicos de la carpeta, por +ejemplo: ```rust,no_run use pagetop_build::StaticFilesBundle; use std::path::Path; fn main() -> std::io::Result<()> { - StaticFilesBundle::from_dir("./static/js", Some(only_js)) - .with_name("app_js") - .build() -} + fn only_pdf_files(path: &Path) -> bool { + // Selecciona únicamente los archivos con extensión `.pdf`. + path.extension().map_or(false, |ext| ext == "pdf") + } -fn only_js(path: &Path) -> bool { - path.extension().map_or(false, |ext| ext == "js") + StaticFilesBundle::from_dir("./static", Some(only_pdf_files)) + .with_name("guides") + .build() } ``` -Cada paquete de recursos genera un archivo `.rs` en -[OUT_DIR](https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts). -No es necesario acceder a él directamente: el nombre asignado con `.with_name()` se usa como -identificador en `serve_static_files!` para configurar la ruta del servicio: +## Compilar archivos SCSS a CSS + +Se puede compilar un archivo SCSS, que podría importar otros a su vez, para preparar un recurso con +el archivo CSS minificado obtenido. Por ejemplo: + +```rust,no_run +use pagetop_build::StaticFilesBundle; + +fn main() -> std::io::Result<()> { + StaticFilesBundle::from_scss("./styles/main.scss", "styles.min.css") + .with_name("main_styles") + .build() +} +``` + +Este código compila el archivo `main.scss` de la carpeta `static` del proyecto, y prepara un recurso +llamado `main_styles` que contiene el archivo `styles.min.css` obtenido. + + +# 📦 Archivos generados + +Cada conjunto de recursos [`StaticFilesBundle`] genera un archivo en el directorio estándar +[OUT_DIR](https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts) +donde se incluye el código necesario para compilar el proyecto. Por ejemplo, para +`with_name("guides")` se genera un archivo llamado `guides.rs`. + +No hay ningún problema en generar más de un conjunto de recursos para cada proyecto siempre que se +usen nombres diferentes. + +Normalmente no habrá que acceder a estos módulos; sólo declarar el nombre del conjunto de recursos +en [`serve_static_files!`](https://docs.rs/pagetop/latest/pagetop/macro.serve_static_files.html) +para configurar un servicio web que sirva los archivos desde la ruta indicada. Por ejemplo: ```rust,ignore use pagetop::prelude::*; @@ -98,77 +104,39 @@ use pagetop::prelude::*; pub struct MyExtension; impl Extension for MyExtension { + /// Registra los recursos de `guides` en el router bajo `/ruta/a/guides`. fn configure_router(&self, mut router: Router) -> Router { - serve_static_files!(router, ["./static/css", app_css] => "/public/css"); + serve_static_files!(router, [guides] => "/ruta/a/guides"); router } } ``` - -## Ejemplo completo - -```rust,no_run -use pagetop_build::StaticFilesBundle; -use pagetop_build::{compile_scss, copy_file, copy_file_replacing, minify_js}; -use std::path::Path; - -fn main() -> std::io::Result<()> { - // Regenera `static/` desde cero sólo si hay cambios en `assets/`. - println!("cargo:rerun-if-changed=assets"); - let _ = std::fs::remove_dir_all("static"); - - // Genera `static/` a partir de `assets/`. - compile_scss("assets/main.scss", "static/css/main.min.css")?; - copy_file("assets/fonts/icon.woff2", "static/fonts/icon.woff2")?; - copy_file_replacing( - "assets/lib.min.js", - "static/js/app.min.js", - &[("lib.min.js.map", "app.min.js.map")], - )?; - minify_js("assets/shell.js", "static/js/shell.min.js")?; - - // Prepara los paquetes de recursos para incluir en el proyecto. - StaticFilesBundle::from_dir("./static/css", None).with_name("app_css").build()?; - StaticFilesBundle::from_dir("./static/js", Some(only_js)).with_name("app_js").build()?; - StaticFilesBundle::from_dir("./static/fonts", None).with_name("app_fonts").build() -} - -// Los `.map` no se incluyen, se servirán desde disco durante el desarrollo. -fn only_js(path: &Path) -> bool { - path.extension().map_or(false, |ext| ext == "js") -} -``` */ #![doc( - html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/assets/favicon.ico" + html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/static/favicon.ico" )] use grass::{Options, OutputStyle, from_path}; -use minify_js::{Session, TopLevelMode, minify}; -use pagetop_statics::resource_dir; +use pagetop_statics::{ResourceDir, resource_dir}; -use std::fs::{File, copy as fs_copy, create_dir_all, read_dir}; -use std::io::{self, Write}; -use std::path::{Path, PathBuf}; +use std::fs::{File, create_dir_all, remove_dir_all}; +use std::io::Write; +use std::path::Path; -// **< StaticFilesBundle >************************************************************************** - -/// Prepara un paquete de recursos para incluir en el binario del proyecto. +/// Prepara un conjunto de recursos para ser incluidos en el binario del proyecto. pub struct StaticFilesBundle { - dir: PathBuf, - filter: Option bool>, - name: Option, + resource_dir: ResourceDir, } impl StaticFilesBundle { - /// Crea el paquete de recursos con los archivos del directorio indicado. + /// Prepara el conjunto de recursos con los archivos de un directorio. Opcionalmente se puede + /// aplicar un filtro para seleccionar un subconjunto de los archivos. /// /// # Argumentos /// - /// * `dir` - Ruta al directorio con los archivos a incluir, normalmente `static/` o un - /// directorio dentro de este. - /// * `filter` - Función opcional para seleccionar qué archivos incluir en el paquete. + /// * `dir` - Directorio que contiene los archivos. + /// * `filter` - Una función opcional para aceptar o no un archivo según su ruta. /// /// # Ejemplo /// @@ -177,227 +145,124 @@ impl StaticFilesBundle { /// use std::path::Path; /// /// fn main() -> std::io::Result<()> { + /// fn only_images(path: &Path) -> bool { + /// matches!( + /// path.extension().and_then(|ext| ext.to_str()), + /// Some("jpg" | "png" | "gif") + /// ) + /// } + /// /// StaticFilesBundle::from_dir("./static", Some(only_images)) /// .with_name("images") /// .build() /// } - /// - /// fn only_images(path: &Path) -> bool { - /// matches!( - /// path.extension().and_then(|ext| ext.to_str()), - /// Some("jpg" | "png" | "gif") - /// ) - /// } /// ``` pub fn from_dir

(dir: P, filter: Option bool>) -> Self where P: AsRef, { - Self { - dir: dir.as_ref().to_path_buf(), - filter, - name: None, + let dir_path = dir.as_ref(); + let dir_str = dir_path.to_str().unwrap_or_else(|| { + panic!( + "Resource directory path is not valid UTF-8: {}", + dir_path.display() + ); + }); + + let mut resource_dir = resource_dir(dir_str); + + // Aplica el filtro si está definido. + if let Some(f) = filter { + resource_dir.with_filter(f); + } + + // Identifica el directorio temporal de recursos. + StaticFilesBundle { resource_dir } + } + + /// Prepara un recurso CSS minimizado a partir de la compilación de un archivo SCSS (que puede a + /// su vez importar otros archivos SCSS). + /// + /// # Argumentos + /// + /// * `path` - Archivo SCSS a compilar. + /// * `target_name` - Nombre para el archivo CSS. + /// + /// # Ejemplo + /// + /// ```rust,no_run + /// use pagetop_build::StaticFilesBundle; + /// + /// fn main() -> std::io::Result<()> { + /// StaticFilesBundle::from_scss("./bootstrap/scss/main.scss", "bootstrap.min.css") + /// .with_name("bootstrap_css") + /// .build() + /// } + /// ``` + pub fn from_scss

(path: P, target_name: &str) -> Self + where + P: AsRef, + { + // Crea un directorio temporal único para el archivo CSS (basado en su nombre, para que + // varias llamadas a from_scss en el mismo build.rs no se pisen). + let out_dir = std::env::var("OUT_DIR").unwrap(); + let safe_name = target_name.replace(['.', '-'], "_"); + let temp_dir = Path::new(&out_dir).join(format!("from_scss_{safe_name}")); + + // Limpia el directorio temporal de ejecuciones previas, si existe. + if temp_dir.exists() { + remove_dir_all(&temp_dir).unwrap_or_else(|e| { + panic!( + "Failed to clean temporary directory `{}`: {e}", + temp_dir.display() + ); + }); + } + create_dir_all(&temp_dir).unwrap_or_else(|e| { + panic!( + "Failed to create temporary directory `{}`: {e}", + temp_dir.display() + ); + }); + + // Compila SCSS a CSS. + let css_content = from_path( + path.as_ref(), + &Options::default().style(OutputStyle::Compressed), + ) + .unwrap_or_else(|e| { + panic!( + "Failed to compile SCSS file `{}`: {e}", + path.as_ref().display(), + ) + }); + + // Guarda el archivo CSS compilado en el directorio temporal. + let css_path = temp_dir.join(target_name); + File::create(&css_path) + .unwrap_or_else(|_| panic!("Failed to create CSS file `{}`", css_path.display())) + .write_all(css_content.as_bytes()) + .unwrap_or_else(|_| panic!("Failed to write CSS content to `{}`", css_path.display())); + + // Identifica el directorio temporal de recursos. + StaticFilesBundle { + resource_dir: resource_dir(temp_dir.to_str().unwrap()), } } - /// Asigna un nombre al paquete de recursos. - /// - /// El nombre debe ser un identificador Rust válido que se convertirá en nombre del módulo y de - /// la función del archivo `.rs` generado en `OUT_DIR`. Si no se llama a este método, el nombre - /// por defecto será `"bundle"`. - /// - /// Este nombre es el que hay que declarar en - /// [`serve_static_files!`](https://docs.rs/pagetop/latest/pagetop/macro.serve_static_files.html) - /// para configurar la ruta del servicio: - /// - /// ```rust,ignore - /// serve_static_files!(router, ["./static/css", app_css] => "/public/css"); - /// // ^^^^^^^ - /// // debe coincidir con .with_name("app_css") - /// ``` + /// Asigna un nombre al conjunto de recursos. pub fn with_name(mut self, name: impl AsRef) -> Self { - self.name = Some(name.as_ref().to_string()); + let name = name.as_ref(); + let out_dir = std::env::var("OUT_DIR").unwrap(); + let filename = Path::new(&out_dir).join(format!("{name}.rs")); + self.resource_dir.with_generated_filename(filename); + self.resource_dir.with_module_name(format!("bundle_{name}")); + self.resource_dir.with_generated_fn(name); self } - /// Genera el archivo `.rs` en `OUT_DIR` para incluir los recursos del directorio en el binario. + /// Contruye finalmente el conjunto de recursos para incluir en el binario de la aplicación. pub fn build(self) -> std::io::Result<()> { - let out_dir = std::env::var("OUT_DIR").unwrap(); - let name = self.name.as_deref().unwrap_or("bundle"); - - let mut rd = resource_dir(&self.dir); - if let Some(f) = self.filter { - rd.with_filter(f); - } - - let generated_filename = PathBuf::from(&out_dir).join(format!("{name}.rs")); - rd.with_generated_filename(generated_filename); - rd.with_module_name(format!("bundle_{name}")); - rd.with_generated_fn(name); - rd.build() + self.resource_dir.build() } } - -// **< compile_scss / copy_dir / copy_file / copy_file_replacing / minify_js >********************** - -/// Compila un archivo SCSS a CSS minificado y lo escribe en la ruta de destino. -/// -/// Crea el directorio padre del destino si no existe. -/// -/// # Ejemplo -/// -/// ```rust,no_run -/// fn main() -> std::io::Result<()> { -/// pagetop_build::compile_scss("assets/main.scss", "static/css/main.min.css") -/// } -/// ``` -pub fn compile_scss(src: P, dst: Q) -> io::Result<()> -where - P: AsRef, - Q: AsRef, -{ - let src = src.as_ref(); - let dst = dst.as_ref(); - - if let Some(parent) = dst.parent() { - create_dir_all(parent)?; - } - - let options = Options::default().style(OutputStyle::Compressed); - let css = from_path(src, &options) - .map_err(|e| io::Error::other(format!("failed to compile `{}`: {e}", src.display())))?; - File::create(dst)?.write_all(css.as_bytes()) -} - -/// Copia recursivamente el contenido de un directorio a otro destino. -/// -/// Crea el directorio destino y todos los subdirectorios necesarios. -/// -/// # Ejemplo -/// -/// ```rust,no_run -/// fn main() -> std::io::Result<()> { -/// pagetop_build::copy_dir("assets", "static") -/// } -/// ``` -pub fn copy_dir(src: P, dst: Q) -> io::Result<()> -where - P: AsRef, - Q: AsRef, -{ - let src = src.as_ref(); - let dst = dst.as_ref(); - create_dir_all(dst)?; - for entry in read_dir(src)? { - let entry = entry?; - let src_path = entry.path(); - let dst_path = dst.join(entry.file_name()); - if src_path.is_dir() { - copy_dir(&src_path, &dst_path)?; - } else { - fs_copy(&src_path, &dst_path)?; - } - } - Ok(()) -} - -/// Copia un archivo a su destino. -/// -/// Crea el directorio padre del destino si no existe. -/// -/// # Ejemplo -/// -/// ```rust,no_run -/// fn main() -> std::io::Result<()> { -/// pagetop_build::copy_file("assets/fonts/icon.woff2", "static/fonts/icon.woff2") -/// } -/// ``` -pub fn copy_file(src: P, dst: Q) -> io::Result<()> -where - P: AsRef, - Q: AsRef, -{ - let src = src.as_ref(); - let dst = dst.as_ref(); - - if let Some(parent) = dst.parent() { - create_dir_all(parent)?; - } - - fs_copy(src, dst)?; - Ok(()) -} - -/// Copia un archivo a su destino con una lista de sustituciones de texto en su contenido. -/// -/// El archivo fuente se lee como texto UTF-8; no debe usarse con archivos binarios. Las -/// sustituciones de texto se aplican en orden y de forma encadenada: el resultado de cada -/// sustitución puede ser entrada de la siguiente. -/// -/// Crea el directorio padre del destino si no existe. -/// -/// # Ejemplo -/// -/// ```rust,no_run -/// fn main() -> std::io::Result<()> { -/// pagetop_build::copy_file_replacing( -/// "assets/adminlte.min.js", -/// "static/js/myapp.min.js", -/// &[("adminlte.min.js.map", "myapp.min.js.map")], -/// ) -/// } -/// ``` -pub fn copy_file_replacing(src: P, dst: Q, replacements: &[(&str, &str)]) -> io::Result<()> -where - P: AsRef, - Q: AsRef, -{ - let src = src.as_ref(); - let dst = dst.as_ref(); - - if let Some(parent) = dst.parent() { - create_dir_all(parent)?; - } - - let content = std::fs::read_to_string(src)?; - let patched = replacements - .iter() - .fold(content, |acc, (old, new)| acc.replace(old, new)); - File::create(dst)?.write_all(patched.as_bytes()) -} - -/// Minifica un archivo JavaScript y lo escribe en la ruta de destino. -/// -/// El archivo se procesa en modo de ámbito global (`TopLevelMode::Global`), adecuado para scripts -/// sin `import`/`export`. Los archivos con sintaxis de módulo ES deben procesarse con -/// `TopLevelMode::Module`, que el *crate* subyacente (`minify-js`) también soporta pero esta -/// función no expone actualmente. -/// -/// Crea el directorio padre del destino si no existe. -/// -/// # Ejemplo -/// -/// ```rust,no_run -/// fn main() -> std::io::Result<()> { -/// pagetop_build::minify_js("assets/shell.js", "static/js/shell.min.js") -/// } -/// ``` -pub fn minify_js(src: P, dst: Q) -> io::Result<()> -where - P: AsRef, - Q: AsRef, -{ - let src = src.as_ref(); - let dst = dst.as_ref(); - - if let Some(parent) = dst.parent() { - create_dir_all(parent)?; - } - - let source = std::fs::read(src)?; - let session = Session::new(); - let mut output = Vec::new(); - minify(&session, TopLevelMode::Global, &source, &mut output) - .map_err(|e| io::Error::other(format!("failed to minify `{}`: {e:?}", src.display())))?; - File::create(dst)?.write_all(&output) -} diff --git a/helpers/pagetop-macros/README.md b/helpers/pagetop-macros/README.md index 599f81bb..9b0174a6 100644 --- a/helpers/pagetop-macros/README.md +++ b/helpers/pagetop-macros/README.md @@ -11,13 +11,14 @@ -## Sobre PageTop +## 🧭 Sobre PageTop [PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y configurables, basadas en HTML, CSS y JavaScript. -## Créditos + +## 📚 Créditos Este *crate* incluye entre sus macros una adaptación de [maud-macros](https://crates.io/crates/maud_macros) @@ -28,13 +29,15 @@ Este *crate* incluye entre sus macros una adaptación de necesidad de referenciar `maud` o `smart_default` en las dependencias del archivo `Cargo.toml` de cada proyecto PageTop. -## Advertencia + +## 🚧 Advertencia **PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos hasta que se libere la versión **1.0.0**. -## Licencia + +## 📜 Licencia El código está disponible bajo una doble licencia: diff --git a/helpers/pagetop-macros/src/lib.rs b/helpers/pagetop-macros/src/lib.rs index 4557196e..63349aa0 100644 --- a/helpers/pagetop-macros/src/lib.rs +++ b/helpers/pagetop-macros/src/lib.rs @@ -31,7 +31,7 @@ cada proyecto PageTop. */ #![doc( - html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/assets/favicon.ico" + html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/static/favicon.ico" )] mod maud; @@ -126,7 +126,7 @@ pub fn derive_auto_default(input: TokenStream) -> TokenStream { /// /// Si defines un método `with_` como este: /// -/// ```rust,no_run +/// ```rust /// # use pagetop_macros::builder_fn; /// # struct Example {value: Option}; /// # impl Example { @@ -140,7 +140,7 @@ pub fn derive_auto_default(input: TokenStream) -> TokenStream { /// /// la macro reescribirá el método `with_` y generará un nuevo método `alter_`: /// -/// ```rust,no_run +/// ```rust /// # struct Example {value: Option}; /// # impl Example { /// #[inline] diff --git a/helpers/pagetop-minimal/README.md b/helpers/pagetop-minimal/README.md index b7a17bc0..1f8ec148 100644 --- a/helpers/pagetop-minimal/README.md +++ b/helpers/pagetop-minimal/README.md @@ -11,19 +11,21 @@ -## Sobre PageTop +## 🧭 Sobre PageTop [PageTop](https://docs.rs/pagetop) es un entorno de desarrollo que reivindica la esencia de la web clásica para crear soluciones web SSR (*renderizadas en el servidor*) modulares, extensibles y configurables, basadas en HTML, CSS y JavaScript. -## Descripción general + +## 🗺️ Descripción general Este *crate* proporciona un conjunto básico de macros que se integran en las utilidades de PageTop para optimizar operaciones habituales relacionadas con la composición estructurada de texto, la concatenación de cadenas y el uso rápido de colecciones clave-valor. -## Créditos + +## 📚 Créditos Las macros para texto multilínea **`indoc!`**, **`formatdoc!`** y **`concatdoc!`** se reexportan del *crate* [indoc](https://crates.io/crates/indoc) de [David Tolnay](https://crates.io/users/dtolnay). @@ -37,13 +39,15 @@ La macro para generar identificadores dinámicos **`paste!`** se reexporta del * [pastey](https://crates.io/crates/pastey), una implementación avanzada y soportada del popular `paste!` de [David Tolnay](https://crates.io/users/dtolnay). -## Advertencia + +## 🚧 Advertencia **PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos hasta que se libere la versión **1.0.0**. -## Licencia + +## 📜 Licencia El código está disponible bajo una doble licencia: diff --git a/helpers/pagetop-minimal/src/lib.rs b/helpers/pagetop-minimal/src/lib.rs index eab70c84..3b8c9036 100644 --- a/helpers/pagetop-minimal/src/lib.rs +++ b/helpers/pagetop-minimal/src/lib.rs @@ -40,7 +40,7 @@ La macro para generar identificadores dinámicos **`paste!`** se reexporta del * */ #![doc( - html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/assets/favicon.ico" + html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/static/favicon.ico" )] #[doc(hidden)] diff --git a/helpers/pagetop-statics/README.md b/helpers/pagetop-statics/README.md index 92541096..3184f095 100644 --- a/helpers/pagetop-statics/README.md +++ b/helpers/pagetop-statics/README.md @@ -31,13 +31,15 @@ Para ello, adapta el código de [static-files](https://crates.io/crates/static_f se integra en PageTop para evitar que cada proyecto tenga que declarar `static-files` manualmente como dependencia en su `Cargo.toml`. -## Advertencia + +## 🚧 Advertencia **PageTop** es un proyecto personal para aprender [Rust](https://www.rust-lang.org/es) y conocer su ecosistema. Su API está sujeta a cambios frecuentes. No se recomienda su uso en producción, al menos hasta que se libere la versión **1.0.0**. -## Licencia + +## 📜 Licencia El código está disponible bajo una doble licencia: diff --git a/helpers/pagetop-statics/src/lib.rs b/helpers/pagetop-statics/src/lib.rs index 426991d5..d72176c6 100644 --- a/helpers/pagetop-statics/src/lib.rs +++ b/helpers/pagetop-statics/src/lib.rs @@ -35,14 +35,12 @@ como dependencia en su `Cargo.toml`. #![doc(test(no_crate_inject))] #![doc( - html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/assets/favicon.ico" + html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/static/favicon.ico" )] #![allow(clippy::needless_doctest_main)] /// Resource definition and single module based generation. pub mod resource; - -#[doc(inline)] pub use resource::Resource as StaticFile; mod resource_dir; diff --git a/src/base/action/component/after_render_component.rs b/src/base/action/component/after_render_component.rs index 08eefdb5..0778e9c8 100644 --- a/src/base/action/component/after_render_component.rs +++ b/src/base/action/component/after_render_component.rs @@ -6,7 +6,7 @@ use super::FnActionWithComponent; pub struct AfterRender { f: FnActionWithComponent, referer_type_id: Option, - referer_id: Option, + referer_id: AttrId, weight: Weight, } @@ -19,7 +19,7 @@ impl ActionDispatcher for AfterRender { /// Devuelve el identificador del componente. fn referer_id(&self) -> Option { - self.referer_id.clone() + self.referer_id.get() } /// Devuelve el peso para definir el orden de ejecución. @@ -34,7 +34,7 @@ impl AfterRender { AfterRender { f, referer_type_id: Some(UniqueId::of::()), - referer_id: None, + referer_id: AttrId::default(), weight: 0, } } @@ -42,8 +42,7 @@ impl AfterRender { /// Afina el registro para ejecutar la acción [`FnActionWithComponent`] sólo para el componente /// `C` con identificador `id`. pub fn filter_by_referer_id(mut self, id: impl AsRef) -> Self { - let id = id.as_ref().trim().to_ascii_lowercase().replace(' ', "_"); - self.referer_id = if id.is_empty() { None } else { Some(id) }; + self.referer_id.alter_id(id); self } diff --git a/src/base/action/component/before_render_component.rs b/src/base/action/component/before_render_component.rs index e91589a2..051a3dd6 100644 --- a/src/base/action/component/before_render_component.rs +++ b/src/base/action/component/before_render_component.rs @@ -6,7 +6,7 @@ use super::FnActionWithComponent; pub struct BeforeRender { f: FnActionWithComponent, referer_type_id: Option, - referer_id: Option, + referer_id: AttrId, weight: Weight, } @@ -19,7 +19,7 @@ impl ActionDispatcher for BeforeRender { /// Devuelve el identificador del componente. fn referer_id(&self) -> Option { - self.referer_id.clone() + self.referer_id.get() } /// Devuelve el peso para definir el orden de ejecución. @@ -34,7 +34,7 @@ impl BeforeRender { BeforeRender { f, referer_type_id: Some(UniqueId::of::()), - referer_id: None, + referer_id: AttrId::default(), weight: 0, } } @@ -42,8 +42,7 @@ impl BeforeRender { /// Afina el registro para ejecutar la acción [`FnActionWithComponent`] sólo para el componente /// `C` con identificador `id`. pub fn filter_by_referer_id(mut self, id: impl AsRef) -> Self { - let id = id.as_ref().trim().to_ascii_lowercase().replace(' ', "_"); - self.referer_id = if id.is_empty() { None } else { Some(id) }; + self.referer_id.alter_id(id); self } diff --git a/src/base/action/component/transform_markup_component.rs b/src/base/action/component/transform_markup_component.rs index bed2e192..3e3a81f5 100644 --- a/src/base/action/component/transform_markup_component.rs +++ b/src/base/action/component/transform_markup_component.rs @@ -6,7 +6,7 @@ use super::FnActionTransformMarkup; pub struct TransformMarkup { f: FnActionTransformMarkup, referer_type_id: Option, - referer_id: Option, + referer_id: AttrId, weight: Weight, } @@ -19,7 +19,7 @@ impl ActionDispatcher for TransformMarkup { /// Devuelve el identificador del componente. fn referer_id(&self) -> Option { - self.referer_id.clone() + self.referer_id.get() } /// Devuelve el peso para definir el orden de ejecución. @@ -34,7 +34,7 @@ impl TransformMarkup { TransformMarkup { f, referer_type_id: Some(UniqueId::of::()), - referer_id: None, + referer_id: AttrId::default(), weight: 0, } } @@ -42,8 +42,7 @@ impl TransformMarkup { /// Afina el registro para ejecutar la acción [`FnActionTransformMarkup`] sólo para el /// componente `C` con identificador `id`. pub fn filter_by_referer_id(mut self, id: impl AsRef) -> Self { - let id = id.as_ref().trim().to_ascii_lowercase().replace(' ', "_"); - self.referer_id = if id.is_empty() { None } else { Some(id) }; + self.referer_id.alter_id(id); self } diff --git a/src/base/component.rs b/src/base/component.rs index 5c9fee3d..7ea596d3 100644 --- a/src/base/component.rs +++ b/src/base/component.rs @@ -1,18 +1,11 @@ //! Componentes nativos proporcionados por PageTop. -mod block; -pub use block::Block; - -mod button; -pub use button::{Button, ButtonAction}; - -pub mod form; -#[doc(inline)] -pub use form::Form; - mod html; pub use html::Html; +mod block; +pub use block::Block; + mod intro; pub use intro::{Intro, IntroOpening}; diff --git a/src/base/component/block.rs b/src/base/component/block.rs index 2e56c5b0..01f10d42 100644 --- a/src/base/component/block.rs +++ b/src/base/component/block.rs @@ -6,8 +6,10 @@ use crate::prelude::*; /// opcional y un cuerpo que sólo se renderiza si existen componentes hijos (*children*). #[derive(AutoDefault, Clone, Debug, Getters)] pub struct Block { - /// Devuelve identificador, clases CSS y atributos HTML del componente. - props: Props, + #[getters(skip)] + id: AttrId, + /// Devuelve las clases CSS asociadas al bloque. + classes: Classes, /// Devuelve el título del bloque. title: L10n, /// Devuelve la lista de componentes hijo del bloque. @@ -20,15 +22,11 @@ impl Component for Block { } fn id(&self) -> Option { - self.props.get_id() + self.id.get() } - fn setup(&mut self, cx: &Context) { - // Asegura que el bloque tiene un identificador único. - self.alter_prop(PropsOp::ensure_id(cx.build_id::(1))); - - // Todos los bloques tienen la clase CSS `block` por defecto. - self.alter_prop(PropsOp::prepend_classes("block")); + fn setup(&mut self, _cx: &Context) { + self.alter_classes(ClassesOp::Prepend, "block"); } fn prepare(&self, cx: &mut Context) -> Result { @@ -38,12 +36,14 @@ impl Component for Block { return Ok(html! {}); } + let id = cx.required_id::(self.id(), 1); + Ok(html! { - div (self.props()) { + div id=(&id) class=[self.classes().get()] { @if let Some(title) = self.title().lookup(cx) { - h2 class="block-title" { span { (title) } } + h2 class="block__title" { span { (title) } } } - div class="block-body" { (block_body) } + div class="block__body" { (block_body) } } }) } @@ -52,17 +52,17 @@ impl Component for Block { impl Block { // **< Block BUILDER >************************************************************************** - /// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`. + /// Establece el identificador único (`id`) del bloque. #[builder_fn] - pub fn with_id(mut self, id: impl Into) -> Self { - self.props.alter_id(id); + pub fn with_id(mut self, id: impl AsRef) -> Self { + self.id.alter_id(id); self } - /// Modifica identificador, clases CSS o atributos HTML del componente. + /// Modifica la lista de clases CSS aplicadas al bloque. #[builder_fn] - pub fn with_prop(mut self, op: PropsOp) -> Self { - self.props.alter_prop(op); + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef) -> Self { + self.classes.alter_classes(op, classes); self } diff --git a/src/base/component/button.rs b/src/base/component/button.rs deleted file mode 100644 index 8d2f0fa7..00000000 --- a/src/base/component/button.rs +++ /dev/null @@ -1,207 +0,0 @@ -use crate::prelude::*; - -use std::fmt; - -// **< ButtonAction >******************************************************************************* - -/// Comportamiento de un [`Button`] al activarse. -#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] -pub enum ButtonAction { - /// Envía un formulario al servidor. Es el **tipo por defecto**. - #[default] - Submit, - /// Restablece todos los campos de un formulario a sus valores iniciales. - Reset, - /// Botón de propósito general, sin efecto predeterminado. Su comportamiento podría definirse - /// mediante JavaScript. - Plain, -} - -impl fmt::Display for ButtonAction { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match self { - ButtonAction::Submit => "submit", - ButtonAction::Reset => "reset", - ButtonAction::Plain => "button", - }) - } -} - -// **< Button >************************************************************************************* - -/// Componente para crear un **botón**. -/// -/// Renderiza un botón con soporte para las variantes disponibles en [`ButtonAction`] (`submit`, -/// `reset` y botón genérico). -/// -/// El comportamiento del botón se establece al crearlo: -/// -/// - [`Button::submit()`]: botón de envío (por defecto). -/// - [`Button::reset()`]: botón de restablecimiento de valores. -/// - [`Button::plain()`]: botón genérico sin comportamiento predeterminado. -/// -/// El botón puede usarse dentro o fuera de un formulario. -/// -/// # Ejemplo -/// -/// ```rust,no_run -/// use pagetop::prelude::*; -/// -/// let save = Button::submit(L10n::n("Save")); -/// let cancel = Button::plain(L10n::n("Cancel")); -/// let clear = Button::reset(L10n::n("Clear")); -/// ``` -/// -/// Cuando el botón activa el envío, el navegador incluye el par `name=value` en los datos del -/// formulario **sólo si** tiene el atributo `name` definido. Es la forma habitual de identificar -/// cuál de los botones de envío fue pulsado. En el servidor se deserializa como `Option`: -/// -/// ```rust,ignore -/// #[derive(serde::Deserialize)] -/// struct FormData { -/// #[serde(default)] -/// action: Option, // p. ej., "save" o "delete"; `None` si el botón no tenía `name`. -/// } -/// ``` -#[derive(AutoDefault, Clone, Debug, Getters)] -pub struct Button { - /// Devuelve identificador, clases CSS y atributos HTML del componente. - props: Props, - /// Devuelve el comportamiento del botón al activarse. - kind: ButtonAction, - /// Devuelve el nombre del botón. - name: AttrName, - /// Devuelve el valor del botón. - value: AttrValue, - /// Devuelve la etiqueta del botón. - label: Attr, - /// Devuelve si el botón recibe el foco automáticamente al cargar la página. - autofocus: bool, - /// Devuelve si el botón está deshabilitado. - disabled: bool, -} - -impl Component for Button { - fn new() -> Self { - Self::default() - } - - fn id(&self) -> Option { - self.props.get_id() - } - - fn setup(&mut self, _cx: &Context) { - self.alter_prop(PropsOp::prepend_classes("button")); - } - - fn prepare(&self, cx: &mut Context) -> Result { - Ok(html! { - button - type=(self.kind()) - (self.props()) - name=[self.name().get()] - value=[self.value().get()] - autofocus[*self.autofocus()] - disabled[*self.disabled()] - { - @if let Some(label) = self.label().lookup(cx) { - (label) - } - } - }) - } -} - -impl Button { - /// Crea un botón de **envío** (`type="submit"`). - /// - /// Es la acción predeterminada al pulsar un botón en la mayoría de los formularios: envía los - /// datos al servidor. - pub fn submit(label: L10n) -> Self { - Self { - kind: ButtonAction::Submit, - label: Attr::some(label), - ..Default::default() - } - } - - /// Crea un botón de **restablecimiento** (`type="reset"`). - /// - /// Al pulsarlo, devuelve todos los campos del formulario a sus valores iniciales. - pub fn reset(label: L10n) -> Self { - Self { - kind: ButtonAction::Reset, - label: Attr::some(label), - ..Default::default() - } - } - - /// Crea un **botón genérico** (`type="button"`). - /// - /// No tiene un comportamiento predeterminado sobre el formulario. Su comportamiento puede - /// definirse mediante JavaScript. - pub fn plain(label: L10n) -> Self { - Self { - kind: ButtonAction::Plain, - label: Attr::some(label), - ..Default::default() - } - } - - // **< Button BUILDER >************************************************************************* - - /// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`. - #[builder_fn] - pub fn with_id(mut self, id: impl Into) -> Self { - self.props.alter_id(id); - self - } - - /// Modifica identificador, clases CSS o atributos HTML del componente. - #[builder_fn] - pub fn with_prop(mut self, op: PropsOp) -> Self { - self.props.alter_prop(op); - self - } - - /// Establece el nombre del botón (atributo `name`). - /// - /// Cuando el formulario tiene varios botones de envío, el navegador incluye en el envío el par - /// `name=value` sólo del botón que activó el formulario. Permite identificar cuál fue pulsado. - #[builder_fn] - pub fn with_name(mut self, name: impl AsRef) -> Self { - self.name.alter_name(name); - self - } - - /// Establece el valor del botón (atributo `value`). - /// - /// Es el dato que el navegador transmite al servidor junto con el `name` cuando este botón - /// activa el envío. Útil para distinguir entre varios botones de envío en un mismo formulario. - #[builder_fn] - pub fn with_value(mut self, value: impl AsRef) -> Self { - self.value.alter_str(value); - self - } - - /// Establece o elimina la etiqueta visible del botón (basta pasar `None` para quitarla). - #[builder_fn] - pub fn with_label(mut self, label: impl Into>) -> Self { - self.label.alter_opt(label.into()); - self - } - - /// Establece si el botón recibe el foco automáticamente al cargar la página. - #[builder_fn] - pub fn with_autofocus(mut self, autofocus: bool) -> Self { - self.autofocus = autofocus; - self - } - - /// Establece si el botón está deshabilitado. - #[builder_fn] - pub fn with_disabled(mut self, disabled: bool) -> Self { - self.disabled = disabled; - self - } -} diff --git a/src/base/component/form.rs b/src/base/component/form.rs deleted file mode 100644 index a656bb6b..00000000 --- a/src/base/component/form.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! Componentes y tipos para crear formularios HTML ([`Form`]). - -mod props; -pub use props::{Autocomplete, AutofillField, CheckboxKind, Method}; - -mod component; -pub use component::Form; - -mod fieldset; -pub use fieldset::Fieldset; - -mod checkbox; -pub use checkbox::Checkbox; - -pub mod check; - -pub mod radio; - -pub mod select; - -pub mod input; - -mod textarea; -pub use textarea::Textarea; - -mod range; -pub use range::Range; - -mod hidden; -pub use hidden::Hidden; diff --git a/src/base/component/form/input.rs b/src/base/component/form/input.rs deleted file mode 100644 index 7b57c648..00000000 --- a/src/base/component/form/input.rs +++ /dev/null @@ -1,424 +0,0 @@ -//! Definiciones para crear campos de texto de una línea. - -use crate::prelude::*; - -use std::fmt; - -// **< Kind >*************************************************************************************** - -/// Tipo de campo para un [`form::input::Field`]. -/// -/// Determina el tipo de entrada que acepta, así como el comportamiento del navegador al interactuar -/// con el campo. Implícitamente se aplica al crear el control: [`text()`](Field::text), -/// [`password()`](Field::password), [`search()`](Field::search), [`email()`](Field::email), -/// [`telephone()`](Field::telephone) o [`url()`](Field::url). -#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] -pub enum Kind { - /// Entrada de texto genérico (`type="text"`). Es el tipo por defecto. - #[default] - Text, - /// Entrada de una contraseña (`type="password"`). El contenido aparece enmascarado. - Password, - /// Campo de búsqueda (`type="search"`). Es un tipo semántico para los cuadros de búsqueda. - Search, - /// Entrada de un correo electrónico (`type="email"`). Permite validar el formato del correo. - Email, - /// Entrada de un teléfono (`type="tel"`). Activa el teclado de llamadas en móviles. - Telephone, - /// Entrada de una URL (`type="url"`). Comprueba que la entrada sea una URL bien formada. - Url, -} - -impl fmt::Display for Kind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match self { - Kind::Text => "text", - Kind::Password => "password", - Kind::Search => "search", - Kind::Email => "email", - Kind::Telephone => "tel", - Kind::Url => "url", - }) - } -} - -// **< Mode >*************************************************************************************** - -/// Sugerencia para el teclado virtual de un [`form::input::Field`]. -/// -/// Indica al navegador qué tipo de teclado virtual mostrar en dispositivos móviles o táctiles al -/// editar el campo. A diferencia del atributo `type` ([`form::input::Kind`]), no restringe los -/// valores aceptados ni activa la validación del navegador; es sólo una sugerencia de presentación. -/// -/// Se establece con [`form::input::Field::with_inputmode()`]. -#[derive(Clone, Copy, Debug, PartialEq)] -pub enum Mode { - /// Suprime el teclado virtual. Útil en campos con teclado personalizado basado en JavaScript. - None, - /// Teclado de texto genérico. - Text, - /// Teclado decimal, con dígitos y separador decimal. - Decimal, - /// Teclado numérico, con sólo dígitos. - Numeric, - /// Teclado de teléfono, con dígitos y símbolos `+`, `*` y `#`. - Tel, - /// Teclado optimizado para búsquedas (puede incluir tecla de búsqueda). - Search, - /// Teclado optimizado para correo electrónico (incluye `@` y `.`). - Email, - /// Teclado optimizado para URL (incluye `/`, `.` y `.com`). - Url, -} - -impl fmt::Display for Mode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match self { - Mode::None => "none", - Mode::Text => "text", - Mode::Decimal => "decimal", - Mode::Numeric => "numeric", - Mode::Tel => "tel", - Mode::Search => "search", - Mode::Email => "email", - Mode::Url => "url", - }) - } -} - -// **< Field >************************************************************************************** - -/// Componente para crear un **campo de texto de una línea**. -/// -/// Renderiza los tipos más habituales en formularios: -/// -/// - [`Field::text()`]: campo de texto genérico (`type="text"`, por defecto). -/// - [`Field::password()`]: contraseña (`type="password"`). -/// - [`Field::search()`]: búsqueda (`type="search"`). -/// - [`Field::email()`]: correo electrónico (`type="email"`). -/// - [`Field::telephone()`]: teléfono (`type="tel"`). -/// - [`Field::url()`]: URL (`type="url"`). -/// -/// # Ejemplo -/// -/// ```rust,no_run -/// use pagetop::prelude::*; -/// -/// let email = form::input::Field::email() -/// .with_name("email") -/// .with_label(L10n::n("Email address")) -/// .with_placeholder(L10n::n("user@example.com")) -/// .with_autocomplete(Some(form::Autocomplete::email())) -/// .with_required(true); -/// ``` -/// -/// Al enviar el formulario el navegador transmite `name=valor`. Un campo de texto siempre envía su -/// valor, incluso si está vacío. En el servidor se deserializa como `String`: -/// -/// ```rust,ignore -/// #[derive(serde::Deserialize)] -/// struct FormData { -/// email: String, // Siempre presente; cadena vacía si el usuario no escribió nada. -/// } -/// ``` -#[derive(AutoDefault, Clone, Debug, Getters)] -pub struct Field { - /// Devuelve identificador, clases CSS y atributos HTML del componente. - props: Props, - /// Devuelve el tipo de campo. - kind: Kind, - /// Devuelve el nombre del campo. - name: AttrName, - /// Devuelve el valor inicial del campo. - value: AttrValue, - /// Devuelve la etiqueta del campo. - label: Attr, - /// Devuelve el texto de ayuda del campo. - help_text: Attr, - /// Devuelve la longitud mínima permitida en caracteres. - minlength: Attr, - /// Devuelve la longitud máxima permitida en caracteres. - maxlength: Attr, - /// Devuelve el texto indicativo del campo. - placeholder: Attr, - /// Devuelve la configuración de autocompletado del campo. - autocomplete: Attr, - /// Devuelve si el campo recibe el foco automáticamente al cargar la página. - autofocus: bool, - /// Devuelve si el campo es de sólo lectura. - readonly: bool, - /// Devuelve si el campo es obligatorio. - required: bool, - /// Devuelve si el campo está deshabilitado. - disabled: bool, - /// Devuelve si el campo se muestra como texto plano sin bordes ni fondo. - plaintext: bool, - /// Devuelve la sugerencia de teclado virtual para el campo. - inputmode: Attr, -} - -impl Component for Field { - fn new() -> Self { - Self::default() - } - - fn id(&self) -> Option { - self.props.get_id() - } - - fn setup(&mut self, _cx: &Context) { - if let Some(container_id) = self - .id() - .or_else(|| self.name().get().map(|n| util::join!("edit-", n))) - { - self.alter_prop(PropsOp::ensure_id(container_id)); - } - - // Clases CSS del contenedor del campo de texto. - self.alter_prop(PropsOp::prepend_classes(util::join!( - "form-field form-field-", - self.kind().to_string() - ))); - } - - fn prepare(&self, cx: &mut Context) -> Result { - let container_id = self.id(); - let input_id = container_id.as_deref().map(|id| util::join!(id, "-input")); - let input_class = if *self.plaintext() { - "form-control-plaintext" - } else { - "form-control" - }; - - Ok(html! { - div (self.props()) { - @if let Some(label) = self.label().lookup(cx) { - label for=[input_id.as_deref()] class="form-label" { - (label) - @if *self.required() { - span - class="form-required" - title=(L10n::l("field_required").using(cx)) - { - "*" - } - } - } - } - input - type=(self.kind()) - id=[input_id.as_deref()] - class=(input_class) - name=[self.name().get()] - value=[self.value().get()] - minlength=[self.minlength().get()] - maxlength=[self.maxlength().get()] - placeholder=[self.placeholder().lookup(cx)] - inputmode=[self.inputmode().get()] - autocomplete=[self.autocomplete().get()] - autofocus[*self.autofocus()] - readonly[*self.readonly() || *self.plaintext()] - required[*self.required()] - disabled[*self.disabled()]; - @if let Some(description) = self.help_text().lookup(cx) { - div class="form-text" { (description) } - } - } - }) - } -} - -impl Field { - /// Crea un campo de **texto genérico** (`type="text"`). - /// - /// Es el tipo por defecto. Adecuado para nombres, apellidos, ciudades y cualquier entrada - /// textual sin restricciones de formato específicas. - pub fn text() -> Self { - Self::default() - } - - /// Crea un campo de **contraseña** (`type="password"`). - /// - /// El navegador oculta los caracteres introducidos. Se recomienda usar con - /// [`with_autocomplete()`](Self::with_autocomplete) para permitir autorrellenar con una - /// contraseña guardada o dejar al usuario recibir sugerencias o crear una nueva. - pub fn password() -> Self { - Self { - kind: Kind::Password, - ..Default::default() - } - } - - /// Crea un campo de **búsqueda** (`type="search"`). - /// - /// Semánticamente equivalente a `text` pero optimizado para búsquedas: algunos navegadores - /// añaden un botón para borrar el contenido. - pub fn search() -> Self { - Self { - kind: Kind::Search, - ..Default::default() - } - } - - /// Crea un campo de **correo electrónico** (`type="email"`). - /// - /// El navegador valida el formato de la dirección antes de enviar el formulario. En - /// dispositivos móviles muestra un teclado adaptado para introducir direcciones de correo. - pub fn email() -> Self { - Self { - kind: Kind::Email, - ..Default::default() - } - } - - /// Crea un campo de **teléfono** (`type="tel"`). - /// - /// No impone ninguna restricción de formato (los formatos de teléfono varían por país), pero - /// en dispositivos móviles muestra el teclado numérico de llamadas. - pub fn telephone() -> Self { - Self { - kind: Kind::Telephone, - ..Default::default() - } - } - - /// Crea un campo de **URL** (`type="url"`). - /// - /// El navegador valida que el valor sea una URL bien formada antes de enviar el formulario. - pub fn url() -> Self { - Self { - kind: Kind::Url, - ..Default::default() - } - } - - // **< Field BUILDER >************************************************************************** - - /// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`. - #[builder_fn] - pub fn with_id(mut self, id: impl Into) -> Self { - self.props.alter_id(id); - self - } - - /// Modifica identificador, clases CSS o atributos HTML del componente. - #[builder_fn] - pub fn with_prop(mut self, op: PropsOp) -> Self { - self.props.alter_prop(op); - self - } - - /// Establece el nombre del campo (atributo `name`). - /// - /// Sin él, el valor del campo no se transmite al servidor al enviar el formulario. Para - /// deserializar el campo en el servidor es recomendable establecer un `name` explícito. - #[builder_fn] - pub fn with_name(mut self, name: impl AsRef) -> Self { - self.name.alter_name(name); - self - } - - /// Establece el valor inicial del campo. - #[builder_fn] - pub fn with_value(mut self, value: impl AsRef) -> Self { - self.value.alter_str(value); - self - } - - /// Establece o elimina la etiqueta visible del campo (basta pasar `None` para quitarla). - #[builder_fn] - pub fn with_label(mut self, label: impl Into>) -> Self { - self.label.alter_opt(label.into()); - self - } - - /// Establece o elimina el texto de ayuda del campo (basta pasar `None` para quitarlo). - #[builder_fn] - pub fn with_help_text(mut self, help_text: impl Into>) -> Self { - self.help_text.alter_opt(help_text.into()); - self - } - - /// Establece la longitud mínima permitida en caracteres (`None` para no imponer mínimo). - #[builder_fn] - pub fn with_minlength(mut self, minlength: Option) -> Self { - self.minlength.alter_opt(minlength); - self - } - - /// Establece la longitud máxima permitida en caracteres (`None` para no imponer límite). - #[builder_fn] - pub fn with_maxlength(mut self, maxlength: Option) -> Self { - self.maxlength.alter_opt(maxlength); - self - } - - /// Establece o elimina el texto indicativo del campo (`None` para quitarlo). - /// - /// Este texto aparece en el mismo campo y desaparece en cuanto el usuario empieza a escribir. - /// Al ser texto visible para el usuario se acepta [`L10n`] para poder localizarlo. - #[builder_fn] - pub fn with_placeholder(mut self, placeholder: impl Into>) -> Self { - self.placeholder.alter_opt(placeholder.into()); - self - } - - /// Establece la configuración de autocompletado del campo. - /// - /// Usar los métodos de [`form::Autocomplete`] para los valores más habituales (p. ej. - /// [`Autocomplete::email()`](form::Autocomplete::email) o - /// [`Autocomplete::current_password()`](form::Autocomplete::current_password)). - #[builder_fn] - pub fn with_autocomplete(mut self, autocomplete: Option) -> Self { - self.autocomplete.alter_opt(autocomplete); - self - } - - /// Establece si el campo recibe el foco automáticamente al cargar la página. - #[builder_fn] - pub fn with_autofocus(mut self, autofocus: bool) -> Self { - self.autofocus = autofocus; - self - } - - /// Establece si el campo es de sólo lectura. - #[builder_fn] - pub fn with_readonly(mut self, readonly: bool) -> Self { - self.readonly = readonly; - self - } - - /// Establece si el campo es obligatorio. - #[builder_fn] - pub fn with_required(mut self, required: bool) -> Self { - self.required = required; - self - } - - /// Establece si el campo está deshabilitado. - #[builder_fn] - pub fn with_disabled(mut self, disabled: bool) -> Self { - self.disabled = disabled; - self - } - - /// Establece si el campo se muestra como texto plano (sin bordes ni fondo). - /// - /// Útil para mostrar un valor no editable en pantalla que sí se envía al servidor con el - /// formulario. El efecto visual depende del tema activo. - #[builder_fn] - pub fn with_plaintext(mut self, plaintext: bool) -> Self { - self.plaintext = plaintext; - self - } - - /// Establece el modo de entrada sugerido para el teclado virtual en dispositivos móviles. - /// - /// A diferencia del atributo `type` ([`form::input::Kind`]), no restringe los valores aceptados - /// ni activa la validación del navegador; es sólo una sugerencia de presentación. - #[builder_fn] - pub fn with_inputmode(mut self, inputmode: Option) -> Self { - self.inputmode.alter_opt(inputmode); - self - } -} diff --git a/src/base/component/form/select.rs b/src/base/component/form/select.rs deleted file mode 100644 index fc94d06c..00000000 --- a/src/base/component/form/select.rs +++ /dev/null @@ -1,427 +0,0 @@ -//! Definiciones para crear listas de selección. - -use crate::prelude::*; - -// **< Item >*************************************************************************************** - -/// Elemento individual de [`form::select::Field`] o de [`form::select::Group`]. -/// -/// Representa un elemento dentro de una lista de selección o de un grupo de elementos de la lista. -/// Cada elemento tiene un valor que se envía al servidor y una etiqueta localizable visible para el -/// usuario. -/// -/// Puede marcarse como seleccionado por defecto con [`with_selected()`](Self::with_selected) o -/// deshabilitado de forma independiente al resto usando [`with_disabled()`](Self::with_disabled). -/// -/// # Ejemplo -/// -/// ```rust,no_run -/// use pagetop::prelude::*; -/// -/// let item = form::select::Item::new("es", L10n::n("Spanish")).with_selected(true); -/// ``` -#[derive(AutoDefault, Clone, Debug, Getters)] -pub struct Item { - /// Devuelve el valor enviado al servidor cuando se selecciona el elemento. - value: AttrValue, - /// Devuelve la etiqueta visible del elemento. - label: L10n, - /// Devuelve si el elemento debe aparecer seleccionado por defecto. - selected: bool, - /// Devuelve si el elemento está deshabilitado. - disabled: bool, -} - -impl Item { - /// Crea un nuevo elemento con el valor y la etiqueta indicados. - pub fn new(value: impl AsRef, label: L10n) -> Self { - Self { - value: AttrValue::new(value), - label, - selected: false, - disabled: false, - } - } - - // **< Item BUILDER >*************************************************************************** - - /// Establece si el elemento aparece seleccionado por defecto. - /// - /// En una lista de selección única, el navegador aplica la selección al último elemento marcado - /// si hay más de uno; mientras que en una lista múltiple se respetan todos los elementos - /// marcados. - pub fn with_selected(mut self, selected: bool) -> Self { - self.selected = selected; - self - } - - /// Establece si el elemento está deshabilitado. - pub fn with_disabled(mut self, disabled: bool) -> Self { - self.disabled = disabled; - self - } -} - -// **< Group >************************************************************************************** - -/// Grupo de elementos dentro de [`form::select::Field`]. -/// -/// Agrupa un conjunto de elementos dentro de una lista de selección con una etiqueta visible. El -/// grupo completo puede deshabilitarse en bloque con [`with_disabled()`](Self::with_disabled). -/// -/// # Ejemplo -/// -/// ```rust,no_run -/// use pagetop::prelude::*; -/// -/// let group = form::select::Group::new(L10n::n("Europe")) -/// .with_item(form::select::Item::new("es", L10n::n("Spanish"))) -/// .with_item(form::select::Item::new("fr", L10n::n("French"))); -/// ``` -#[derive(AutoDefault, Clone, Debug, Getters)] -pub struct Group { - /// Devuelve la etiqueta visible del grupo de elementos. - label: L10n, - /// Devuelve los elementos del grupo. - items: Vec, - /// Devuelve si el grupo de elementos está deshabilitado. - disabled: bool, -} - -impl Group { - /// Crea un nuevo grupo con la etiqueta indicada. - pub fn new(label: L10n) -> Self { - Self { - label, - ..Self::default() - } - } - - // **< Group BUILDER >************************************************************************** - - /// Añade un elemento al grupo. Los elementos se muestran en el orden en que se añaden. - pub fn with_item(mut self, item: Item) -> Self { - self.items.push(item); - self - } - - /// Establece si el grupo de elementos está deshabilitado en bloque. - pub fn with_disabled(mut self, disabled: bool) -> Self { - self.disabled = disabled; - self - } -} - -// **< Entry >************************************************************************************** - -/// Entrada de [`form::select::Field`] con un elemento o un grupo de elementos. -/// -/// Cada entrada se crea implícitamente cuando se usa [`form::select::Field::with_item()`] para -/// añadir un elemento individual o [`form::select::Field::with_group()`] para añadir un grupo de -/// elementos a una lista de selección. -/// -/// Con [`form::select::Field::entries()`] se pueden recuperar todas las entradas para su -/// renderizado. -#[derive(Clone, Debug)] -pub enum Entry { - /// Elemento individual. - Item(Item), - /// Grupo de elementos. - Group(Group), -} - -// **< Field >************************************************************************************** - -/// Componente para crear una **lista de selección**. -/// -/// Renderiza un campo para mostrar una lista de elementos con una etiqueta opcional. Permite elegir -/// uno, o más de uno si se activa la selección múltiple con -/// [`with_multiple()`](Self::with_multiple). -/// -/// Los elementos individuales se añaden con [`with_item()`](Self::with_item); los grupos de -/// elementos con un encabezado común se añaden con [`with_group()`](Self::with_group). Ambos -/// métodos pueden combinarse libremente. -/// -/// # Ejemplo -/// -/// ```rust,no_run -/// use pagetop::prelude::*; -/// -/// let idioma = form::select::Field::new() -/// .with_name("language") -/// .with_label(L10n::n("Language")) -/// .with_item(form::select::Item::new("", L10n::n("— Choose —")).with_selected(true)) -/// .with_group( -/// form::select::Group::new(L10n::n("Europe")) -/// .with_item(form::select::Item::new("es", L10n::n("Spanish"))) -/// .with_item(form::select::Item::new("fr", L10n::n("French"))), -/// ) -/// .with_group( -/// form::select::Group::new(L10n::n("Americas")) -/// .with_item(form::select::Item::new("en", L10n::n("English"))) -/// .with_item(form::select::Item::new("pt", L10n::n("Portuguese"))), -/// ) -/// .with_required(true); -/// ``` -/// -/// Cuando el usuario selecciona un elemento y envía el formulario, el navegador transmite -/// `name=valor`. Si el campo es obligatorio el valor siempre estará presente y puede deserializarse -/// como `String`; si es opcional, usa `Option`: -/// -/// ```rust,ignore -/// #[derive(serde::Deserialize)] -/// struct FormData { -/// language: String, // Siempre presente (campo obligatorio). -/// // language: Option, // None si no se selecciona ninguna opción. -/// } -/// ``` -/// -/// Con selección múltiple activa, el navegador envía un valor por cada elemento marcado; si no se -/// marca ninguno, no envía nada. Usa `Vec` con `#[serde(default)]`: -/// -/// ```rust,ignore -/// #[derive(serde::Deserialize)] -/// struct FormData { -/// #[serde(default)] -/// interests: Vec, // p. ej. ["art", "tech"] o [] si no se marcó ninguna. -/// } -/// ``` -#[derive(AutoDefault, Clone, Debug, Getters)] -pub struct Field { - /// Devuelve identificador, clases CSS y atributos HTML del componente. - props: Props, - /// Devuelve el nombre del campo. - name: AttrName, - /// Devuelve la etiqueta del campo. - label: Attr, - /// Devuelve el texto de ayuda del campo. - help_text: Attr, - /// Devuelve las entradas de la lista (elementos individuales y grupos de elementos). - entries: Vec, - /// Devuelve si la lista permite selección múltiple. - multiple: bool, - /// Devuelve el número de filas visibles de la lista de selección. - rows: Attr, - /// Devuelve la configuración de autocompletado del campo. - autocomplete: Attr, - /// Devuelve si la lista recibe el foco automáticamente al cargar la página. - autofocus: bool, - /// Devuelve si la selección de un elemento es obligatoria. - required: bool, - /// Devuelve si la lista está deshabilitada. - disabled: bool, -} - -impl Component for Field { - fn new() -> Self { - Self::default() - } - - fn id(&self) -> Option { - self.props.get_id() - } - - fn setup(&mut self, _cx: &Context) { - if let Some(container_id) = self - .id() - .or_else(|| self.name().get().map(|n| util::join!("edit-", n))) - { - self.alter_prop(PropsOp::ensure_id(container_id)); - } - - // Clases CSS del contenedor de la lista de selección. - self.alter_prop(PropsOp::prepend_classes("form-field form-field-select")); - } - - fn prepare(&self, cx: &mut Context) -> Result { - let container_id = self.id(); - let select_id = container_id.as_deref().map(|id| util::join!(id, "-select")); - - Ok(html! { - div (self.props()) { - @if let Some(label) = self.label().lookup(cx) { - label for=[select_id.as_deref()] class="form-label" { - (label) - @if *self.required() { - span - class="form-required" - title=(L10n::l("field_required").using(cx)) - { - "*" - } - } - } - } - select - id=[select_id.as_deref()] - class="form-select" - name=[self.name().get()] - multiple[*self.multiple()] - size=[self.rows().get()] - autocomplete=[self.autocomplete().get()] - autofocus[*self.autofocus()] - required[*self.required()] - disabled[*self.disabled()] - { - @for entry in self.entries() { - @match entry { - Entry::Item(opt) => { - option - value=(opt.value().as_str().unwrap_or("")) - selected[*opt.selected()] - disabled[*opt.disabled()] - { - (opt.label().using(cx)) - } - } - Entry::Group(group) => { - optgroup - label=(group.label().using(cx)) - disabled[*group.disabled()] - { - @for opt in group.items() { - option - value=(opt.value().as_str().unwrap_or("")) - selected[*opt.selected()] - disabled[*opt.disabled()] - { - (opt.label().using(cx)) - } - } - } - } - } - } - } - @if let Some(description) = self.help_text().lookup(cx) { - div class="form-text" { (description) } - } - } - }) - } -} - -impl Field { - // **< Field BUILDER >************************************************************************** - - /// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`. - #[builder_fn] - pub fn with_id(mut self, id: impl Into) -> Self { - self.props.alter_id(id); - self - } - - /// Modifica identificador, clases CSS o atributos HTML del componente. - #[builder_fn] - pub fn with_prop(mut self, op: PropsOp) -> Self { - self.props.alter_prop(op); - self - } - - /// Establece el nombre del campo (atributo `name`). - /// - /// Sin él, el valor seleccionado no se transmite al servidor al enviar el formulario. Para - /// deserializar el campo en el servidor es recomendable establecer un `name` explícito. - #[builder_fn] - pub fn with_name(mut self, name: impl AsRef) -> Self { - self.name.alter_name(name); - self - } - - /// Establece o elimina la etiqueta visible del campo (basta pasar `None` para quitarla). - #[builder_fn] - pub fn with_label(mut self, label: impl Into>) -> Self { - self.label.alter_opt(label.into()); - self - } - - /// Establece o elimina el texto de ayuda del campo (basta pasar `None` para quitarlo). - #[builder_fn] - pub fn with_help_text(mut self, help_text: impl Into>) -> Self { - self.help_text.alter_opt(help_text.into()); - self - } - - /// Añade un elemento individual a la lista de selección. - /// - /// Los elementos y grupos se muestran en el orden en que se añaden. - #[builder_fn] - pub fn with_item(mut self, item: Item) -> Self { - self.entries.push(Entry::Item(item)); - self - } - - /// Añade un grupo de elementos a la lista de selección. - /// - /// Los elementos y grupos se muestran en el orden en que se añaden. - #[builder_fn] - pub fn with_group(mut self, group: Group) -> Self { - self.entries.push(Entry::Group(group)); - self - } - - /// Establece si el control permite seleccionar varios elementos. - /// - /// Al activar la selección múltiple, se muestra una lista en lugar de un desplegable. Se - /// recomienda combinar con [`with_rows()`](Self::with_rows) para controlar el número de filas - /// visibles. - /// - /// Para un número reducido de elementos con etiquetas descriptivas considera usar - /// [`form::check::Field`] en su lugar, ofrece una presentación más clara y es más accesible en - /// pantallas pequeñas. - #[builder_fn] - pub fn with_multiple(mut self, multiple: bool) -> Self { - self.multiple = multiple; - self - } - - /// Establece el número de filas visibles de la lista de selección. - /// - /// Cuando se establece un valor mayor que 1, el control se muestra como lista en lugar de - /// desplegable, tanto en modo simple como múltiple. Con `None` se omite el atributo y presenta - /// el control como desplegable (comportamiento por defecto). - /// - /// Es especialmente útil con selección múltiple para controlar el número de filas visibles sin - /// necesidad de recurrir al desplazamiento. - #[builder_fn] - pub fn with_rows(mut self, rows: Option) -> Self { - self.rows.alter_opt(rows); - self - } - - /// Establece la configuración de autocompletado del campo. - /// - /// Permite al navegador rellenar automáticamente el elemento seleccionado en listas de países - /// (`"country"`), idiomas (`"language"`), sexo (`"sex"`) u otros campos con valores - /// predefinidos. En listas de selección múltiples no es útil en la práctica, ya que los - /// navegadores no gestionan selecciones múltiples con autocompletado. - /// - /// Usa los métodos de [`form::Autocomplete`] para los valores más habituales. Pasa `None` para - /// omitir el atributo. - #[builder_fn] - pub fn with_autocomplete(mut self, autocomplete: Option) -> Self { - self.autocomplete.alter_opt(autocomplete); - self - } - - /// Establece si el campo recibe el foco automáticamente al cargar la página. - #[builder_fn] - pub fn with_autofocus(mut self, autofocus: bool) -> Self { - self.autofocus = autofocus; - self - } - - /// Establece si el campo es obligatorio. - #[builder_fn] - pub fn with_required(mut self, required: bool) -> Self { - self.required = required; - self - } - - /// Establece si el campo está deshabilitado. - #[builder_fn] - pub fn with_disabled(mut self, disabled: bool) -> Self { - self.disabled = disabled; - self - } -} diff --git a/src/base/component/form/textarea.rs b/src/base/component/form/textarea.rs deleted file mode 100644 index 5b4cc1ff..00000000 --- a/src/base/component/form/textarea.rs +++ /dev/null @@ -1,256 +0,0 @@ -//! Definiciones para crear áreas de texto en formularios. - -use crate::prelude::*; - -/// Componente para crear un **área de texto** de formulario. -/// -/// Permite escribir en un área de texto de más de una línea, con una etiqueta opcional y atributos -/// como el número de filas a presentar, longitud mínima (`minlength`) y máxima (`maxlength`), texto -/// indicativo (`placeholder`) o autocompletado (`autocomplete`). -/// -/// # Ejemplo -/// -/// ```rust,no_run -/// use pagetop::prelude::*; -/// -/// let descripcion = form::Textarea::new() -/// .with_name("description") -/// .with_label(L10n::n("Description")) -/// .with_rows(Some(8)) -/// .with_maxlength(Some(500)) -/// .with_placeholder(L10n::n("Write here...")) -/// .with_required(true); -/// ``` -/// -/// Al enviar el formulario el navegador transmite `name=valor`. Un área de texto siempre envía su -/// valor, incluso si está vacía. En el servidor se deserializa como `String`: -/// -/// ```rust,ignore -/// #[derive(serde::Deserialize)] -/// struct FormData { -/// description: String, // Siempre presente; cadena vacía si el usuario no escribió nada. -/// } -/// ``` -#[derive(AutoDefault, Clone, Debug, Getters)] -pub struct Textarea { - /// Devuelve identificador, clases CSS y atributos HTML del componente. - props: Props, - /// Devuelve el nombre del campo. - name: AttrName, - /// Devuelve el valor inicial del área de texto. - value: AttrValue, - /// Devuelve la etiqueta del campo. - label: Attr, - /// Devuelve el texto de ayuda del campo. - help_text: Attr, - /// Devuelve el número de filas visibles del área de texto. - rows: Attr, - /// Devuelve la longitud mínima permitida en caracteres. - minlength: Attr, - /// Devuelve la longitud máxima permitida en caracteres. - maxlength: Attr, - /// Devuelve el texto indicativo del área de texto. - placeholder: Attr, - /// Devuelve la configuración de autocompletado del campo. - autocomplete: Attr, - /// Devuelve si el campo recibe el foco automáticamente al cargar la página. - autofocus: bool, - /// Devuelve si el campo es de sólo lectura. - readonly: bool, - /// Devuelve si el campo es obligatorio. - required: bool, - /// Devuelve si el campo está deshabilitado. - disabled: bool, -} - -impl Component for Textarea { - fn new() -> Self { - Self::default() - } - - fn id(&self) -> Option { - self.props.get_id() - } - - fn setup(&mut self, _cx: &Context) { - if let Some(container_id) = self - .id() - .or_else(|| self.name().get().map(|n| util::join!("edit-", n))) - { - self.alter_prop(PropsOp::ensure_id(container_id)); - } - - // Clases CSS del contenedor del área de texto. - self.alter_prop(PropsOp::prepend_classes("form-field form-field-textarea")); - } - - fn prepare(&self, cx: &mut Context) -> Result { - let container_id = self.id(); - let textarea_id = container_id - .as_deref() - .map(|id| util::join!(id, "-textarea")); - - Ok(html! { - div (self.props()) { - @if let Some(label) = self.label().lookup(cx) { - label for=[textarea_id.as_deref()] class="form-label" { - (label) - @if *self.required() { - span - class="form-required" - title=(L10n::l("field_required").using(cx)) - { - "*" - } - } - } - } - textarea - id=[textarea_id.as_deref()] - class="form-control" - name=[self.name().get()] - rows=[self.rows().get()] - minlength=[self.minlength().get()] - maxlength=[self.maxlength().get()] - placeholder=[self.placeholder().lookup(cx)] - autocomplete=[self.autocomplete().get()] - autofocus[*self.autofocus()] - readonly[*self.readonly()] - required[*self.required()] - disabled[*self.disabled()] - { - @if let Some(value) = self.value().get() { - (value) - } - } - @if let Some(description) = self.help_text().lookup(cx) { - div class="form-text" { (description) } - } - } - }) - } -} - -impl Textarea { - // **< Textarea BUILDER >*********************************************************************** - - /// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`. - #[builder_fn] - pub fn with_id(mut self, id: impl Into) -> Self { - self.props.alter_id(id); - self - } - - /// Modifica identificador, clases CSS o atributos HTML del componente. - #[builder_fn] - pub fn with_prop(mut self, op: PropsOp) -> Self { - self.props.alter_prop(op); - self - } - - /// Establece el nombre del campo (atributo `name`). - /// - /// Sin él, el valor del campo no se transmite al servidor al enviar el formulario. Para - /// deserializar el campo en el servidor es recomendable establecer un `name` explícito. - #[builder_fn] - pub fn with_name(mut self, name: impl AsRef) -> Self { - self.name.alter_name(name); - self - } - - /// Establece el valor inicial del área de texto. - #[builder_fn] - pub fn with_value(mut self, value: impl AsRef) -> Self { - self.value.alter_str(value); - self - } - - /// Establece o elimina la etiqueta visible del campo (basta pasar `None` para quitarla). - #[builder_fn] - pub fn with_label(mut self, label: impl Into>) -> Self { - self.label.alter_opt(label.into()); - self - } - - /// Establece o elimina el texto de ayuda del campo (basta pasar `None` para quitarlo). - #[builder_fn] - pub fn with_help_text(mut self, help_text: impl Into>) -> Self { - self.help_text.alter_opt(help_text.into()); - self - } - - /// Establece el número de filas visibles del área de texto. - /// - /// Sin valor o pasando `None`, el área muestra su altura predeterminada, dos filas según el - /// estándar. - #[builder_fn] - pub fn with_rows(mut self, rows: Option) -> Self { - self.rows.alter_opt(rows); - self - } - - /// Establece la longitud mínima permitida en caracteres. - #[builder_fn] - pub fn with_minlength(mut self, minlength: Option) -> Self { - self.minlength.alter_opt(minlength); - self - } - - /// Establece la longitud máxima permitida en caracteres. - #[builder_fn] - pub fn with_maxlength(mut self, maxlength: Option) -> Self { - self.maxlength.alter_opt(maxlength); - self - } - - /// Establece o elimina el texto indicativo del área de texto (`None` para quitarlo). - /// - /// Este texto aparece en el área de texto y desaparece en cuanto el usuario empieza a escribir. - /// Al ser texto visible para el usuario se acepta [`L10n`] para poder localizarlo. - #[builder_fn] - pub fn with_placeholder(mut self, placeholder: impl Into>) -> Self { - self.placeholder.alter_opt(placeholder.into()); - self - } - - /// Establece la configuración de autocompletado del campo. - /// - /// Permite al navegador sugerir o rellenar automáticamente el contenido del área de texto - /// con valores guardados. Es especialmente útil en áreas con contenido semántico predefinido. - /// - /// Usa los métodos de [`form::Autocomplete`] para los valores más habituales. Pasa `None` para - /// omitir el atributo. - #[builder_fn] - pub fn with_autocomplete(mut self, autocomplete: Option) -> Self { - self.autocomplete.alter_opt(autocomplete); - self - } - - /// Establece si el campo recibe el foco automáticamente al cargar la página. - #[builder_fn] - pub fn with_autofocus(mut self, autofocus: bool) -> Self { - self.autofocus = autofocus; - self - } - - /// Establece si el campo es de sólo lectura. - #[builder_fn] - pub fn with_readonly(mut self, readonly: bool) -> Self { - self.readonly = readonly; - self - } - - /// Establece si el campo es obligatorio. - #[builder_fn] - pub fn with_required(mut self, required: bool) -> Self { - self.required = required; - self - } - - /// Establece si el campo está deshabilitado. - #[builder_fn] - pub fn with_disabled(mut self, disabled: bool) -> Self { - self.disabled = disabled; - self - } -} diff --git a/src/base/component/html.rs b/src/base/component/html.rs index e0c9e519..9f44be6b 100644 --- a/src/base/component/html.rs +++ b/src/base/component/html.rs @@ -10,7 +10,7 @@ use std::sync::Arc; /// /// # Ejemplo /// -/// ```rust,no_run +/// ```rust /// # use pagetop::prelude::*; /// let component = Html::with(|_| { /// html! { @@ -23,7 +23,7 @@ use std::sync::Arc; /// /// Para renderizar contenido que dependa del contexto, se puede acceder a él dentro del *closure*: /// -/// ```rust,no_run +/// ```rust /// # use pagetop::prelude::*; /// let component = Html::with(|cx| { /// let user = cx.param_or("username", "visitor".to_string()); diff --git a/src/base/component/intro.rs b/src/base/component/intro.rs index 1ecd6318..63902f10 100644 --- a/src/base/component/intro.rs +++ b/src/base/component/intro.rs @@ -34,14 +34,14 @@ pub enum IntroOpening { /// /// **Intro mínima por defecto** /// -/// ```rust,no_run +/// ```rust /// # use pagetop::prelude::*; /// let intro = Intro::default(); /// ``` /// /// **Título, eslogan y botón personalizados** /// -/// ```rust,no_run +/// ```rust /// # use pagetop::prelude::*; /// let intro = Intro::default() /// .with_title(L10n::l("intro_custom_title")) @@ -54,7 +54,7 @@ pub enum IntroOpening { /// /// **Sin botón y en modo *Custom* (sin *badges* predefinidos)** /// -/// ```rust,no_run +/// ```rust /// # use pagetop::prelude::*; /// let intro = Intro::default() /// .with_button(None::<(L10n, FnPathByContext)>) @@ -63,7 +63,7 @@ pub enum IntroOpening { /// /// **Añadir contenidos hijo** /// -/// ```rust,no_run +/// ```rust /// # use pagetop::prelude::*; /// let intro = Intro::default() /// .with_child( @@ -221,7 +221,7 @@ impl Intro { /// /// # Ejemplo /// - /// ```rust,no_run + /// ```rust /// # use pagetop::prelude::*; /// let intro = Intro::default().with_title(L10n::n("Intro title")); /// ``` @@ -235,7 +235,7 @@ impl Intro { /// /// # Ejemplo /// - /// ```rust,no_run + /// ```rust /// # use pagetop::prelude::*; /// let intro = Intro::default().with_slogan(L10n::n("A short slogan")); /// ``` @@ -253,7 +253,7 @@ impl Intro { /// /// # Ejemplo /// - /// ```rust,no_run + /// ```rust /// # use pagetop::prelude::*; /// // Define un botón con texto y una URL fija. /// let intro = Intro::default().with_button(Some((L10n::n("Start"), |_| "/start".into()))); @@ -274,7 +274,7 @@ impl Intro { /// /// # Ejemplo /// - /// ```rust,no_run + /// ```rust /// # use pagetop::prelude::*; /// let intro = Intro::default().with_opening(IntroOpening::Custom); /// ``` diff --git a/src/base/component/poweredby.rs b/src/base/component/poweredby.rs index 3e4792b8..bd2e0a9b 100644 --- a/src/base/component/poweredby.rs +++ b/src/base/component/poweredby.rs @@ -47,7 +47,7 @@ impl PoweredBy { /// Al pasar `Some(valor)` se sobrescribe el texto de copyright por defecto. Al pasar `None` se /// eliminará, pero en este caso es necesario especificar el tipo explícitamente: /// - /// ```rust,no_run + /// ```rust /// # use pagetop::prelude::*; /// let p1 = PoweredBy::default().with_copyright(Some("2001 © Foo Inc.")); /// let p2 = PoweredBy::new().with_copyright(None::); diff --git a/src/core/component.rs b/src/core/component.rs index 54a00b36..93421e13 100644 --- a/src/core/component.rs +++ b/src/core/component.rs @@ -27,7 +27,7 @@ pub use context::{AssetsOp, Context, ContextError, Contextual}; /// /// # Ejemplo /// -/// ```rust,no_run +/// ```rust /// # use pagetop::prelude::*; /// #[derive(AutoDefault, Clone)] /// struct SampleComponent { @@ -81,7 +81,7 @@ pub type FnIsRenderable = fn(cx: &Context) -> bool; /// El caso más común es construir rutas relativas dependientes del contexto, normalmente usando /// [`Context::route`](crate::core::component::Context::route): /// -/// ```rust,no_run +/// ```rust /// # use pagetop::prelude::*; /// # let relative_route: FnPathByContext = /// |cx| cx.route("/path/to/page") @@ -90,7 +90,7 @@ pub type FnIsRenderable = fn(cx: &Context) -> bool; /// /// También es posible usar rutas estáticas sin asignaciones adicionales: /// -/// ```rust,no_run +/// ```rust /// # use pagetop::prelude::*; /// # let external_route: FnPathByContext = /// |_| "https://www.example.com".into() @@ -99,7 +99,7 @@ pub type FnIsRenderable = fn(cx: &Context) -> bool; /// /// O componer rutas dinámicas en tiempo de ejecución: /// -/// ```rust,no_run +/// ```rust /// # use pagetop::prelude::*; /// # let dynamic_route: FnPathByContext = /// |cx| RoutePath::new("/user").with_param("id", cx.param::("user_id").unwrap().to_string()) diff --git a/src/core/component/children.rs b/src/core/component/children.rs index 480170f4..bfedec14 100644 --- a/src/core/component/children.rs +++ b/src/core/component/children.rs @@ -178,7 +178,7 @@ impl Embed { /// /// # Ejemplo /// - /// ```rust,no_run + /// ```rust /// # use pagetop::prelude::*; /// let embed = Embed::with(Html::with(|_| html! { "Prueba" })); /// { diff --git a/src/core/component/context.rs b/src/core/component/context.rs index d3b13cd3..9ff7251c 100644 --- a/src/core/component/context.rs +++ b/src/core/component/context.rs @@ -3,14 +3,14 @@ use crate::core::component::{ChildOp, Component, MessageLevel, StatusMessage}; use crate::core::theme::all::DEFAULT_THEME; use crate::core::theme::{ChildrenInRegions, DefaultRegion, RegionRef, TemplateRef, ThemeRef}; use crate::html::{Assets, Favicon, JavaScript, StyleSheet}; -use crate::html::{Markup, Props, PropsOp, RoutePath, html}; +use crate::html::{Markup, RoutePath, html}; use crate::locale::L10n; use crate::locale::{LangId, LanguageIdentifier, RequestLocale}; use crate::web::HttpRequest; use crate::{CowStr, builder_fn, util}; -use std::any::{Any, TypeId}; -use std::cell::RefCell; +use std::any::Any; +use std::cell::Cell; use std::collections::HashMap; use std::fmt; @@ -83,7 +83,7 @@ impl std::error::Error for ContextError {} /// /// # Ejemplo /// -/// ```rust,no_run +/// ```rust /// # use pagetop::prelude::*; /// # use pagetop_aliner::Aliner; /// fn prepare_context(cx: C) -> C { @@ -123,7 +123,7 @@ pub trait Contextual: LangId { /// /// # Ejemplo /// - /// ```rust,no_run + /// ```rust /// # use pagetop::prelude::*; /// let cx = Context::new(None) /// .with_param("user_id", 42_i32) @@ -137,10 +137,6 @@ pub trait Contextual: LangId { #[builder_fn] fn with_assets(self, op: AssetsOp) -> Self; - /// Modifica identificador, clases CSS o atributos HTML del elemento ``. - #[builder_fn] - fn with_body_props(self, op: PropsOp) -> Self; - /// Añade un componente o aplica una operación [`ChildOp`] en la región por defecto del /// documento. #[builder_fn] @@ -210,9 +206,6 @@ pub trait Contextual: LangId { /// Devuelve los scripts JavaScript de los recursos del contexto. fn javascripts(&self) -> &Assets; - /// Devuelve identificador, clases CSS y atributos HTML del elemento ``. - fn body_props(&self) -> &Props; - // **< Contextual HELPERS >********************************************************************* /// Elimina un parámetro del contexto. Devuelve `true` si la clave existía y se eliminó. @@ -239,7 +232,7 @@ pub trait Contextual: LangId { /// /// Crea un nuevo contexto asociado a una petición HTTP: /// -/// ```rust,no_run +/// ```rust /// # use pagetop::prelude::*; /// # use pagetop_aliner::Aliner; /// fn new_context(request: HttpRequest) -> Context { @@ -261,7 +254,7 @@ pub trait Contextual: LangId { /// /// Y hace operaciones con un contexto dado: /// -/// ```rust,no_run +/// ```rust /// # use pagetop::prelude::*; /// # #[derive(AutoDefault, Clone, Debug)] /// # struct Menu; @@ -278,7 +271,7 @@ pub trait Contextual: LangId { /// assert_eq!(id, 42); /// /// // Genera un identificador para un componente de tipo `Menu`. -/// let unique_id = cx.build_id::

(1); +/// let unique_id = cx.required_id::(None, 1); /// assert_eq!(unique_id, "menu-1"); // Si es el primero generado. /// } /// ``` @@ -291,10 +284,9 @@ pub struct Context { favicon : Option, // Favicon, si se ha definido. stylesheets: Assets, // Hojas de estilo CSS. javascripts: Assets, // Scripts JavaScript. - body_props : Props, // Identificador, clases CSS y atributos del . regions : ChildrenInRegions, // Regiones de componentes para renderizar. params : HashMap<&'static str, (Box, &'static str)>, // Parámetros en ejecución. - id_counters: RefCell>, // RefCell permite mutar desde build_id(&self). + id_counter : Cell, // Cell permite incrementar desde &self en required_id(). messages : Vec, // Mensajes de usuario acumulados. } @@ -320,10 +312,9 @@ impl Context { favicon : None, stylesheets: Assets::::new(), javascripts: Assets::::new(), - body_props : Props::default(), regions : ChildrenInRegions::default(), params : HashMap::default(), - id_counters: RefCell::new(HashMap::new()), + id_counter : Cell::new(0), messages : Vec::new(), } } @@ -383,42 +374,31 @@ impl Context { route } - /// Construye un identificador HTML único para el tipo de componente `C`. + /// Garantiza un identificador único para un componente `C`, generándolo si no se proporciona + /// ninguno. /// - /// Toma los `segments` finales del *path* completo del tipo, los une con `-` y los convierte a - /// minúsculas, y añade un contador independiente por tipo. Por ejemplo, para `MyApp::ui::Menu` - /// con `segments = 2` devuelve `ui-menu-1` la primera vez que se invoca para ese tipo, - /// `ui-menu-2` la segunda, etc. + /// Si `id` es `None`, crea un identificador usando los últimos segmentos del *path* completo + /// del tipo `C`, separados por `-` y en minúsculas, seguidos de un contador incremental interno + /// del contexto. Por ejemplo, para un componente `MyApp::ui::Menu` con `parts = 2` podría + /// devolver un identificador como `ui-menu-1` si ha sido el primero en generarse. /// - /// Con `segments = 1` se usa sólo el nombre corto del tipo. Si `segments` es `0` o supera el - /// número de segmentos del *path*, se usan todos. + /// Con `parts = 1` se usa el nombre corto del tipo. Si `parts` es `0` o supera el número de + /// segmentos del *path*, entonces se usará el *path* completo. /// - /// Es útil para asignar identificadores cuando el componente no recibe uno explícito. El - /// contador es local a este contexto y se reinicia para cada nueva petición. - pub fn build_id(&self, segments: usize) -> String { - let path: Vec<&str> = TypeInfo::FullName.of::().split("::").collect(); - let segments = if segments == 0 || segments >= path.len() { - path.len() - } else { - segments - }; - let count = { - let mut map = self.id_counters.borrow_mut(); - let n = map.entry(TypeId::of::()).or_insert(0); - *n += 1; - *n - }; - let prefix = path[path.len() - segments..].join("-").to_lowercase(); - util::join!(prefix, "-", count.to_string()) - } - - /// Devuelve `id` si contiene un valor, o genera uno único con [`build_id`](Self::build_id) - /// si es `None`. - pub fn required_id(&self, id: Option, segments: usize) -> String { - match id { - Some(id) => id, - None => self.build_id::(segments), + /// Es útil para asignar identificadores HTML cuando el componente no recibe uno explícito. + pub fn required_id(&self, id: Option, parts: usize) -> String { + if let Some(id) = id { + return id; } + let segments: Vec<&str> = TypeInfo::FullName.of::().split("::").collect(); + let parts = if parts == 0 || parts >= segments.len() { + segments.len() + } else { + parts + }; + self.id_counter.set(self.id_counter.get() + 1); + let prefix = segments[segments.len() - parts..].join("-").to_lowercase(); + util::join!(prefix, "-", self.id_counter.get().to_string()) } /// Acumula un [`StatusMessage`] en el contexto para notificar al visitante. @@ -429,7 +409,7 @@ impl Context { /// /// # Ejemplo /// - /// ```rust,no_run + /// ```rust /// # use pagetop::prelude::*; /// # let mut cx = Context::new(None); /// cx.push_message(MessageLevel::Warning, L10n::n("Session is not valid")); @@ -530,12 +510,6 @@ impl Contextual for Context { self } - #[builder_fn] - fn with_body_props(mut self, op: PropsOp) -> Self { - self.body_props.alter_prop(op); - self - } - #[builder_fn] fn with_child(mut self, op: impl Into) -> Self { self.regions @@ -585,10 +559,6 @@ impl Contextual for Context { &self.javascripts } - fn body_props(&self) -> &Props { - &self.body_props - } - // **< Contextual HELPERS >********************************************************************* fn remove_param(&mut self, key: &'static str) -> bool { diff --git a/src/core/component/definition.rs b/src/core/component/definition.rs index f5af637e..b7ceaa9a 100644 --- a/src/core/component/definition.rs +++ b/src/core/component/definition.rs @@ -34,10 +34,11 @@ pub trait ComponentRender { /// # Requisito: derivar `Clone` /// /// Todo tipo que implemente `Component` **debe** derivar también [`Clone`]. Aunque el compilador -/// no lo exige directamente (hacerlo rompería la seguridad de objeto de `dyn Component`), -/// [`ComponentClone`] se implementa automáticamente mediante una *impl* blanket solo para los tipos -/// que sean `Component + Clone + 'static`. Sin `Clone`, habría que implementar [`ComponentClone`] a -/// mano, y el componente no podría registrarse en [`InRegion`](crate::core::theme::InRegion). +/// no lo exige directamente —hacerlo rompería la seguridad de objeto de `dyn Component`—, +/// [`ComponentClone`] se implementa automáticamente mediante una *impl* blanket solo para los +/// tipos que sean `Component + Clone + 'static`. Sin `Clone`, habría que implementar +/// [`ComponentClone`] a mano, y el componente no podría registrarse en +/// [`InRegion`](crate::core::theme::InRegion). pub trait Component: AnyInfo + ComponentClone + ComponentRender + Send + Sync { /// Crea una nueva instancia del componente. /// diff --git a/src/core/component/error.rs b/src/core/component/error.rs index 9fa34d29..86f9e4aa 100644 --- a/src/core/component/error.rs +++ b/src/core/component/error.rs @@ -9,7 +9,7 @@ use crate::{AutoDefault, Getters}; /// /// # Ejemplo /// -/// ```rust,no_run +/// ```rust /// # use pagetop::prelude::*; /// # #[derive(Clone)] /// # struct MyComponent; diff --git a/src/core/component/message.rs b/src/core/component/message.rs index 5d3125b4..9f6704a2 100644 --- a/src/core/component/message.rs +++ b/src/core/component/message.rs @@ -25,7 +25,7 @@ pub enum MessageLevel { /// /// # Ejemplo /// -/// ```rust,no_run +/// ```rust /// # use pagetop::prelude::*; /// // Mensaje informativo con clave traducible. /// let info = StatusMessage::new(MessageLevel::Info, L10n::l("saved-successfully")); diff --git a/src/core/extension/definition.rs b/src/core/extension/definition.rs index 496001f9..984a5cc1 100644 --- a/src/core/extension/definition.rs +++ b/src/core/extension/definition.rs @@ -10,7 +10,7 @@ use crate::web::Router; /// Este *trait* es fácil de implementar, basta con declarar una estructura sin campos para la /// extensión y sobrescribir los métodos que sean necesarios. Por ejemplo: /// -/// ```rust,no_run +/// ```rust /// # use pagetop::prelude::*; /// pub struct MyExtension; /// @@ -48,7 +48,7 @@ pub trait Extension: AnyInfo + Send + Sync { /// /// # Ejemplo /// - /// ```rust,no_run + /// ```rust /// # use pagetop::prelude::*; /// pub struct MyTheme; /// @@ -107,7 +107,7 @@ pub trait Extension: AnyInfo + Send + Sync { /// /// ## Rutas HTTP básicas /// - /// ```rust,no_run + /// ```rust /// # use pagetop::prelude::*; /// # async fn list_posts() -> &'static str { "" } /// # async fn view_post() -> &'static str { "" } @@ -126,7 +126,7 @@ pub trait Extension: AnyInfo + Send + Sync { /// /// ## Rutas agrupadas bajo un prefijo /// - /// ```rust,no_run + /// ```rust /// # use pagetop::prelude::*; /// # async fn dashboard() -> &'static str { "" } /// # async fn list_users() -> &'static str { "" } diff --git a/src/core/theme.rs b/src/core/theme.rs index 9f358c41..43649db1 100644 --- a/src/core/theme.rs +++ b/src/core/theme.rs @@ -238,11 +238,8 @@ impl Template for DefaultTemplate {} macro_rules! render_component { ($component:expr, { $($type:ty => |$var:ident| $body:expr),* $(,)? }) => { 'render_component: { - // Reborrow explícito como referencia compartida para que `downcast_ref` funcione - // correctamente con `&mut dyn Component` (limitación del compilador con trait objects). - let __c = &*($component); $( - if let Some($var) = __c.downcast_ref::<$type>() { + if let Some($var) = ($component).downcast_ref::<$type>() { break 'render_component Some($body); } )* diff --git a/src/core/theme/definition.rs b/src/core/theme/definition.rs index 0e9e85e6..0b036dd4 100644 --- a/src/core/theme/definition.rs +++ b/src/core/theme/definition.rs @@ -24,7 +24,7 @@ use crate::web::http::StatusCode; /// El único método **obligatorio** de `Extension` para un tema es [`theme()`](Extension::theme), /// que debe devolver una referencia al propio tema: /// -/// ```rust,no_run +/// ```rust /// # use pagetop::prelude::*; /// pub struct MyTheme; /// diff --git a/src/core/theme/regions.rs b/src/core/theme/regions.rs index bbfce795..a10e3ecc 100644 --- a/src/core/theme/regions.rs +++ b/src/core/theme/regions.rs @@ -79,19 +79,19 @@ impl ChildrenInRegions { let mut result = Children::new(); - // 1. Prototipos globales comunes - clon fresco por cada página. + // 1. Prototipos globales comunes — clon fresco por cada página. if let Some(protos) = common.get(name) { for proto in protos { result.add(proto.as_child()); } } - // 2. Children propios de la página - se mueven (son por petición, no requieren clonado). + // 2. Children propios de la página — se mueven (son por petición, no requieren clonado). if let Some(page_children) = self.0.remove(name) { for child in page_children { result.add(child); } } - // 3. Prototipos del tema activo - clon fresco por cada página. + // 3. Prototipos del tema activo — clon fresco por cada página. if let Some(theme_map) = themed.get(&theme_ref.type_id()) { if let Some(protos) = theme_map.get(name) { for proto in protos { @@ -114,7 +114,7 @@ impl ChildrenInRegions { /// /// # Ejemplo /// -/// ```rust,no_run +/// ```rust /// # use pagetop::prelude::*; /// // Banner global en la región de contenido. /// InRegion::Content.add(Html::with(|_| html! { "🎉 ¡Bienvenido!" })); @@ -157,7 +157,7 @@ impl InRegion { /// /// # Ejemplo /// - /// ```rust,no_run + /// ```rust /// # use pagetop::prelude::*; /// // Banner global en la región por defecto. /// InRegion::Content.add(Html::with(|_| { diff --git a/src/global.rs b/src/global.rs index 17bbbe94..2d7b1b60 100644 --- a/src/global.rs +++ b/src/global.rs @@ -90,11 +90,12 @@ pub struct App { /// Sección **`[dev]`** de la configuración. Forma parte de [`Settings`]. #[derive(Debug, Deserialize)] pub struct Dev { - /// Directorio raíz de `static/` para servir los archivos estáticos propios de PageTop. + /// Directorio desde el que servir los archivos estáticos de PageTop. /// - /// Si se indica una ruta válida, absoluta o relativa al directorio del proyecto o del binario - /// en ejecución, los archivos estáticos se servirán desde disco. Útil para poder modificar los - /// archivos estáticos mientras la aplicación está en ejecución, sin necesidad de recompilar. + /// Por defecto, los archivos se integran en el binario de la aplicación. Si aquí se indica una + /// ruta válida, ya sea absoluta o relativa al directorio del proyecto o del binario en + /// ejecución, se servirán desde el sistema de ficheros en su lugar. Esto es especialmente útil + /// en desarrollo, ya que evita recompilar el proyecto por cambios en estos archivos. /// /// Si la cadena está vacía, se ignora este ajuste. pub pagetop_static_dir: String, diff --git a/src/html.rs b/src/html.rs index 9f2daace..6020627d 100644 --- a/src/html.rs +++ b/src/html.rs @@ -1,7 +1,7 @@ //! HTML en código. -pub(crate) mod maud; -pub use maud::{DOCTYPE, Escaper, Markup, PreEscaped, Render, display, html, html_private}; +mod maud; +pub use maud::{DOCTYPE, Escaper, Markup, PreEscaped, display, html, html_private}; mod route; pub use route::RoutePath; @@ -20,10 +20,10 @@ pub use logo::PageTopSvg; // **< HTML ATTRIBUTES >**************************************************************************** mod attr; -pub use attr::{Attr, AttrName, AttrValue}; +pub use attr::{Attr, AttrId, AttrName, AttrValue}; -mod props; -pub use props::{Props, PropsOp}; +mod classes; +pub use classes::{Classes, ClassesOp}; mod unit; pub use unit::UnitValue; diff --git a/src/html/assets/favicon.rs b/src/html/assets/favicon.rs index de5880b0..9d0fb688 100644 --- a/src/html/assets/favicon.rs +++ b/src/html/assets/favicon.rs @@ -17,7 +17,7 @@ use crate::{AutoDefault, CowStr}; /// /// # Ejemplo /// -/// ```rust,no_run +/// ```rust /// # use pagetop::prelude::*; /// let favicon = Favicon::new() /// // Estándar de facto admitido por todos los navegadores. diff --git a/src/html/assets/javascript.rs b/src/html/assets/javascript.rs index 03d3cff4..6af0fd55 100644 --- a/src/html/assets/javascript.rs +++ b/src/html/assets/javascript.rs @@ -43,7 +43,7 @@ enum Source { /// /// # Ejemplo /// -/// ```rust,no_run +/// ```rust /// # use pagetop::prelude::*; /// // Script externo con carga diferida, versión de caché y prioridad en el renderizado. /// let script = JavaScript::defer("/assets/js/app.js") diff --git a/src/html/assets/stylesheet.rs b/src/html/assets/stylesheet.rs index d106ae8e..fb71fd44 100644 --- a/src/html/assets/stylesheet.rs +++ b/src/html/assets/stylesheet.rs @@ -60,7 +60,7 @@ impl TargetMedia { /// /// # Ejemplo /// -/// ```rust,no_run +/// ```rust /// # use pagetop::prelude::*; /// // Crea una hoja de estilos externa con control de versión y medio específico (`screen`). /// let stylesheet = StyleSheet::from("/assets/css/main.css") diff --git a/src/html/attr.rs b/src/html/attr.rs index 07fe52f5..8f25a5eb 100644 --- a/src/html/attr.rs +++ b/src/html/attr.rs @@ -8,7 +8,7 @@ use crate::{AutoDefault, builder_fn}; /// /// Este tipo **no impone ninguna normalización ni semántica concreta**; dichas reglas se definen en /// implementaciones concretas como `Attr` y `Attr`, o en tipos específicos como -/// [`AttrName`]. +/// [`AttrId`] y [`AttrName`]. #[derive(AutoDefault, Clone, Debug)] pub struct Attr(Option); @@ -128,6 +128,73 @@ impl Attr { } } +// **< AttrId >************************************************************************************* + +/// Identificador normalizado para el atributo `id` o similar de HTML. +/// +/// Este tipo encapsula `Option` garantizando un valor normalizado para su uso: +/// +/// - Se eliminan los espacios al principio y al final. +/// - Se convierte a minúsculas. +/// - Se sustituyen los espacios (`' '`) intermedios por guiones bajos (`_`). +/// - Si el resultado es una cadena vacía, se guarda `None`. +/// +/// # Ejemplo +/// +/// ```rust +/// # use pagetop::prelude::*; +/// let id = AttrId::new(" main Section "); +/// assert_eq!(id.as_str(), Some("main_section")); +/// +/// let empty = AttrId::default(); +/// assert_eq!(empty.get(), None); +/// ``` +#[derive(AutoDefault, Clone, Debug)] +pub struct AttrId(Attr); + +impl AttrId { + /// Crea un nuevo `AttrId` normalizando el valor. + pub fn new(id: impl AsRef) -> Self { + Self::default().with_id(id) + } + + // **< AttrId BUILDER >************************************************************************* + + /// Establece un identificador nuevo normalizando el valor. + #[builder_fn] + pub fn with_id(mut self, id: impl AsRef) -> Self { + let id = id.as_ref().trim(); + if id.is_empty() { + self.0 = Attr::default(); + } else { + self.0 = Attr::some(id.to_ascii_lowercase().replace(' ', "_")); + } + self + } + + // **< AttrId GETTERS >************************************************************************* + + /// Devuelve el identificador normalizado, si existe. + pub fn get(&self) -> Option { + self.0.get() + } + + /// Devuelve el identificador normalizado (sin clonar), si existe. + pub fn as_str(&self) -> Option<&str> { + self.0.as_str() + } + + /// Devuelve el identificador normalizado (propiedad), si existe. + pub fn into_inner(self) -> Option { + self.0.into_inner() + } + + /// `true` si no hay valor. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + // **< AttrName >*********************************************************************************** /// Nombre normalizado para el atributo `name` o similar de HTML. diff --git a/src/html/classes.rs b/src/html/classes.rs new file mode 100644 index 00000000..903475ec --- /dev/null +++ b/src/html/classes.rs @@ -0,0 +1,205 @@ +use crate::{AutoDefault, builder_fn, util}; + +use std::collections::HashSet; + +/// Operaciones disponibles sobre la lista de clases en [`Classes`]. +/// +/// Cada variante opera sobre **una o más clases** proporcionadas como una cadena separada por +/// espacios (p. ej. `"btn active"`), que se normalizan internamente a minúsculas en +/// [`Classes::with_classes()`]. +/// +/// # Orden de las clases y CSS +/// +/// El navegador aplica los estilos según la especificidad de los selectores y el orden en que las +/// reglas aparecen en la **hoja de estilos**, no por el orden de las clases en el atributo `class`. +/// Por tanto, `"btn active"` y `"active btn"` producen exactamente el mismo resultado visual. +/// +/// Las operaciones [`Add`](Self::Add) y [`Prepend`](Self::Prepend) permiten controlar ese orden +/// únicamente por legibilidad o por convención de proyecto, no porque afecte al comportamiento +/// del navegador. +/// +/// # Reemplazar una clase +/// +/// Para sustituir una clase por otra encadena [`Remove`](Self::Remove) y [`Add`](Self::Add): +/// ```rust +/// # use pagetop::prelude::*; +/// let c = Classes::new("btn btn-primary active") +/// .with_classes(ClassesOp::Remove, "btn-primary") +/// .with_classes(ClassesOp::Add, "btn-secondary"); +/// assert_eq!(c.get(), Some("btn active btn-secondary".to_string())); +/// ``` +#[derive(AutoDefault, Clone, Debug, PartialEq)] +pub enum ClassesOp { + /// Añade las clases que no existan al final. + #[default] + Add, + /// Añade las clases que no existan al principio. + Prepend, + /// Elimina las clases indicadas que existan. + Remove, + /// Alterna presencia/ausencia de una o más clases. + /// + /// Si en una misma llamada se repite una clase (p. ej. `"a a"`) que ya existe, el resultado + /// mantiene la pertenencia pero puede cambiar el orden (primero se elimina y luego se añade al + /// final). + Toggle, + /// Sustituye la lista completa por las clases indicadas. + Reset, +} + +/// Lista de clases CSS normalizadas para el atributo `class` de HTML. +/// +/// Permite construir y modificar dinámicamente con [`ClassesOp`] una lista de clases CSS +/// normalizadas. +/// +/// # Normalización +/// +/// - El orden de las clases no afecta al resultado en CSS; las operaciones de ordenación +/// ([`Add`](ClassesOp::Add), [`Prepend`](ClassesOp::Prepend)) son puramente estéticas. +/// - Solo se acepta una lista de clases con caracteres ASCII. +/// - Las clases se almacenan en minúsculas. +/// - No se permiten clases duplicadas tras la normalización (por ejemplo, `Btn` y `btn` se +/// consideran la misma clase). +/// - Las clases vacías se ignoran. +/// - Sin clases, [`get()`](Self::get) devuelve `None` (no `Some("")`). +/// +/// # Ejemplo +/// +/// ```rust +/// # use pagetop::prelude::*; +/// let classes = Classes::new("Btn btn-primary") +/// .with_classes(ClassesOp::Add, "Active") +/// .with_classes(ClassesOp::Remove, "active") +/// .with_classes(ClassesOp::Add, "Disabled") +/// .with_classes(ClassesOp::Remove, "btn-primary"); +/// +/// assert_eq!(classes.get(), Some("btn disabled".to_string())); +/// assert!(classes.contains("disabled")); +/// ``` +#[derive(AutoDefault, Clone, Debug)] +pub struct Classes(Vec); + +impl Classes { + /// Crea una nueva lista de clases a partir de la clase o clases proporcionadas en `classes`. + pub fn new(classes: impl AsRef) -> Self { + Self::default().with_classes(ClassesOp::default(), classes) + } + + // **< Classes BUILDER >************************************************************************ + + /// Modifica la lista de clases según la operación indicada. + /// + /// Realiza la operación indicada en `op` para las clases proporcionadas en `classes` sobre la + /// lista de clases actual. + #[builder_fn] + pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef) -> Self { + let Some(normalized) = + util::normalize_ascii_or_empty(classes.as_ref(), "Classes::with_classes") + else { + return self; + }; + match op { + ClassesOp::Add => { + self.add(normalized.as_ref().split_ascii_whitespace(), self.0.len()); + } + ClassesOp::Prepend => { + self.add(normalized.as_ref().split_ascii_whitespace(), 0); + } + ClassesOp::Remove => { + let mut classes_to_remove = normalized.as_ref().split_ascii_whitespace(); + + // 0 clases: no se hace nada. + let Some(first) = classes_to_remove.next() else { + return self; + }; + + // 1 clase: un único *retain*, sin reservas extra. + let Some(second) = classes_to_remove.next() else { + self.0.retain(|c| c != first); + return self; + }; + + // 2+ clases: HashSet y un único *retain*. + let mut to_remove: HashSet<&str> = HashSet::new(); + to_remove.insert(first); + to_remove.insert(second); + for class in classes_to_remove { + to_remove.insert(class); + } + self.0.retain(|c| !to_remove.contains(c.as_str())); + } + ClassesOp::Toggle => { + for class in normalized.as_ref().split_ascii_whitespace() { + if let Some(pos) = self.0.iter().position(|c| c == class) { + self.0.remove(pos); + } else { + self.0.push(class.to_string()); + } + } + } + ClassesOp::Reset => { + self.0.clear(); + self.add(normalized.as_ref().split_ascii_whitespace(), 0); + } + } + + self + } + + #[inline] + fn add<'a, I>(&mut self, classes: I, mut pos: usize) + where + I: IntoIterator, + { + for class in classes { + // Inserción segura descartando duplicados. + if !self.0.iter().any(|c| c == class) { + let class = class.to_string(); + if pos >= self.0.len() { + self.0.push(class); + } else { + self.0.insert(pos, class); + } + pos += 1; + } + } + } + + // **< Classes GETTERS >************************************************************************ + + /// Devuelve `true` si no hay clases. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + /// Devuelve la cadena de clases, si existe. + pub fn get(&self) -> Option { + if self.0.is_empty() { + None + } else { + Some(self.0.join(" ")) + } + } + + /// Devuelve `true` si la clase o **todas** las clases indicadas están presentes. + pub fn contains(&self, classes: impl AsRef) -> bool { + let Ok(normalized) = util::normalize_ascii(classes.as_ref()) else { + return false; + }; + normalized + .as_ref() + .split_ascii_whitespace() + .all(|class| self.0.iter().any(|c| c == class)) + } + + /// Devuelve `true` si la clase o **alguna** de las clases indicadas está presente. + pub fn contains_any(&self, classes: impl AsRef) -> bool { + let Ok(normalized) = util::normalize_ascii(classes.as_ref()) else { + return false; + }; + normalized + .as_ref() + .split_ascii_whitespace() + .any(|class| self.0.iter().any(|c| c == class)) + } +} diff --git a/src/html/logo.rs b/src/html/logo.rs index 2e6711e5..7746da7a 100644 --- a/src/html/logo.rs +++ b/src/html/logo.rs @@ -7,7 +7,7 @@ use crate::locale::L10n; /// /// # Ejemplo /// -/// ```rust,no_run +/// ```rust /// # use pagetop::prelude::*; /// fn render_logo(cx: &mut Context) -> Markup { /// html! { diff --git a/src/html/maud.rs b/src/html/maud.rs index cfc35105..ab1236e4 100644 --- a/src/html/maud.rs +++ b/src/html/maud.rs @@ -252,7 +252,7 @@ impl Default for PreEscaped { /// /// A minimal web page: /// -/// ```rust,no_run +/// ```rust /// use pagetop::prelude::*; /// /// let markup = html! { diff --git a/src/html/props.rs b/src/html/props.rs deleted file mode 100644 index 223924af..00000000 --- a/src/html/props.rs +++ /dev/null @@ -1,431 +0,0 @@ -use crate::html::maud::{Escaper, Render}; -use crate::{AutoDefault, CowStr, builder_fn, util}; - -use std::fmt::Write; - -// **< PropsOp >************************************************************************************ - -/// Operaciones disponibles sobre atributos HTML y clases CSS en [`Props`]. -/// -/// Cada variante lleva los datos necesarios para ejecutarse. El método recomendado para usarlas es -/// recurrir a los constructores asociados como [`set()`](Self::set), [`set_id()`](Self::set_id), -/// [`remove()`](Self::remove), [`add_classes()`](Self::add_classes), etc. -/// -/// Las variantes `*Id` operan sobre el atributo `id` del componente. Cuando se usa `"id"` como -/// nombre de atributo en `Set`, el valor se normaliza igual que en `SetId` o `EnsureId`. -/// -/// Las variantes `*Classes` operan siempre sobre la lista de clases CSS para el componente. Cuando -/// se usa `"class"` como nombre en `Set` o `Remove` la operación se aplica a la lista de clases -/// completa. Así, `Set("class", ...)` reemplaza la lista de clases completa por las nuevas clases -/// indicadas, y `Remove("class")` vacía la lista de clases. -#[derive(Clone, Debug, PartialEq)] -pub enum PropsOp { - /// Establece el identificador del elemento normalizando el valor: recorta espacios, convierte a - /// minúsculas y sustituye los espacios intermedios por `_`. Si el resultado es vacío, elimina - /// el identificador. - SetId(CowStr), - /// Establece el identificador del elemento si aún no hay ninguno definido, de modo que no - /// sobrescribe un valor asignado con anterioridad. Aplica la misma normalización que - /// [`SetId`](Self::SetId); si el resultado es vacío, la operación tampoco tiene efecto. - EnsureId(CowStr), - /// Añade el atributo o sustituye su valor si ya existe. Usar `"id"` como nombre aplica la misma - /// normalización que [`SetId`](Self::SetId). Usar `"class"` como nombre reemplaza la lista - /// completa de clases por las nuevas indicadas; la operación se ignora si el valor contiene - /// caracteres no ASCII. - Set(CowStr, CowStr), - /// Elimina el atributo indicado, incluido `"id"`. Si se usa `"class"` como nombre se vacía la - /// lista de clases. - Remove(CowStr), - /// Añade las clases que no existan al final de la lista. La operación se ignora si el valor - /// contiene caracteres no ASCII. - AddClasses(CowStr), - /// Añade las clases que no existan al principio de la lista. La operación se ignora si el valor - /// contiene caracteres no ASCII. - PrependClasses(CowStr), - /// Elimina las clases indicadas de la lista. La operación se ignora si el valor contiene - /// caracteres no ASCII. - RemoveClasses(CowStr), -} - -impl PropsOp { - /// Crea la variante [`SetId`](Self::SetId) con el identificador indicado. - pub fn set_id(id: impl Into) -> Self { - Self::SetId(id.into()) - } - - /// Crea la variante [`EnsureId`](Self::EnsureId) con el identificador indicado. - pub fn ensure_id(id: impl Into) -> Self { - Self::EnsureId(id.into()) - } - - /// Crea la variante [`Set`](Self::Set) con nombre y valor del atributo. - pub fn set(name: impl Into, value: impl Into) -> Self { - Self::Set(name.into(), value.into()) - } - - /// Crea la variante [`Remove`](Self::Remove) para el atributo indicado. - pub fn remove(name: impl Into) -> Self { - Self::Remove(name.into()) - } - - /// Crea la variante [`AddClasses`](Self::AddClasses) con las clases indicadas. - pub fn add_classes(classes: impl Into) -> Self { - Self::AddClasses(classes.into()) - } - - /// Crea la variante [`PrependClasses`](Self::PrependClasses) con las clases indicadas. - pub fn prepend_classes(classes: impl Into) -> Self { - Self::PrependClasses(classes.into()) - } - - /// Crea la variante [`RemoveClasses`](Self::RemoveClasses) con las clases indicadas. - pub fn remove_classes(classes: impl Into) -> Self { - Self::RemoveClasses(classes.into()) - } -} - -// **< Props >************************************************************************************** - -/// Colección de identificador, atributos HTML y clases CSS para aplicar en componentes. -/// -/// Al renderizar en `html!` emite primero `id` (si existe), luego `class` (si hay clases) y después -/// el resto de atributos. -/// -/// # Ejemplo -/// -/// ```rust -/// # use pagetop::prelude::*; -/// let props = Props::new("hx-get", "/api/items") -/// .with_prop(PropsOp::set("hx-target", "#lista")) -/// .with_prop(PropsOp::set("hx-swap", "outerHTML")); -/// -/// let markup = html! { -/// button (props) { "Cargar" } -/// }; -/// -/// assert_eq!( -/// markup.into_string(), -/// r##""## -/// ); -/// ``` -/// -/// # Identificadores -/// -/// [`SetId`](PropsOp::SetId) (usando [`PropsOp::set_id`]) normaliza el valor asignado al -/// identificador del componente: recorta espacios, convierte a minúsculas y sustituye los espacios -/// intermedios por `_`. -/// -/// ```rust -/// # use pagetop::prelude::*; -/// let props = Props::default().with_id("My Button"); -/// let markup = html! { button (props) { "OK" } }; -/// assert_eq!(markup.into_string(), r#""#); -/// ``` -/// -/// [`EnsureId`](PropsOp::EnsureId) (usando [`PropsOp::ensure_id`]) sólo asigna si no -/// hay identificador previo: -/// -/// ```rust -/// # use pagetop::prelude::*; -/// // Con `id` previo: `EnsureId` no tiene efecto. -/// let props = Props::default() -/// .with_id("explicit") -/// .with_prop(PropsOp::ensure_id("default")); -/// assert_eq!(props.get_id(), Some("explicit".to_string())); -/// -/// // Sin `id` previo: `EnsureId` asigna el valor. -/// let props = Props::default().with_prop(PropsOp::ensure_id("default")); -/// assert_eq!(props.get_id(), Some("default".to_string())); -/// ``` -/// -/// # Clases CSS -/// -/// ```rust -/// # use pagetop::prelude::*; -/// let props = Props::default() -/// .with_prop(PropsOp::add_classes("btn btn-primary")) -/// .with_prop(PropsOp::add_classes("active")); -/// -/// let markup = html! { button (props) { "OK" } }; -/// assert_eq!(markup.into_string(), r#""#); -/// ``` -/// -/// # Integración en componentes -/// -/// El patrón recomendado es añadir un campo `props: Props` con su método *builder* delegado: -/// -/// ```rust,no_run -/// # use pagetop::prelude::*; -/// #[derive(AutoDefault, Clone, Getters)] -/// pub struct MyButton { -/// label: L10n, -/// props: Props, -/// } -/// -/// impl Component for MyButton { -/// fn new() -> Self { Self::default() } -/// -/// fn prepare(&self, cx: &mut Context) -> Result { -/// Ok(html! { -/// button (self.props()) { -/// (self.label().using(cx)) -/// } -/// }) -/// } -/// } -/// -/// impl MyButton { -/// /// Modifica identificador, clases CSS o atributos HTML del elemento raíz. -/// #[builder_fn] -/// pub fn with_prop(mut self, op: PropsOp) -> Self { -/// self.props.alter_prop(op); -/// self -/// } -/// } -/// ``` -#[derive(AutoDefault, Clone, Debug)] -pub struct Props { - id: Option, - attrs: Vec<(CowStr, CowStr)>, - classes: Vec, -} - -impl Props { - /// Crea una colección con un primer atributo ya establecido. - pub fn new(name: impl Into, value: impl Into) -> Self { - Self::default().with_prop(PropsOp::set(name, value)) - } - - /// Crea una colección con las clases CSS iniciales indicadas. - pub fn classes(classes: impl Into) -> Self { - Self::default().with_prop(PropsOp::add_classes(classes)) - } - - // **< Props BUILDER >************************************************************************** - - /// Establece el identificador del componente; equivale a `with_prop(PropsOp::set_id(id))`. - #[builder_fn] - pub fn with_id(mut self, id: impl Into) -> Self { - self.apply_id(id.into().as_ref()); - self - } - - /// Modifica el identificador, los atributos o las clases según la operación indicada. - /// - /// - [`SetId(value)`](PropsOp::SetId) establece el identificador normalizando el valor. - /// - [`EnsureId(value)`](PropsOp::EnsureId) establece el identificador (con la misma - /// normalización) sólo si no hay ninguno definido. - /// - [`Set(name, value)`](PropsOp::Set) añade el atributo o reemplaza su valor. - /// `Set("id", ...)` aplica la misma normalización que `SetId`. - /// `Set("class", ...)` reemplaza la lista de clases completa. - /// - [`Remove(name)`](PropsOp::Remove) elimina el atributo. `Remove("id")` elimina el - /// identificador. `Remove("class")` vacía la lista de clases. - /// - [`AddClasses(clases)`](PropsOp::AddClasses) añade clases al final (sin duplicados). - /// - [`PrependClasses(clases)`](PropsOp::PrependClasses) añade clases al principio (sin - /// duplicados). - /// - [`RemoveClasses(clases)`](PropsOp::RemoveClasses) elimina las clases indicadas. - #[builder_fn] - pub fn with_prop(mut self, op: PropsOp) -> Self { - match op { - PropsOp::SetId(value) => { - self.apply_id(value.as_ref()); - } - PropsOp::EnsureId(value) => { - if self.id.is_none() { - self.apply_id(value.as_ref()); - } - } - PropsOp::Set(name, value) => { - if name.as_ref() == "id" { - self.apply_id(value.as_ref()); - } else if name.as_ref() == "class" { - if let Some(normalized) = - util::normalize_ascii_or_empty(value.as_ref(), "Props::with_prop") - { - self.classes.clear(); - self.insert_classes(normalized.as_ref().split_ascii_whitespace(), 0); - } - } else if let Some(pos) = self.attrs.iter().position(|(k, _)| k == &name) { - self.attrs[pos].1 = value; - } else { - self.attrs.push((name, value)); - } - } - PropsOp::Remove(name) => { - if name.as_ref() == "id" { - self.id = None; - } else if name.as_ref() == "class" { - self.classes.clear(); - } else { - self.attrs.retain(|(k, _)| k != &name); - } - } - PropsOp::AddClasses(classes) => { - let Some(normalized) = - util::normalize_ascii_or_empty(classes.as_ref(), "Props::with_prop") - else { - return self; - }; - let pos = self.classes.len(); - self.insert_classes(normalized.as_ref().split_ascii_whitespace(), pos); - } - PropsOp::PrependClasses(classes) => { - let Some(normalized) = - util::normalize_ascii_or_empty(classes.as_ref(), "Props::with_prop") - else { - return self; - }; - self.insert_classes(normalized.as_ref().split_ascii_whitespace(), 0); - } - PropsOp::RemoveClasses(classes) => { - let Some(normalized) = - util::normalize_ascii_or_empty(classes.as_ref(), "Props::with_prop") - else { - return self; - }; - self.classes.retain(|c| { - !normalized - .as_ref() - .split_ascii_whitespace() - .any(|r| r == c.as_str()) - }); - } - } - self - } - - // **< Props GETTERS >************************************************************************** - - /// Devuelve el identificador normalizado del elemento, si existe. - #[inline] - pub fn get_id(&self) -> Option { - self.id.clone() - } - - /// Devuelve el valor del atributo indicado, si existe. - /// - /// Los nombres `"id"` y `"class"` son equivalentes a llamar a [`get_id()`](Self::get_id) y - /// [`get_classes()`](Self::get_classes) respectivamente. - pub fn get_prop(&self, name: impl AsRef) -> Option { - match name.as_ref() { - "id" => self.id.clone(), - "class" => self.get_classes(), - name => self - .attrs - .iter() - .find(|(k, _)| k.as_ref() == name) - .map(|(_, v)| v.to_string()), - } - } - - /// Devuelve la lista de clases como cadena de texto, si hay clases definidas. - pub fn get_classes(&self) -> Option { - if self.classes.is_empty() { - None - } else { - Some(self.classes.join(" ")) - } - } - - /// Devuelve `true` si no hay ningún identificador definido. - #[inline] - pub fn is_id_empty(&self) -> bool { - self.id.is_none() - } - - /// Devuelve `true` si no hay ningún atributo extra definido, sin tener en cuenta el - /// identificador ni las clases. - #[inline] - pub fn is_attrs_empty(&self) -> bool { - self.attrs.is_empty() - } - - /// Devuelve `true` si no hay ninguna clase definida. - #[inline] - pub fn is_classes_empty(&self) -> bool { - self.classes.is_empty() - } - - /// Devuelve `true` si no hay ningún identificador, atributo ni clase definidos. - #[inline] - pub fn is_empty(&self) -> bool { - self.id.is_none() && self.attrs.is_empty() && self.classes.is_empty() - } - - /// Devuelve `true` si la clase o **todas** las clases indicadas están presentes. - pub fn has_class(&self, classes: impl AsRef) -> bool { - let Ok(normalized) = util::normalize_ascii(classes.as_ref()) else { - return false; - }; - normalized - .as_ref() - .split_ascii_whitespace() - .all(|class| self.classes.iter().any(|c| c == class)) - } - - /// Devuelve `true` si la clase o **alguna** de las clases indicadas está presente. - pub fn has_any_class(&self, classes: impl AsRef) -> bool { - let Ok(normalized) = util::normalize_ascii(classes.as_ref()) else { - return false; - }; - normalized - .as_ref() - .split_ascii_whitespace() - .any(|class| self.classes.iter().any(|c| c == class)) - } - - // **< Props PRIVATE >************************************************************************** - - fn apply_id(&mut self, id: &str) { - let id = id.trim(); - self.id = if id.is_empty() { - None - } else { - Some(id.to_ascii_lowercase().replace(' ', "_")) - }; - } - - fn insert_classes<'a, I>(&mut self, classes: I, mut pos: usize) - where - I: IntoIterator, - { - for class in classes { - if !self.classes.iter().any(|c| c == class) { - let class = class.to_string(); - if pos >= self.classes.len() { - self.classes.push(class); - } else { - self.classes.insert(pos, class); - } - pos += 1; - } - } - } -} - -#[doc(hidden)] -impl Render for Props { - fn render_to(&self, w: &mut String) { - if let Some(id) = self.id.as_deref() { - w.push_str(" id=\""); - let _ = write!(Escaper::new(w), "{}", id); - w.push('"'); - } - if let Some((first, rest)) = self.classes.split_first() { - w.push_str(" class=\""); - let _ = write!(Escaper::new(w), "{}", first); - for class in rest { - w.push(' '); - let _ = write!(Escaper::new(w), "{}", class); - } - w.push('"'); - } - for (name, value) in &self.attrs { - w.push(' '); - let _ = write!(Escaper::new(w), "{}", name); - w.push_str("=\""); - let _ = write!(Escaper::new(w), "{}", value); - w.push('"'); - } - } -} diff --git a/src/html/unit.rs b/src/html/unit.rs index 3df35612..cb564c71 100644 --- a/src/html/unit.rs +++ b/src/html/unit.rs @@ -263,7 +263,7 @@ impl FromStr for UnitValue { /// Deserializa desde una cadena usando la misma gramática que [`FromStr`]. /// /// # Ejemplo con `serde_json` -/// ```rust,no_run +/// ```rust /// # use pagetop::prelude::*; /// use serde::Deserialize; /// diff --git a/src/lib.rs b/src/lib.rs index 40689318..9e900335 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ /*!
- +

PageTop

@@ -86,7 +86,7 @@ estructurar e inicializar la aplicación de forma modular. #![cfg_attr(docsrs, feature(doc_cfg))] #![doc( - html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/assets/favicon.ico" + html_favicon_url = "https://git.cillero.es/manuelcillero/pagetop/raw/branch/main/static/favicon.ico" )] // Alias para que las rutas absolutas `::pagetop::...` generadas por las macros funcionen en el @@ -102,7 +102,7 @@ use std::ops::Deref; /// /// Útil para versionar recursos estáticos de PageTop desde otros *crates*. Por ejemplo: /// -/// ```rust,no_run +/// ```rust /// use pagetop::prelude::*; /// /// pub struct MyTheme; @@ -138,14 +138,14 @@ pub use pagetop_statics::{StaticFile, resource}; pub use getter_methods::Getters; -/// Contenedor para un paquete de recursos embebidos. +/// Contenedor para un conjunto de recursos embebidos. #[derive(AutoDefault)] pub struct StaticResources { bundle: HashMap<&'static str, StaticFile>, } impl StaticResources { - /// Crea un contenedor para un paquete de recursos generado por `build.rs` (consultar + /// Crea un contenedor para un conjunto de recursos generado por `build.rs` (consultar /// [`pagetop_build`](https://docs.rs/pagetop-build)). pub fn new(bundle: HashMap<&'static str, StaticFile>) -> Self { Self { bundle } diff --git a/src/locale.rs b/src/locale.rs index c9c6d7a8..a019d665 100644 --- a/src/locale.rs +++ b/src/locale.rs @@ -75,7 +75,7 @@ //! Si los recursos se encuentran en el directorio por defecto `src/locale` del *crate*, sólo hay //! que declarar: //! -//! ```rust,no_run +//! ```rust //! # use pagetop::prelude::*; //! include_locales!(LOCALES_SAMPLE); //! ``` @@ -125,7 +125,7 @@ pub use l10n::L10n; /// /// Uso básico con el directorio por defecto `"src/locale"`: /// -/// ```rust,no_run +/// ```rust /// # use pagetop::prelude::*; /// include_locales!(LOCALES_SAMPLE); /// ``` diff --git a/src/locale/definition.rs b/src/locale/definition.rs index e9dfe9fd..bffc805c 100644 --- a/src/locale/definition.rs +++ b/src/locale/definition.rs @@ -54,7 +54,7 @@ pub trait LangId { /// resuelve un idioma soportado o porque se aplica el idioma por defecto o, en último término, el /// de respaldo (`"en-US"`): /// -/// ```rust,no_run +/// ```rust /// # use pagetop::prelude::*; /// // Idioma por defecto si no resuelve. /// let lang = Locale::resolve("it-IT"); diff --git a/src/locale/en-US/base.ftl b/src/locale/en-US/base.ftl index b244f9e2..76baa120 100644 --- a/src/locale/en-US/base.ftl +++ b/src/locale/en-US/base.ftl @@ -1,6 +1,3 @@ -# Form components. -field_required = This field is required - # Intro component. intro_default_title = Hello, world! intro_default_slogan = Discover⚡{ $app } diff --git a/src/locale/es-ES/base.ftl b/src/locale/es-ES/base.ftl index 50a459ec..09867d13 100644 --- a/src/locale/es-ES/base.ftl +++ b/src/locale/es-ES/base.ftl @@ -1,6 +1,3 @@ -# Form components. -field_required = Este campo es obligatorio - # Intro component. intro_default_title = ¡Hola, mundo! intro_default_slogan = Descubre⚡{ $app } diff --git a/src/locale/l10n.rs b/src/locale/l10n.rs index 66f20dc5..e75e103d 100644 --- a/src/locale/l10n.rs +++ b/src/locale/l10n.rs @@ -37,7 +37,7 @@ enum L10nOp { /// /// Los argumentos dinámicos se añaden con `with_arg()` o `with_args()`. /// -/// ```rust,no_run +/// ```rust /// # use pagetop::prelude::*; /// // Texto literal sin traducción. /// let raw = L10n::n("© 2025 PageTop").get(); @@ -128,7 +128,7 @@ impl L10n { /// /// # Ejemplo /// - /// ```rust,no_run + /// ```rust /// # use pagetop::prelude::*; /// let text = L10n::l("greeting").with_arg("name", "Manuel").get(); /// ``` @@ -142,7 +142,7 @@ impl L10n { /// /// # Ejemplo /// - /// ```rust,no_run + /// ```rust /// # use pagetop::prelude::*; /// struct ResourceLang; /// @@ -180,7 +180,7 @@ impl L10n { /// /// # Ejemplo /// - /// ```rust,no_run + /// ```rust /// # use pagetop::prelude::*; /// let html = L10n::l("welcome.message").using(&Locale::resolve("es")); /// ``` diff --git a/src/prelude.rs b/src/prelude.rs index fbd84da3..5e6f7ec1 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -36,7 +36,7 @@ pub use crate::locale::*; pub use crate::datetime::*; pub use crate::web; -pub use crate::web::{HttpRequest, IntoResponse, Response, Router}; +pub use crate::web::{HttpRequest, Router}; pub use crate::core::{AnyCast, AnyInfo, TypeInfo}; diff --git a/src/response/json.rs b/src/response/json.rs index 64873839..7ef4b402 100644 --- a/src/response/json.rs +++ b/src/response/json.rs @@ -5,7 +5,7 @@ //! Convierte automáticamente el cuerpo de una petición con `Content-Type: application/json` en un //! tipo Rust fuertemente tipado, validando el formato y deserializando con *serde*. //! -//! ```rust,no_run +//! ```rust //! # use pagetop::prelude::*; //! #[derive(serde::Deserialize)] //! struct NuevoUsuario { nombre: String, email: String } @@ -23,7 +23,7 @@ //! Serializa valores Rust a JSON y genera una respuesta HTTP con el encabezado apropiado //! `application/json; charset=utf-8`, todo con una llamada compacta. //! -//! ```rust,no_run +//! ```rust //! # use pagetop::prelude::*; //! #[derive(serde::Serialize)] //! struct Usuario { id: u32, nombre: String } diff --git a/src/response/page.rs b/src/response/page.rs index 129ccf03..2376fd39 100644 --- a/src/response/page.rs +++ b/src/response/page.rs @@ -20,7 +20,8 @@ use crate::base::action; use crate::core::component::{AssetsOp, ChildOp, Context, ContextError, Contextual}; use crate::core::theme::{DefaultRegion, Region, RegionRef, TemplateRef, ThemeRef}; use crate::html::{Assets, Favicon, JavaScript, StyleSheet}; -use crate::html::{Attr, Props, PropsOp}; +use crate::html::{Attr, AttrId}; +use crate::html::{Classes, ClassesOp}; use crate::html::{DOCTYPE, Markup, html}; use crate::locale::{CharacterDirection, L10n, LangId, LanguageIdentifier}; use crate::web::HttpRequest; @@ -89,6 +90,8 @@ pub struct Page { description : Attr, metadata : Vec<(&'static str, &'static str)>, properties : Vec<(&'static str, &'static str)>, + body_id : AttrId, + body_classes: Classes, context : Context, } @@ -104,6 +107,8 @@ impl Page { description : Attr::::default(), metadata : Vec::default(), properties : Vec::default(), + body_id : AttrId::default(), + body_classes: Classes::default(), context : Context::new(Some(request)), } } @@ -138,6 +143,20 @@ impl Page { self } + /// Establece el atributo `id` del elemento ``. + #[builder_fn] + pub fn with_body_id(mut self, id: impl AsRef) -> Self { + self.body_id.alter_id(id); + self + } + + /// Modifica las clases CSS del elemento `` con una operación sobre [`Classes`]. + #[builder_fn] + pub fn with_body_classes(mut self, op: ClassesOp, classes: impl AsRef) -> Self { + self.body_classes.alter_classes(op, classes); + self + } + // **< Page GETTERS >*************************************************************************** /// Devuelve el título traducido para el idioma de la página, si existe. @@ -160,6 +179,16 @@ impl Page { &self.properties } + /// Devuelve el identificador del elemento ``. + pub fn body_id(&self) -> &AttrId { + &self.body_id + } + + /// Devuelve las clases CSS del elemento ``. + pub fn body_classes(&self) -> &Classes { + &self.body_classes + } + /// Devuelve una referencia mutable al [`Context`] de la página. /// /// El [`Context`] actúa como intermediario para muchos métodos de `Page` (idioma, tema, @@ -233,7 +262,7 @@ impl Page { head { (head) } - body (self.body_props()) { + body id=[self.body_id().get()] class=[self.body_classes().get()] { (body) } } @@ -291,12 +320,6 @@ impl Contextual for Page { self } - #[builder_fn] - fn with_body_props(mut self, op: PropsOp) -> Self { - self.context.alter_body_props(op); - self - } - #[builder_fn] fn with_child(mut self, op: impl Into) -> Self { self.context @@ -340,10 +363,6 @@ impl Contextual for Page { self.context.javascripts() } - fn body_props(&self) -> &Props { - self.context.body_props() - } - // **< Contextual HELPERS >********************************************************************* fn remove_param(&mut self, key: &'static str) -> bool { diff --git a/assets/banner.png b/static/banner.png similarity index 100% rename from assets/banner.png rename to static/banner.png diff --git a/static/css/basic.css b/static/css/basic.css new file mode 100644 index 00000000..9b23b272 --- /dev/null +++ b/static/css/basic.css @@ -0,0 +1,43 @@ +:root { + /* Font families */ + --val-font-sans: system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif; + --val-font-serif: Georgia,"Times New Roman",serif; + --val-font-monospace: SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace; + --val-font-family: var(--val-font-sans); + /* Font size */ + --val-fs--base: 1rem; + /* Font weight */ + --val-fw--base: 400; + /* Line height */ + --val-lh--base: 1.5; + /* Colors */ + --val-color--bg: #fafafa; + --val-color--text: #212529; +} + +*, *::before, *::after { + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; +} + +body { + font-family: var(--val-font-family); + font-size: var(--val-fs--base); + font-weight: var(--val-fw--base); + line-height: var(--val-lh--base); + color: var(--val-color--text); + background-color: var(--val-color--bg); + -webkit-tap-highlight-color: transparent; +} + +/* + * Region Footer + */ + +.region-footer { + padding: .75rem 0 3rem; + text-align: center; +} diff --git a/assets/css/intro.css b/static/css/intro.css similarity index 94% rename from assets/css/intro.css rename to static/css/intro.css index cc60b7b8..00fe0d21 100644 --- a/assets/css/intro.css +++ b/static/css/intro.css @@ -389,10 +389,10 @@ body { .intro-text-body .block { position: relative; } -.intro-text-body .block-title { +.intro-text-body .block__title { margin: 1em 0 .8em; } -.intro-text-body .block-title span { +.intro-text-body .block__title span { display: inline-block; padding: 10px 30px 14px; margin: 30px 20px 0; @@ -403,7 +403,7 @@ body { border-color: orangered; transform: rotate(-3deg) translateY(-25%); } -.intro-text-body .block-title:before { +.intro-text-body .block__title:before { content: ""; height: 5px; position: absolute; @@ -416,7 +416,7 @@ body { transform: rotate(2deg) translateY(-50%); transform-origin: top left; } -.intro-text-body .block-title:after { +.intro-text-body .block__title:after { content: ""; height: 120%; position: absolute; @@ -427,22 +427,22 @@ body { background: var(--intro-bg-block-1); transform: rotate(2deg); } -.intro-text-body .block:nth-of-type(6n+1) .block-title:after { +.intro-text-body .block:nth-of-type(6n+1) .block__title:after { background: var(--intro-bg-block-1); } -.intro-text-body .block:nth-of-type(6n+2) .block-title:after { +.intro-text-body .block:nth-of-type(6n+2) .block__title:after { background: var(--intro-bg-block-2); } -.intro-text-body .block:nth-of-type(6n+3) .block-title:after { +.intro-text-body .block:nth-of-type(6n+3) .block__title:after { background: var(--intro-bg-block-3); } -.intro-text-body .block:nth-of-type(6n+4) .block-title:after { +.intro-text-body .block:nth-of-type(6n+4) .block__title:after { background: var(--intro-bg-block-4); } -.intro-text-body .block:nth-of-type(6n+5) .block-title:after { +.intro-text-body .block:nth-of-type(6n+5) .block__title:after { background: var(--intro-bg-block-5); } -.intro-text-body .block:nth-of-type(6n+6) .block-title:after { +.intro-text-body .block:nth-of-type(6n+6) .block__title:after { background: var(--intro-bg-block-6); } diff --git a/assets/css/normalize.css b/static/css/normalize.css similarity index 100% rename from assets/css/normalize.css rename to static/css/normalize.css diff --git a/assets/favicon.ico b/static/favicon.ico similarity index 100% rename from assets/favicon.ico rename to static/favicon.ico diff --git a/assets/img/intro-header-sm.avif b/static/img/intro-header-sm.avif similarity index 100% rename from assets/img/intro-header-sm.avif rename to static/img/intro-header-sm.avif diff --git a/assets/img/intro-header-sm.jpg b/static/img/intro-header-sm.jpg similarity index 100% rename from assets/img/intro-header-sm.jpg rename to static/img/intro-header-sm.jpg diff --git a/assets/img/intro-header-sm.webp b/static/img/intro-header-sm.webp similarity index 100% rename from assets/img/intro-header-sm.webp rename to static/img/intro-header-sm.webp diff --git a/assets/img/intro-header.avif b/static/img/intro-header.avif similarity index 100% rename from assets/img/intro-header.avif rename to static/img/intro-header.avif diff --git a/assets/img/intro-header.jpg b/static/img/intro-header.jpg similarity index 100% rename from assets/img/intro-header.jpg rename to static/img/intro-header.jpg diff --git a/assets/img/intro-header.webp b/static/img/intro-header.webp similarity index 100% rename from assets/img/intro-header.webp rename to static/img/intro-header.webp diff --git a/tests/component_children.rs b/tests/component_children.rs index dc5eb5cc..b224f1b8 100644 --- a/tests/component_children.rs +++ b/tests/component_children.rs @@ -1,13 +1,13 @@ use pagetop::prelude::*; -// **< TestComp - componente mínimo para los tests >************************************************ +// **< TestComp — componente mínimo para los tests >************************************************ // // Componente con id configurable y texto fijo de salida. El id permite probar las operaciones de // `Children` basadas en identificador (`InsertAfterId`, `RemoveById`, etc.). #[derive(AutoDefault, Clone)] struct TestComp { - props: Props, + id: AttrId, text: String, } @@ -17,7 +17,7 @@ impl Component for TestComp { } fn id(&self) -> Option { - self.props.get_id() + self.id.get() } fn prepare(&self, _cx: &mut Context) -> Result { @@ -29,7 +29,7 @@ impl TestComp { /// Crea un componente con id y texto de salida fijos. fn tagged(id: &str, text: &str) -> Self { let mut c = Self::default(); - c.props.alter_prop(PropsOp::set_id(id.to_string())); + c.id.alter_id(id); c.text = text.to_string(); c } @@ -303,8 +303,7 @@ async fn embed_get_allows_mutating_component() { let embed = Embed::with(TestComp::tagged("orig", "texto")); // El `;` final convierte el `if let` en sentencia y libera el guard antes que `embed`. if let Some(mut comp) = embed.get() { - comp.props - .alter_prop(PropsOp::set_id("modificado".to_string())); + comp.id.alter_id("modificado"); }; assert_eq!(embed.id(), Some("modificado".to_string())); } @@ -332,8 +331,7 @@ async fn embed_clone_is_deep() { let clone = original.clone(); // Mutar el clon no debe afectar al original. if let Some(mut comp) = clone.get() { - comp.props - .alter_prop(PropsOp::set_id("clone-id".to_string())); + comp.id.alter_id("clone-id"); } assert_eq!(original.id(), Some("orig".to_string())); assert_eq!(clone.id(), Some("clone-id".to_string())); diff --git a/tests/html_classes.rs b/tests/html_classes.rs index 6beeca44..e2198335 100644 --- a/tests/html_classes.rs +++ b/tests/html_classes.rs @@ -1,7 +1,7 @@ use pagetop::prelude::*; -fn assert_classes(p: &Props, expected: Option<&str>) { - let got = p.get_classes(); +fn assert_classes(c: &Classes, expected: Option<&str>) { + let got = c.get(); assert_eq!( got.as_deref(), expected, @@ -15,154 +15,176 @@ fn assert_classes(p: &Props, expected: Option<&str>) { #[pagetop::test] async fn classes_new_empty_and_whitespace_is_empty() { - assert_classes(&Props::classes(""), None); - assert_classes(&Props::classes(" "), None); - assert_classes(&Props::classes("\t\n\r "), None); + assert_classes(&Classes::new(""), None); + assert_classes(&Classes::new(" "), None); + assert_classes(&Classes::new("\t\n\r "), None); } #[pagetop::test] async fn classes_new_normalizes_and_dedups_and_preserves_first_occurrence_order() { - let p = Props::classes("Btn btn BTN btn-primary BTN-PRIMARY"); - assert_classes(&p, Some("btn btn-primary")); - assert!(p.has_class("BTN")); - assert!(p.has_class("btn-primary")); + let c = Classes::new("Btn btn BTN btn-primary BTN-PRIMARY"); + assert_classes(&c, Some("btn btn-primary")); + assert!(c.contains("BTN")); + assert!(c.contains("btn-primary")); } #[pagetop::test] async fn classes_get_returns_none_when_empty_some_when_not() { - assert_classes(&Props::classes(" "), None); - assert_classes(&Props::classes("a"), Some("a")); + assert_classes(&Classes::new(" "), None); + assert_classes(&Classes::new("a"), Some("a")); } // **< Basic operations (add/prepend/set) >********************************************************* #[pagetop::test] async fn classes_add_appends_unique_and_normalizes() { - let p = Props::classes("a b").with_prop(PropsOp::add_classes("C b D")); - assert_classes(&p, Some("a b c d")); + let c = Classes::new("a b").with_classes(ClassesOp::Add, "C b D"); + assert_classes(&c, Some("a b c d")); } #[pagetop::test] async fn classes_add_ignores_empty_input() { - let p = Props::classes("a b").with_prop(PropsOp::add_classes(" \t")); - assert_classes(&p, Some("a b")); + let c = Classes::new("a b").with_classes(ClassesOp::Add, " \t"); + assert_classes(&c, Some("a b")); } #[pagetop::test] async fn classes_add_same_tokens() { - let p = Props::classes("a b").with_prop(PropsOp::add_classes("A B a b")); - assert_classes(&p, Some("a b")); + let c = Classes::new("a b").with_classes(ClassesOp::Add, "A B a b"); + assert_classes(&c, Some("a b")); } #[pagetop::test] async fn classes_add_rejects_non_ascii_is_noop() { - let p = Props::classes("a b").with_prop(PropsOp::add_classes("c ñ d")); - assert_classes(&p, Some("a b")); + let c = Classes::new("a b").with_classes(ClassesOp::Add, "c ñ d"); + assert_classes(&c, Some("a b")); } #[pagetop::test] async fn classes_prepend_inserts_at_front_preserving_new_order() { - let p = Props::classes("c d").with_prop(PropsOp::prepend_classes("A b")); - assert_classes(&p, Some("a b c d")); + let c = Classes::new("c d").with_classes(ClassesOp::Prepend, "A b"); + assert_classes(&c, Some("a b c d")); } #[pagetop::test] async fn classes_prepend_inserts_new_tokens_skipping_duplicates() { - let p = Props::classes("b c").with_prop(PropsOp::prepend_classes("a b d")); - assert_classes(&p, Some("a d b c")); + let c = Classes::new("b c").with_classes(ClassesOp::Prepend, "a b d"); + assert_classes(&c, Some("a d b c")); } #[pagetop::test] async fn classes_prepend_ignores_empty_input() { - let p = Props::classes("a b").with_prop(PropsOp::prepend_classes("")); - assert_classes(&p, Some("a b")); + let c = Classes::new("a b").with_classes(ClassesOp::Prepend, ""); + assert_classes(&c, Some("a b")); } #[pagetop::test] async fn classes_reset_replaces_entire_list_and_dedups() { - let p = Props::classes("a b c").with_prop(PropsOp::set("class", "X y y Z")); - assert_classes(&p, Some("x y z")); + let c = Classes::new("a b c").with_classes(ClassesOp::Reset, "X y y Z"); + assert_classes(&c, Some("x y z")); } #[pagetop::test] async fn classes_reset_with_empty_input_clears() { - let p = Props::classes("a b").with_prop(PropsOp::set("class", " \n ")); - assert_classes(&p, None); + let base = Classes::new("a b"); + let c = base.with_classes(ClassesOp::Reset, " \n "); + assert_classes(&c, None); } -#[pagetop::test] -async fn classes_reset_with_non_ascii_is_noop() { - let p = Props::classes("a b").with_prop(PropsOp::set("class", "ñ")); - assert_classes(&p, Some("a b")); -} - -// **< Mutation operations (remove) >*************************************************************** +// **< Mutation operations (remove/toggle) >******************************************************** #[pagetop::test] async fn classes_remove_is_case_insensitive() { - let p = Props::classes("a b c d").with_prop(PropsOp::remove_classes("B D")); - assert_classes(&p, Some("a c")); + let c = Classes::new("a b c d").with_classes(ClassesOp::Remove, "B D"); + assert_classes(&c, Some("a c")); } #[pagetop::test] async fn classes_remove_non_existing_is_noop() { - let p = Props::classes("a b c").with_prop(PropsOp::remove_classes("x y z")); - assert_classes(&p, Some("a b c")); + let c = Classes::new("a b c").with_classes(ClassesOp::Remove, "x y z"); + assert_classes(&c, Some("a b c")); } #[pagetop::test] async fn classes_remove_with_extra_whitespace() { - let p = Props::classes("a b c d").with_prop(PropsOp::remove_classes(" b\t\t \n d ")); - assert_classes(&p, Some("a c")); + let c = Classes::new("a b c d").with_classes(ClassesOp::Remove, " b\t\t \n d "); + assert_classes(&c, Some("a c")); +} + +#[pagetop::test] +async fn classes_toggle_removes_if_present_case_insensitive() { + let c = Classes::new("a b c").with_classes(ClassesOp::Toggle, "B"); + assert_classes(&c, Some("a c")); +} + +#[pagetop::test] +async fn classes_toggle_adds_if_missing_and_normalizes() { + let c = Classes::new("a b").with_classes(ClassesOp::Toggle, "C"); + assert_classes(&c, Some("a b c")); +} + +#[pagetop::test] +async fn classes_toggle_multiple_tokens_is_sequential_and_order_dependent() { + let c = Classes::new("a b").with_classes(ClassesOp::Toggle, "C B A"); + assert_classes(&c, Some("c")); +} + +#[pagetop::test] +async fn classes_toggle_duplicate_tokens_are_applied_sequentially() { + let c = Classes::new("b").with_classes(ClassesOp::Toggle, "a a"); + assert_classes(&c, Some("b")); + + let c = Classes::new("a b").with_classes(ClassesOp::Toggle, "a a"); + assert_classes(&c, Some("b a")); } // **< Queries (contains) >************************************************************************* #[pagetop::test] async fn classes_contains_single() { - let p = Props::classes("btn btn-primary"); - assert!(p.has_class("btn")); - assert!(p.has_class("BTN")); - assert!(!p.has_class("missing")); + let c = Classes::new("btn btn-primary"); + assert!(c.contains("btn")); + assert!(c.contains("BTN")); + assert!(!c.contains("missing")); } #[pagetop::test] async fn classes_contains_all_and_any() { - let p = Props::classes("btn btn-primary active"); + let c = Classes::new("btn btn-primary active"); - assert!(p.has_class("btn active")); - assert!(p.has_class("BTN BTN-PRIMARY")); - assert!(!p.has_class("btn missing")); + assert!(c.contains("btn active")); + assert!(c.contains("BTN BTN-PRIMARY")); + assert!(!c.contains("btn missing")); - assert!(p.has_any_class("missing active")); - assert!(p.has_any_class("BTN-PRIMARY missing")); - assert!(!p.has_any_class("missing other")); + assert!(c.contains_any("missing active")); + assert!(c.contains_any("BTN-PRIMARY missing")); + assert!(!c.contains_any("missing other")); } #[pagetop::test] async fn classes_contains_empty_and_whitespace_is_false() { - let p = Props::classes("a b"); - assert!(!p.has_class("")); - assert!(!p.has_class(" \t")); - assert!(!p.has_any_class("")); - assert!(!p.has_any_class(" \n ")); + let c = Classes::new("a b"); + assert!(!c.contains("")); + assert!(!c.contains(" \t")); + assert!(!c.contains_any("")); + assert!(!c.contains_any(" \n ")); } #[pagetop::test] async fn classes_contains_non_ascii_is_false() { - let p = Props::classes("a b"); - assert!(!p.has_class("ñ")); - assert!(!p.has_any_class("a ñ")); + let c = Classes::new("a b"); + assert!(!c.contains("ñ")); + assert!(!c.contains_any("a ñ")); } // **< Properties / regression (combined sequences, ordering) >************************************* #[pagetop::test] async fn classes_order_is_stable_for_existing_items() { - let p = Props::classes("a b c") - .with_prop(PropsOp::add_classes("d")) // a b c d - .with_prop(PropsOp::prepend_classes("x")) // x a b c d - .with_prop(PropsOp::remove_classes("b")) // x a c d - .with_prop(PropsOp::add_classes("b")); // x a c d b - assert_classes(&p, Some("x a c d b")); + let c = Classes::new("a b c") + .with_classes(ClassesOp::Add, "d") // a b c d + .with_classes(ClassesOp::Prepend, "x") // x a b c d + .with_classes(ClassesOp::Remove, "b") // x a c d + .with_classes(ClassesOp::Add, "b"); // x a c d b + assert_classes(&c, Some("x a c d b")); } diff --git a/tests/html_props.rs b/tests/html_props.rs deleted file mode 100644 index 3db26746..00000000 --- a/tests/html_props.rs +++ /dev/null @@ -1,363 +0,0 @@ -use pagetop::prelude::*; - -// **< Construction & invariants >****************************************************************** - -#[pagetop::test] -async fn props_default_renders_nothing() { - assert_eq!( - html! { span (Props::default()) {} }.into_string(), - "" - ); -} - -#[pagetop::test] -async fn props_new_creates_first_attr() { - let p = Props::new("hx-get", "/api"); - assert_eq!(p.get_prop("hx-get"), Some("/api".to_string())); -} - -#[pagetop::test] -async fn props_get_missing_key_returns_none() { - let p = Props::new("hx-get", "/api"); - assert_eq!(p.get_prop("hx-post"), None); - assert_eq!(p.get_prop(""), None); -} - -// **< Props::classes >***************************************************************************** - -#[pagetop::test] -async fn props_classes_renders_class_attribute() { - let p = Props::classes("btn btn-primary"); - assert_eq!( - html! { button (p) { "OK" } }.into_string(), - r#""# - ); -} - -#[pagetop::test] -async fn props_classes_empty_input_renders_no_class_attribute() { - let p = Props::classes(" "); - assert_eq!( - html! { button (p) { "OK" } }.into_string(), - "" - ); -} - -#[pagetop::test] -async fn props_classes_can_be_extended_with_with_prop() { - let p = Props::classes("btn").with_prop(PropsOp::add_classes("active")); - assert_eq!( - html! { button (p) { "OK" } }.into_string(), - r#""# - ); -} - -// **< PropsOp::set >******************************************************************************* - -#[pagetop::test] -async fn props_set_adds_new_attrs() { - let p = Props::default() - .with_prop(PropsOp::set("hx-get", "/api")) - .with_prop(PropsOp::set("hx-swap", "outerHTML")); - assert_eq!(p.get_prop("hx-get"), Some("/api".to_string())); - assert_eq!(p.get_prop("hx-swap"), Some("outerHTML".to_string())); -} - -#[pagetop::test] -async fn props_set_replaces_existing_value() { - let p = Props::new("hx-get", "/old").with_prop(PropsOp::set("hx-get", "/new")); - assert_eq!(p.get_prop("hx-get"), Some("/new".to_string())); -} - -#[pagetop::test] -async fn props_set_does_not_create_duplicate_key() { - // Reasignar la misma clave debe reemplazar el valor, no añadir una entrada duplicada. - let p = Props::new("key", "v1").with_prop(PropsOp::set("key", "v2")); - assert_eq!( - html! { span (p) {} }.into_string(), - r#""# - ); -} - -#[pagetop::test] -async fn props_set_preserves_insertion_order() { - let p = Props::new("a", "1") - .with_prop(PropsOp::set("b", "2")) - .with_prop(PropsOp::set("c", "3")); - assert_eq!( - html! { span (p) {} }.into_string(), - r#""# - ); -} - -// **< PropsOp::remove >**************************************************************************** - -#[pagetop::test] -async fn props_remove_existing_attr() { - let p = Props::new("a", "1") - .with_prop(PropsOp::set("b", "2")) - .with_prop(PropsOp::remove("a")); - assert_eq!(p.get_prop("a"), None); - assert_eq!(p.get_prop("b"), Some("2".to_string())); -} - -#[pagetop::test] -async fn props_remove_nonexistent_key_is_noop() { - let p = Props::new("a", "1").with_prop(PropsOp::remove("missing")); - assert_eq!(p.get_prop("a"), Some("1".to_string())); - assert_eq!(p.get_prop("missing"), None); -} - -#[pagetop::test] -async fn props_renders_nothing_after_removing_last_attr() { - let p = Props::new("only", "one").with_prop(PropsOp::remove("only")); - assert_eq!(html! { span (p) {} }.into_string(), ""); -} - -// **< HTML Escaped >******************************************************************************* - -#[pagetop::test] -async fn props_escapes_ampersand_and_angle_brackets_in_value() { - let p = Props::new("data-info", "a&bd"); - assert_eq!( - html! { span (p) {} }.into_string(), - r#""# - ); -} - -#[pagetop::test] -async fn props_escapes_double_quotes_in_value() { - let p = Props::new("data-label", r#"say "hello""#); - assert_eq!( - html! { span (p) {} }.into_string(), - r#""# - ); -} - -// **< Integration with html! >********************************************************************* - -#[pagetop::test] -async fn props_empty_in_html_macro_produces_no_attributes() { - // Una Props vacía no debe emitir ni siquiera un espacio en blanco extra. - let p = Props::default(); - assert_eq!( - html! { button (p) { "x" } }.into_string(), - "" - ); -} - -#[pagetop::test] -async fn props_single_attr_in_html_macro() { - let p = Props::new("hx-get", "/api"); - assert_eq!( - html! { button (p) { "Load" } }.into_string(), - r#""# - ); -} - -#[pagetop::test] -async fn props_multiple_attrs_preserve_order_in_html_macro() { - let p = Props::new("hx-get", "/api") - .with_prop(PropsOp::set("hx-target", "#result")) - .with_prop(PropsOp::set("hx-swap", "outerHTML")); - assert_eq!( - html! { button (p) {} }.into_string(), - r##""## - ); -} - -#[pagetop::test] -async fn props_alongside_class_and_id_in_html_macro() { - // El splice siempre se emite después de class e id, independientemente del orden escrito. - let p = Props::new("hx-get", "/api"); - assert_eq!( - html! { button #mybtn .btn (p) { "Go" } }.into_string(), - r#""# - ); -} - -#[pagetop::test] -async fn props_alongside_named_attr_renders_after_it() { - let p = Props::new("hx-get", "/api"); - assert_eq!( - html! { button type="button" (p) {} }.into_string(), - r#""# - ); -} - -#[pagetop::test] -async fn props_multiple_splices_in_same_element() { - let p1 = Props::new("hx-get", "/api"); - let p2 = Props::new("hx-swap", "outerHTML"); - assert_eq!( - html! { button (p1) (p2) {} }.into_string(), - r#""# - ); -} - -#[pagetop::test] -async fn props_inline_construction_in_html_macro() { - assert_eq!( - html! { button (Props::new("hx-get", "/api")) { "Go" } }.into_string(), - r#""# - ); -} - -#[pagetop::test] -async fn props_conditional_expression_in_html_macro() { - for (active, expected) in [ - (true, r#""#), - (false, ""), - ] { - let markup = html! { - button (if active { Props::new("hx-get", "/api") } else { Props::default() }) { "x" } - }; - assert_eq!(markup.into_string(), expected); - } -} - -#[pagetop::test] -async fn props_splice_empty_string_emits_nothing() { - // Un splice vacío no emite ningún atributo ni espacio extra. - assert_eq!(html! { span ("") { "x" } }.into_string(), "x"); -} - -// **< is_attrs_empty / is_classes_empty / is_empty >*********************************************** - -#[pagetop::test] -async fn props_is_attrs_empty_on_default() { - assert!(Props::default().is_attrs_empty()); -} - -#[pagetop::test] -async fn props_is_attrs_empty_false_after_set() { - assert!(!Props::new("hx-get", "/api").is_attrs_empty()); -} - -#[pagetop::test] -async fn props_is_attrs_empty_true_after_removing_last_attr() { - let p = Props::new("only", "one").with_prop(PropsOp::remove("only")); - assert!(p.is_attrs_empty()); -} - -#[pagetop::test] -async fn props_is_empty_on_default() { - assert!(Props::default().is_empty()); -} - -#[pagetop::test] -async fn props_is_empty_false_with_id() { - assert!(!Props::default().with_id("main").is_empty()); -} - -#[pagetop::test] -async fn props_is_empty_false_with_attr() { - assert!(!Props::new("hx-get", "/api").is_empty()); -} - -#[pagetop::test] -async fn props_is_empty_false_with_class() { - assert!(!Props::classes("btn").is_empty()); -} - -#[pagetop::test] -async fn props_is_classes_empty_on_default() { - assert!(Props::default().is_classes_empty()); -} - -#[pagetop::test] -async fn props_is_classes_empty_false_after_add_classes() { - assert!(!Props::classes("btn").is_classes_empty()); -} - -#[pagetop::test] -async fn props_is_classes_empty_true_after_remove_class() { - let p = Props::classes("btn").with_prop(PropsOp::remove("class")); - assert!(p.is_classes_empty()); -} - -// **< get_prop("id") / get_prop("class") >********************************************************* - -#[pagetop::test] -async fn get_prop_id_returns_none_by_default() { - assert_eq!(Props::default().get_prop("id"), None); -} - -#[pagetop::test] -async fn get_prop_id_returns_normalized_value() { - let p = Props::default().with_id("My Button"); - assert_eq!(p.get_prop("id"), Some("my_button".to_string())); -} - -#[pagetop::test] -async fn get_prop_id_matches_get_id() { - let p = Props::default().with_id("Header"); - assert_eq!(p.get_prop("id"), p.get_id()); -} - -#[pagetop::test] -async fn get_prop_class_returns_none_by_default() { - assert_eq!(Props::default().get_prop("class"), None); -} - -#[pagetop::test] -async fn get_prop_class_returns_joined_classes() { - let p = Props::classes("btn btn-primary").with_prop(PropsOp::add_classes("active")); - assert_eq!( - p.get_prop("class"), - Some("btn btn-primary active".to_string()) - ); -} - -#[pagetop::test] -async fn get_prop_class_matches_get_classes() { - let p = Props::classes("btn active"); - assert_eq!(p.get_prop("class"), p.get_classes()); -} - -// **< Regression & edge cases >******************************************************************** - -#[pagetop::test] -async fn props_hx_target_value_with_hash_renders_correctly() { - // Regresión: r#"..."# se cerraba prematuramente al encontrar `"#lista"`. - let p = Props::new("hx-target", "#list"); - assert_eq!( - html! { button (p) {} }.into_string(), - r##""## - ); -} - -#[pagetop::test] -async fn props_with_empty_value_renders_attr_with_empty_value() { - let p = Props::new("data-expanded", ""); - assert_eq!( - html! { span (p) {} }.into_string(), - r#""# - ); -} - -#[pagetop::test] -async fn props_chained_set_and_remove_yields_expected_state() { - let p = Props::new("a", "1") - .with_prop(PropsOp::set("b", "2")) - .with_prop(PropsOp::set("c", "3")) - .with_prop(PropsOp::remove("b")) - .with_prop(PropsOp::set("a", "updated")); - assert_eq!(p.get_prop("a"), Some("updated".to_string())); - assert_eq!(p.get_prop("b"), None); - assert_eq!(p.get_prop("c"), Some("3".to_string())); - assert_eq!( - html! { span (p) {} }.into_string(), - r#""# - ); -} - -#[pagetop::test] -async fn props_with_empty_attr_name_renders_without_validation() { - // Comportamiento documentado: los nombres no se validan; el HTML resultante no es estándar. - let p = Props::new("", "val"); - assert_eq!( - html! { span (p) {} }.into_string(), - r#""# - ); -} diff --git a/tools/changelog.sh b/tools/changelog.sh index 81c85747..fc2a8a0c 100755 --- a/tools/changelog.sh +++ b/tools/changelog.sh @@ -65,7 +65,6 @@ case "$CRATE" in # Extensions --exclude-path "extensions/pagetop-aliner/**/*" --exclude-path "extensions/pagetop-bootsier/**/*" - --exclude-path "extensions/pagetop-htmx/**/*" --exclude-path "extensions/pagetop-seaorm/**/*" ) ;; @@ -77,10 +76,6 @@ case "$CRATE" in CHANGELOG_FILE="extensions/pagetop-bootsier/CHANGELOG.md" PATH_FLAGS=(--include-path "extensions/pagetop-bootsier/**/*") ;; - pagetop-htmx) - CHANGELOG_FILE="extensions/pagetop-htmx/CHANGELOG.md" - PATH_FLAGS=(--include-path "extensions/pagetop-htmx/**/*") - ;; pagetop-seaorm) CHANGELOG_FILE="extensions/pagetop-seaorm/CHANGELOG.md" PATH_FLAGS=(--include-path "extensions/pagetop-seaorm/**/*")