diff --git a/pagetop-admin/src/lib.rs b/pagetop-admin/src/lib.rs index 15ed9dbc..a7eb7bec 100644 --- a/pagetop-admin/src/lib.rs +++ b/pagetop-admin/src/lib.rs @@ -29,7 +29,7 @@ impl ModuleTrait for Admin { } fn actions(&self) -> Vec { - actions![ActionBeforePrepareBody::with(before_prepare_body)] + actions![action::page::BeforePrepareBody::with(before_prepare_body)] } fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { diff --git a/pagetop-bootsier/src/lib.rs b/pagetop-bootsier/src/lib.rs index 0db93999..239a0846 100644 --- a/pagetop-bootsier/src/lib.rs +++ b/pagetop-bootsier/src/lib.rs @@ -43,21 +43,6 @@ impl ThemeTrait for Bootsier { ] } - fn before_prepare_body(&self, page: &mut Page) { - page.alter_favicon(Some(Favicon::new().with_icon("/theme/favicon.ico"))) - .alter_context(ContextOp::AddStyleSheet( - StyleSheet::at("/bootsier/css/bootstrap.min.css") - .with_version("5.1.3") - .with_weight(-99), - )) - .alter_context(ContextOp::AddJavaScript( - JavaScript::at("/bootsier/js/bootstrap.bundle.min.js") - .with_version("5.1.3") - .with_weight(-99), - )); - JQuery.enable_jquery(page.context()); - } - fn prepare_body(&self, page: &mut Page) -> Markup { match page.template() { "admin" => html! { @@ -118,6 +103,33 @@ impl ThemeTrait for Bootsier { } } + fn after_prepare_body(&self, page: &mut Page) { + page.alter_favicon(Some(Favicon::new().with_icon("/theme/favicon.ico"))) + .alter_context(ContextOp::AddStyleSheet( + StyleSheet::at("/bootsier/css/bootstrap.min.css") + .with_version("5.1.3") + .with_weight(-99), + )) + .alter_context(ContextOp::AddJavaScript( + JavaScript::at("/bootsier/js/bootstrap.bundle.min.js") + .with_version("5.1.3") + .with_weight(-99), + )); + + if let Some(true) = page.context().get_param::(PARAM_INCLUDE_FLEX) { + page.alter_context(ContextOp::AddStyleSheet( + StyleSheet::at("/theme/css/flex.css").with_version("0.0.0"), + )); + } + if let Some(true) = page.context().get_param::(PARAM_INCLUDE_ICONS) { + page.alter_context(ContextOp::AddStyleSheet( + StyleSheet::at("/theme/icons/bootstrap-icons.css").with_version("1.8.2"), + )); + } + + JQuery.enable_jquery(page.context()); + } + fn render_component(&self, component: &dyn ComponentTrait, cx: &mut Context) -> Option { match component.handle() { ERROR_404 => Some(html! { diff --git a/pagetop-bulmix/src/lib.rs b/pagetop-bulmix/src/lib.rs index c6eb5e37..c8e1dc74 100644 --- a/pagetop-bulmix/src/lib.rs +++ b/pagetop-bulmix/src/lib.rs @@ -29,13 +29,25 @@ impl ModuleTrait for Bulmix { } impl ThemeTrait for Bulmix { - fn before_prepare_body(&self, page: &mut Page) { + fn after_prepare_body(&self, page: &mut Page) { page.alter_favicon(Some(Favicon::new().with_icon("/theme/favicon.ico"))) .alter_context(ContextOp::AddStyleSheet( StyleSheet::at("/bulmix/css/bulma.min.css") .with_version("0.9.4") .with_weight(-99), )); + + if let Some(true) = page.context().get_param::(PARAM_INCLUDE_FLEX) { + page.alter_context(ContextOp::AddStyleSheet( + StyleSheet::at("/theme/css/flex.css").with_version("0.0.0"), + )); + } + if let Some(true) = page.context().get_param::(PARAM_INCLUDE_ICONS) { + page.alter_context(ContextOp::AddStyleSheet( + StyleSheet::at("/theme/icons/bootstrap-icons.css").with_version("1.8.2"), + )); + } + JQuery.enable_jquery(page.context()); } diff --git a/pagetop-homedemo/src/lib.rs b/pagetop-homedemo/src/lib.rs index 48830dce..901776a3 100644 --- a/pagetop-homedemo/src/lib.rs +++ b/pagetop-homedemo/src/lib.rs @@ -42,8 +42,8 @@ async fn demo(request: service::HttpRequest) -> ResultPage { .render() } -fn hello_world() -> Container { - Container::header().with_id("hello-world").with_component( +fn hello_world() -> Wrapper { + Wrapper::header().with_id("hello-world").with_component( flex::Container::new() .with_direction(flex::Direction::Column(BreakPoint::MD)) .with_item( @@ -97,8 +97,8 @@ fn hello_world() -> Container { ) } -fn welcome() -> Container { - Container::section() +fn welcome() -> Wrapper { + Wrapper::section() .with_id("welcome") .with_classes(ClassesOp::Add, "welcome-col-text") .with_component(Heading::h2(L10n::t("welcome_page", &LOCALES_HOMEDEMO))) @@ -119,8 +119,8 @@ fn welcome() -> Container { .with_component(Paragraph::with(L10n::t("welcome_text2", &LOCALES_HOMEDEMO))) } -fn about_pagetop() -> Container { - Container::new().with_id("pagetop").with_component( +fn about_pagetop() -> Wrapper { + Wrapper::new().with_id("pagetop").with_component( flex::Container::new() .with_direction(flex::Direction::Column(BreakPoint::SM)) .with_item( @@ -146,8 +146,8 @@ fn about_pagetop() -> Container { ) } -fn promo_pagetop() -> Container { - Container::new().with_id("promo").with_component( +fn promo_pagetop() -> Wrapper { + Wrapper::new().with_id("promo").with_component( flex::Container::new() .with_direction(flex::Direction::Column(BreakPoint::MD)) .with_item( @@ -180,8 +180,8 @@ fn promo_pagetop() -> Container { ) } -fn reporting_issues() -> Container { - Container::new().with_id("reporting").with_component( +fn reporting_issues() -> Wrapper { + Wrapper::new().with_id("reporting").with_component( flex::Container::new() .with_direction(flex::Direction::Column(BreakPoint::MD)) .with_item( diff --git a/pagetop-jquery/src/lib.rs b/pagetop-jquery/src/lib.rs index ee531e7b..7e63c5f9 100644 --- a/pagetop-jquery/src/lib.rs +++ b/pagetop-jquery/src/lib.rs @@ -29,7 +29,7 @@ impl ModuleTrait for JQuery { } fn actions(&self) -> Vec { - actions![ActionAfterPrepareBody::with(after_prepare_body)] + actions![action::page::AfterPrepareBody::with(after_prepare_body)] } fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { diff --git a/pagetop-node/src/lib.rs b/pagetop-node/src/lib.rs index 96d1c813..3294797b 100644 --- a/pagetop-node/src/lib.rs +++ b/pagetop-node/src/lib.rs @@ -27,7 +27,7 @@ impl ModuleTrait for Node { } fn actions(&self) -> Vec { - actions![ActionBeforePrepareBody::with(before_prepare_body).with_weight(-1)] + actions![action::page::BeforePrepareBody::with(before_prepare_body).with_weight(-1)] } fn migrations(&self) -> Vec { diff --git a/pagetop-user/src/lib.rs b/pagetop-user/src/lib.rs index fae89481..054b24c5 100644 --- a/pagetop-user/src/lib.rs +++ b/pagetop-user/src/lib.rs @@ -40,7 +40,7 @@ async fn login(request: service::HttpRequest) -> ResultPage .with_title(L10n::n("Identificación del usuario")) .with_in( "content", - Container::new() + Wrapper::new() .with_id("welcome") .with_component(form_login()), ) diff --git a/pagetop/src/base.rs b/pagetop/src/base.rs new file mode 100644 index 00000000..acfd1dbe --- /dev/null +++ b/pagetop/src/base.rs @@ -0,0 +1,5 @@ +pub mod action; + +pub mod component; + +pub mod theme; diff --git a/pagetop/src/base/action.rs b/pagetop/src/base/action.rs new file mode 100644 index 00000000..62a06db9 --- /dev/null +++ b/pagetop/src/base/action.rs @@ -0,0 +1,3 @@ +pub mod component; + +pub mod page; diff --git a/pagetop/src/base/action/component.rs b/pagetop/src/base/action/component.rs new file mode 100644 index 00000000..5cf72aa6 --- /dev/null +++ b/pagetop/src/base/action/component.rs @@ -0,0 +1,133 @@ +#[macro_export] +macro_rules! actions_for_component { + ( $Component:ty ) => { + $crate::paste! { + #[allow(unused_imports)] + use $crate::prelude::*; + + pub type [] = fn(component: &$Component, cx: &mut Context); + + // ************************************************************************************* + // ACTION BEFORE PREPARE COMPONENT + // ************************************************************************************* + + $crate::new_handle!([] for Crate); + + pub struct [] { + action: Option<[]>, + weight: Weight, + } + + impl ActionTrait for [] { + fn new() -> Self { + [] { + action: None, + weight: 0, + } + } + + fn handle(&self) -> Handle { + [] + } + + fn weight(&self) -> Weight { + self.weight + } + } + + impl [] { + #[allow(dead_code)] + pub fn with(action: []) -> Self { + [] { + action: Some(action), + weight: 0, + } + } + + #[allow(dead_code)] + pub fn with_weight(mut self, value: Weight) -> Self { + self.weight = value; + self + } + + pub(crate) fn run(&self, component: &mut $Component, cx: &mut Context) { + if let Some(action) = self.action { + action(component, cx) + } + } + } + + #[inline(always)] + pub(crate) fn []( + component: &mut $Component, + cx: &mut Context + ) { + run_actions([], |action| + action_ref::<[]>(&**action) + .run(component, cx) + ); + } + + // ************************************************************************************* + // ACTION AFTER PREPARE COMPONENT + // ************************************************************************************* + + $crate::new_handle!([] for Crate); + + pub struct [] { + action: Option<[]>, + weight: Weight, + } + + impl ActionTrait for [] { + fn new() -> Self { + [] { + action: None, + weight: 0, + } + } + + fn handle(&self) -> Handle { + [] + } + + fn weight(&self) -> Weight { + self.weight + } + } + + impl [] { + #[allow(dead_code)] + pub fn with(action: []) -> Self { + [] { + action: Some(action), + weight: 0, + } + } + + #[allow(dead_code)] + pub fn with_weight(mut self, value: Weight) -> Self { + self.weight = value; + self + } + + pub(crate) fn run(&self, component: &mut $Component, cx: &mut Context) { + if let Some(action) = self.action { + action(component, cx) + } + } + } + + #[inline(always)] + pub(crate) fn []( + component: &mut $Component, + cx: &mut Context + ) { + run_actions([], |action| + action_ref::<[]>(&**action) + .run(component, cx) + ); + } + } + }; +} diff --git a/pagetop/src/response/page/action.rs b/pagetop/src/base/action/page.rs similarity index 82% rename from pagetop/src/response/page/action.rs rename to pagetop/src/base/action/page.rs index d3a81011..8e6390cb 100644 --- a/pagetop/src/response/page/action.rs +++ b/pagetop/src/base/action/page.rs @@ -1,4 +1,4 @@ -use crate::response::page::Page; +use crate::prelude::*; pub type FnActionPage = fn(page: &mut Page); diff --git a/pagetop/src/response/page/action/after_prepare_body.rs b/pagetop/src/base/action/page/after_prepare_body.rs similarity index 66% rename from pagetop/src/response/page/action/after_prepare_body.rs rename to pagetop/src/base/action/page/after_prepare_body.rs index 7b1ef2dd..0e2ced8d 100644 --- a/pagetop/src/response/page/action/after_prepare_body.rs +++ b/pagetop/src/base/action/page/after_prepare_body.rs @@ -1,18 +1,17 @@ -use crate::core::action::{action_ref, run_actions, ActionTrait}; -use crate::response::page::action::FnActionPage; -use crate::response::page::Page; -use crate::{new_handle, Handle, Weight}; +use crate::prelude::*; + +use super::FnActionPage; new_handle!(ACTION_AFTER_PREPARE_BODY for Crate); -pub struct ActionAfterPrepareBody { +pub struct AfterPrepareBody { action: Option, weight: Weight, } -impl ActionTrait for ActionAfterPrepareBody { +impl ActionTrait for AfterPrepareBody { fn new() -> Self { - ActionAfterPrepareBody { + AfterPrepareBody { action: None, weight: 0, } @@ -27,9 +26,9 @@ impl ActionTrait for ActionAfterPrepareBody { } } -impl ActionAfterPrepareBody { +impl AfterPrepareBody { pub fn with(action: FnActionPage) -> Self { - ActionAfterPrepareBody { + AfterPrepareBody { action: Some(action), weight: 0, } @@ -50,6 +49,6 @@ impl ActionAfterPrepareBody { #[inline(always)] pub(crate) fn run_actions_after_prepare_body(page: &mut Page) { run_actions(ACTION_AFTER_PREPARE_BODY, |action| { - action_ref::(&**action).run(page) + action_ref::(&**action).run(page) }); } diff --git a/pagetop/src/response/page/action/before_prepare_body.rs b/pagetop/src/base/action/page/before_prepare_body.rs similarity index 66% rename from pagetop/src/response/page/action/before_prepare_body.rs rename to pagetop/src/base/action/page/before_prepare_body.rs index 3aa7cefe..3198e6f3 100644 --- a/pagetop/src/response/page/action/before_prepare_body.rs +++ b/pagetop/src/base/action/page/before_prepare_body.rs @@ -1,18 +1,17 @@ -use crate::core::action::{action_ref, run_actions, ActionTrait}; -use crate::response::page::action::FnActionPage; -use crate::response::page::Page; -use crate::{new_handle, Handle, Weight}; +use crate::prelude::*; + +use super::FnActionPage; new_handle!(ACTION_BEFORE_PREPARE_BODY for Crate); -pub struct ActionBeforePrepareBody { +pub struct BeforePrepareBody { action: Option, weight: Weight, } -impl ActionTrait for ActionBeforePrepareBody { +impl ActionTrait for BeforePrepareBody { fn new() -> Self { - ActionBeforePrepareBody { + BeforePrepareBody { action: None, weight: 0, } @@ -27,9 +26,9 @@ impl ActionTrait for ActionBeforePrepareBody { } } -impl ActionBeforePrepareBody { +impl BeforePrepareBody { pub fn with(action: FnActionPage) -> Self { - ActionBeforePrepareBody { + BeforePrepareBody { action: Some(action), weight: 0, } @@ -50,6 +49,6 @@ impl ActionBeforePrepareBody { #[inline(always)] pub(crate) fn run_actions_before_prepare_body(page: &mut Page) { run_actions(ACTION_BEFORE_PREPARE_BODY, |action| { - action_ref::(&**action).run(page) + action_ref::(&**action).run(page) }); } diff --git a/pagetop/src/base/component.rs b/pagetop/src/base/component.rs new file mode 100644 index 00000000..09d94026 --- /dev/null +++ b/pagetop/src/base/component.rs @@ -0,0 +1,62 @@ +// Context parameters. +pub const PARAM_INCLUDE_FLEX: &str = "theme.include.flex"; +pub const PARAM_INCLUDE_ICONS: &str = "theme.include.icons"; + +#[rustfmt::skip] +#[derive(Default)] +pub enum BreakPoint { + #[default] + None, /* Does not apply */ + SM, /* @media screen and (max-width: 35.5em) - Applies <= 568px */ + MD, /* @media screen and (max-width: 48em) - Applies <= 768px */ + LG, /* @media screen and (max-width: 64em) - Applies <= 1024px */ + XL, /* @media screen and (max-width: 80em) - Applies <= 1280px */ + X2L, /* @media screen and (max-width: 120em) - Applies <= 1920px */ + X3L, /* @media screen and (max-width: 160em) - Applies <= 2560px */ +} + +#[rustfmt::skip] +impl ToString for BreakPoint { + fn to_string(&self) -> String { + match self { + BreakPoint::None => "bp-no".to_string(), + BreakPoint::SM => "bp-sm".to_string(), + BreakPoint::MD => "bp-md".to_string(), + BreakPoint::LG => "bp-lg".to_string(), + BreakPoint::XL => "bp-xl".to_string(), + BreakPoint::X2L => "bp-x2l".to_string(), + BreakPoint::X3L => "bp-x3l".to_string(), + } + } +} + +mod html; +pub use html::{Html, COMPONENT_HTML}; + +mod l10n; +pub use l10n::{L10n, COMPONENT_L10N}; + +mod wrapper; +pub use wrapper::{Wrapper, WrapperType, COMPONENT_WRAPPER}; + +pub mod flex; + +mod icon; +pub use icon::{Icon, COMPONENT_ICON}; +mod heading; +pub use heading::{Heading, HeadingDisplay, HeadingType, COMPONENT_HEADING}; +mod paragraph; +pub use paragraph::{Paragraph, ParagraphDisplay, COMPONENT_PARAGRAPH}; +mod anchor; +pub use anchor::{Anchor, AnchorTarget, AnchorType, COMPONENT_ANCHOR}; +mod image; +pub use image::{Image, ImageSize, COMPONENT_IMAGE}; +mod block; +pub use block::{Block, COMPONENT_BLOCK}; +mod site_branding; +pub use site_branding::{SiteBranding, COMPONENT_BRANDING}; +mod powered_by; +pub use powered_by::{PoweredBy, PoweredByLogo, COMPONENT_POWEREDBY}; + +pub mod form_element; +pub use form_element::{Form, FormMethod, COMPONENT_FORM}; diff --git a/pagetop/src/base/component/anchor.rs b/pagetop/src/base/component/anchor.rs new file mode 100644 index 00000000..4f021ce2 --- /dev/null +++ b/pagetop/src/base/component/anchor.rs @@ -0,0 +1,211 @@ +use crate::prelude::*; + +new_handle!(COMPONENT_ANCHOR); + +#[derive(Default)] +pub enum AnchorType { + #[default] + Link, + Button, + Location, +} + +#[derive(Default)] +pub enum AnchorTarget { + #[default] + Default, + Blank, + Parent, + Top, + Context(String), +} + +type AnchorIcon = TypedComponent; +type AnchorHtml = TypedComponent; + +#[rustfmt::skip] +#[derive(Default)] +pub struct Anchor { + weight : Weight, + renderable : Renderable, + id : IdentifierValue, + classes : Classes, + anchor_type: AnchorType, + href : AttributeValue, + html : AnchorHtml, + left_icon : AnchorIcon, + right_icon : AnchorIcon, + target : AnchorTarget, + template : String, +} + +impl ComponentTrait for Anchor { + fn new() -> Self { + Anchor::default() + } + + fn handle(&self) -> Handle { + COMPONENT_ANCHOR + } + + fn id(&self) -> Option { + self.id.get() + } + + fn weight(&self) -> Weight { + self.weight + } + + fn is_renderable(&self, cx: &Context) -> bool { + (self.renderable.check)(cx) + } + + #[rustfmt::skip] + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let target = match &self.target() { + AnchorTarget::Blank => Some("_blank"), + AnchorTarget::Parent => Some("_parent"), + AnchorTarget::Top => Some("_top"), + AnchorTarget::Context(name) => Some(name.as_str()), + _ => None, + }; + PrepareMarkup::With(html! { + a + id=[self.id()] + class=[self.classes().get()] + href=[self.href().get()] + target=[target] + { + (self.left_icon().prepare(cx)) + " " span { (self.html().prepare(cx)) } " " + (self.right_icon().prepare(cx)) + } + }) + } +} + +impl Anchor { + pub fn link(href: &str, html: L10n) -> Self { + Anchor::new().with_href(href).with_html(html) + } + + pub fn button(href: &str, html: L10n) -> Self { + Anchor::new() + .with_type(AnchorType::Button) + .with_href(href) + .with_html(html) + } + + pub fn location(id: &str) -> Self { + Anchor::new().with_type(AnchorType::Location).with_id(id) + } + + // Anchor BUILDER. + + #[fn_builder] + pub fn alter_weight(&mut self, value: Weight) -> &mut Self { + self.weight = value; + self + } + + #[fn_builder] + pub fn alter_renderable(&mut self, check: FnIsRenderable) -> &mut Self { + self.renderable.check = check; + self + } + + #[fn_builder] + pub fn alter_id(&mut self, id: &str) -> &mut Self { + self.id.alter_value(id); + self + } + + #[fn_builder] + pub fn alter_classes(&mut self, op: ClassesOp, classes: impl Into) -> &mut Self { + self.classes.alter_value(op, classes); + self + } + + #[fn_builder] + pub fn alter_type(&mut self, anchor_type: AnchorType) -> &mut Self { + self.alter_classes( + ClassesOp::SetDefault, + match anchor_type { + AnchorType::Button => "btn btn-primary", + _ => "", + }, + ); + self.anchor_type = anchor_type; + self + } + + #[fn_builder] + pub fn alter_href(&mut self, href: &str) -> &mut Self { + self.href.alter_value(href); + self + } + + #[fn_builder] + pub fn alter_html(&mut self, html: L10n) -> &mut Self { + self.html.set(html); + self + } + + #[fn_builder] + pub fn alter_left_icon(&mut self, icon: Icon) -> &mut Self { + self.left_icon.set(icon); + self + } + + #[fn_builder] + pub fn alter_right_icon(&mut self, icon: Icon) -> &mut Self { + self.right_icon.set(icon); + self + } + + #[fn_builder] + pub fn alter_target(&mut self, target: AnchorTarget) -> &mut Self { + self.target = target; + self + } + + #[fn_builder] + pub fn alter_template(&mut self, template: &str) -> &mut Self { + self.template = template.to_owned(); + self + } + + // Anchor GETTERS. + + pub fn classes(&self) -> &Classes { + &self.classes + } + + pub fn anchor_type(&self) -> &AnchorType { + &self.anchor_type + } + + pub fn href(&self) -> &AttributeValue { + &self.href + } + + pub fn html(&self) -> &AnchorHtml { + &self.html + } + + pub fn left_icon(&self) -> &AnchorIcon { + &self.left_icon + } + + pub fn right_icon(&self) -> &AnchorIcon { + &self.right_icon + } + + pub fn target(&self) -> &AnchorTarget { + &self.target + } + + pub fn template(&self) -> &str { + self.template.as_str() + } +} diff --git a/pagetop/src/base/component/block.rs b/pagetop/src/base/component/block.rs new file mode 100644 index 00000000..f83ff261 --- /dev/null +++ b/pagetop/src/base/component/block.rs @@ -0,0 +1,130 @@ +use crate::prelude::*; + +new_handle!(COMPONENT_BLOCK); + +actions_for_component!(Block); + +#[rustfmt::skip] +#[derive(Default)] +pub struct Block { + weight : Weight, + renderable: Renderable, + id : IdentifierValue, + classes : Classes, + title : AttributeValue, + stuff : ArcComponents, + template : String, +} + +impl ComponentTrait for Block { + fn new() -> Self { + Block::default().with_classes(ClassesOp::SetDefault, "block") + } + + fn handle(&self) -> Handle { + COMPONENT_BLOCK + } + + fn id(&self) -> Option { + self.id.get() + } + + fn weight(&self) -> Weight { + self.weight + } + + fn is_renderable(&self, cx: &Context) -> bool { + (self.renderable.check)(cx) + } + + fn before_prepare_component(&mut self, cx: &mut Context) { + run_actions_before_prepare_block(self, cx); + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let id = cx.required_id::(self.id()); + PrepareMarkup::With(html! { + div id=(id) class=[self.classes().get()] { + @if let Some(title) = self.title().get() { + h2 class="block-title" { (title) } + } + div class="block-body" { + (self.components().prepare(cx)) + } + } + }) + } + + fn after_prepare_component(&mut self, cx: &mut Context) { + run_actions_after_prepare_block(self, cx); + } +} + +impl Block { + // Block BUILDER. + + #[fn_builder] + pub fn alter_id(&mut self, id: &str) -> &mut Self { + self.id.alter_value(id); + self + } + + #[fn_builder] + pub fn alter_weight(&mut self, value: Weight) -> &mut Self { + self.weight = value; + self + } + + #[fn_builder] + pub fn alter_renderable(&mut self, check: FnIsRenderable) -> &mut Self { + self.renderable.check = check; + self + } + + #[fn_builder] + pub fn alter_classes(&mut self, op: ClassesOp, classes: impl Into) -> &mut Self { + self.classes.alter_value(op, classes); + self + } + + #[fn_builder] + pub fn alter_title(&mut self, title: &str) -> &mut Self { + self.title.alter_value(title); + self + } + + pub fn with_component(mut self, component: impl ComponentTrait) -> Self { + self.stuff.alter(ArcOp::Add(ArcComponent::with(component))); + self + } + + #[fn_builder] + pub fn alter_components(&mut self, op: ArcOp) -> &mut Self { + self.stuff.alter(op); + self + } + + #[fn_builder] + pub fn alter_template(&mut self, template: &str) -> &mut Self { + self.template = template.to_owned(); + self + } + + // Block GETTERS. + + pub fn classes(&self) -> &Classes { + &self.classes + } + + pub fn title(&self) -> &AttributeValue { + &self.title + } + + pub fn components(&self) -> &ArcComponents { + &self.stuff + } + + pub fn template(&self) -> &str { + self.template.as_str() + } +} diff --git a/pagetop/src/base/component/flex.rs b/pagetop/src/base/component/flex.rs new file mode 100644 index 00000000..7e30db94 --- /dev/null +++ b/pagetop/src/base/component/flex.rs @@ -0,0 +1,322 @@ +mod container; +pub use container::{Container, COMPONENT_FLEX_CONTAINER}; +mod item; +pub use item::{Item, COMPONENT_FLEX_ITEM}; + +use crate::prelude::*; + +// ************************************************************************************************* + +#[derive(Default)] +pub enum Direction { + #[default] + Default, + Row(BreakPoint), + RowReverse(BreakPoint), + Column(BreakPoint), + ColumnReverse(BreakPoint), +} + +#[rustfmt::skip] +impl ToString for Direction { + fn to_string(&self) -> String { + match self { + Direction::Default => { + concat_string!("flex-container flex-row ", BreakPoint::default().to_string()) + } + Direction::Row(breakpoint) => { + concat_string!("flex-container flex-row ", breakpoint.to_string()) + } + Direction::RowReverse(breakpoint) => { + concat_string!("flex-container flex-row flex-reverse ", breakpoint.to_string()) + } + Direction::Column(breakpoint) => { + concat_string!("flex-container flex-col ", breakpoint.to_string()) + } + Direction::ColumnReverse(breakpoint) => { + concat_string!("flex-container flex-col flex-reverse ", breakpoint.to_string()) + } + } + } +} + +// ************************************************************************************************* + +#[derive(Default)] +pub enum WrapAlign { + #[default] + Default, + NoWrap, + Wrap(ContentAlign), + WrapReverse(ContentAlign), +} + +#[rustfmt::skip] +impl ToString for WrapAlign { + fn to_string(&self) -> String { + match self { + WrapAlign::Default => "".to_string(), + WrapAlign::NoWrap => "flex-nowrap".to_string(), + WrapAlign::Wrap(a) => concat_string!("flex-wrap ", a.to_string()), + WrapAlign::WrapReverse(a) => concat_string!("flex-wrap-reverse ", a.to_string()), + } + } +} + +// ************************************************************************************************* + +#[derive(Default)] +pub enum ContentAlign { + #[default] + Default, + Start, + End, + Center, + Stretch, + SpaceBetween, + SpaceAround, +} + +#[rustfmt::skip] +impl ToString for ContentAlign { + fn to_string(&self) -> String { + match self { + ContentAlign::Default => "".to_string(), + ContentAlign::Start => "flex-align-start".to_string(), + ContentAlign::End => "flex-align-end".to_string(), + ContentAlign::Center => "flex-align-center".to_string(), + ContentAlign::Stretch => "flex-align-stretch".to_string(), + ContentAlign::SpaceBetween => "flex-align-space-between".to_string(), + ContentAlign::SpaceAround => "flex-align-space-around".to_string(), + } + } +} + +// ************************************************************************************************* + +#[derive(Default)] +pub enum ContentJustify { + #[default] + Default, + Start, + End, + Center, + SpaceBetween, + SpaceAround, + SpaceEvenly, +} + +#[rustfmt::skip] +impl ToString for ContentJustify { + fn to_string(&self) -> String { + match self { + ContentJustify::Default => "".to_string(), + ContentJustify::Start => "flex-justify-start".to_string(), + ContentJustify::End => "flex-justify-end".to_string(), + ContentJustify::Center => "flex-justify-center".to_string(), + ContentJustify::SpaceBetween => "flex-justify-space-between".to_string(), + ContentJustify::SpaceAround => "flex-justify-space-around".to_string(), + ContentJustify::SpaceEvenly => "flex-justify-space-evenly".to_string(), + } + } +} + +// ************************************************************************************************* + +#[derive(Default)] +pub enum ItemAlign { + #[default] + Default, + Top, + Bottom, + Middle, + Stretch, + Baseline, +} + +#[rustfmt::skip] +impl ToString for ItemAlign { + fn to_string(&self) -> String { + match self { + ItemAlign::Default => "".to_string(), + ItemAlign::Top => "flex-item-top".to_string(), + ItemAlign::Bottom => "flex-item-bottom".to_string(), + ItemAlign::Middle => "flex-item-middle".to_string(), + ItemAlign::Stretch => "flex-item-stretch".to_string(), + ItemAlign::Baseline => "flex-item-baseline".to_string(), + } + } +} + +// ************************************************************************************************* + +#[derive(Default)] +pub enum Gap { + #[default] + Default, + Row(unit::Value), + Column(unit::Value), + Distinct(unit::Value, unit::Value), + Both(unit::Value), +} + +#[rustfmt::skip] +impl ToString for Gap { + fn to_string(&self) -> String { + match self { + Gap::Default => "".to_string(), + Gap::Row(r) => concat_string!("row-gap: ", r.to_string(), ";"), + Gap::Column(c) => concat_string!("column-gap: ", c.to_string(), ";"), + Gap::Distinct(r, c) => concat_string!("gap: ", r.to_string(), " ", c.to_string(), ";"), + Gap::Both(v) => concat_string!("gap: ", v.to_string(), ";"), + } + } +} + +// ************************************************************************************************* + +#[derive(Default)] +pub enum ItemGrow { + #[default] + Default, + Is1, + Is2, + Is3, + Is4, + Is5, + Is6, + Is7, + Is8, + Is9, +} + +#[rustfmt::skip] +impl ToString for ItemGrow { + fn to_string(&self) -> String { + match self { + ItemGrow::Default => "".to_string(), + ItemGrow::Is1 => "flex-grow-1".to_string(), + ItemGrow::Is2 => "flex-grow-2".to_string(), + ItemGrow::Is3 => "flex-grow-3".to_string(), + ItemGrow::Is4 => "flex-grow-4".to_string(), + ItemGrow::Is5 => "flex-grow-5".to_string(), + ItemGrow::Is6 => "flex-grow-6".to_string(), + ItemGrow::Is7 => "flex-grow-7".to_string(), + ItemGrow::Is8 => "flex-grow-8".to_string(), + ItemGrow::Is9 => "flex-grow-9".to_string(), + } + } +} + +// ************************************************************************************************* + +#[derive(Default)] +pub enum ItemShrink { + #[default] + Default, + Is1, + Is2, + Is3, + Is4, + Is5, + Is6, + Is7, + Is8, + Is9, +} + +#[rustfmt::skip] +impl ToString for ItemShrink { + fn to_string(&self) -> String { + match self { + ItemShrink::Default => "".to_string(), + ItemShrink::Is1 => "flex-shrink-1".to_string(), + ItemShrink::Is2 => "flex-shrink-2".to_string(), + ItemShrink::Is3 => "flex-shrink-3".to_string(), + ItemShrink::Is4 => "flex-shrink-4".to_string(), + ItemShrink::Is5 => "flex-shrink-5".to_string(), + ItemShrink::Is6 => "flex-shrink-6".to_string(), + ItemShrink::Is7 => "flex-shrink-7".to_string(), + ItemShrink::Is8 => "flex-shrink-8".to_string(), + ItemShrink::Is9 => "flex-shrink-9".to_string(), + } + } +} + +// ************************************************************************************************* + +#[derive(Default)] +pub enum ItemSize { + #[default] + Default, + Percent10, + Percent20, + Percent25, + Percent33, + Percent40, + Percent50, + Percent60, + Percent66, + Percent75, + Percent80, + Percent90, +} + +#[rustfmt::skip] +impl ToString for ItemSize { + fn to_string(&self) -> String { + match self { + ItemSize::Default => "".to_string(), + ItemSize::Percent10 => "flex-width-10".to_string(), + ItemSize::Percent20 => "flex-width-20".to_string(), + ItemSize::Percent25 => "flex-width-25".to_string(), + ItemSize::Percent33 => "flex-width-33".to_string(), + ItemSize::Percent40 => "flex-width-40".to_string(), + ItemSize::Percent50 => "flex-width-50".to_string(), + ItemSize::Percent60 => "flex-width-60".to_string(), + ItemSize::Percent66 => "flex-width-66".to_string(), + ItemSize::Percent75 => "flex-width-75".to_string(), + ItemSize::Percent80 => "flex-width-80".to_string(), + ItemSize::Percent90 => "flex-width-90".to_string(), + } + } +} + +// ************************************************************************************************* + +#[derive(Default)] +pub enum ItemOffset { + #[default] + Default, + Offset10, + Offset20, + Offset25, + Offset33, + Offset40, + Offset50, + Offset60, + Offset66, + Offset75, + Offset80, + Offset90, +} + +#[rustfmt::skip] +impl ToString for ItemOffset { + fn to_string(&self) -> String { + match self { + ItemOffset::Default => "".to_string(), + ItemOffset::Offset10 => "flex-offset-10".to_string(), + ItemOffset::Offset20 => "flex-offset-20".to_string(), + ItemOffset::Offset25 => "flex-offset-25".to_string(), + ItemOffset::Offset33 => "flex-offset-33".to_string(), + ItemOffset::Offset40 => "flex-offset-40".to_string(), + ItemOffset::Offset50 => "flex-offset-50".to_string(), + ItemOffset::Offset60 => "flex-offset-60".to_string(), + ItemOffset::Offset66 => "flex-offset-66".to_string(), + ItemOffset::Offset75 => "flex-offset-75".to_string(), + ItemOffset::Offset80 => "flex-offset-80".to_string(), + ItemOffset::Offset90 => "flex-offset-90".to_string(), + } + } +} diff --git a/pagetop/src/base/component/flex/container.rs b/pagetop/src/base/component/flex/container.rs new file mode 100644 index 00000000..29708326 --- /dev/null +++ b/pagetop/src/base/component/flex/container.rs @@ -0,0 +1,181 @@ +use crate::prelude::*; + +new_handle!(COMPONENT_FLEX_CONTAINER); + +actions_for_component!(Container); + +#[rustfmt::skip] +#[derive(Default)] +pub struct Container { + weight : Weight, + renderable : Renderable, + id : IdentifierValue, + classes : Classes, + items : TypedComponents, + direction : flex::Direction, + wrap_align : flex::WrapAlign, + content_justify: flex::ContentJustify, + items_align : flex::ItemAlign, + gap : flex::Gap, +} + +impl ComponentTrait for Container { + fn new() -> Self { + Container::default() + .with_classes(ClassesOp::SetDefault, flex::Direction::Default.to_string()) + } + + fn handle(&self) -> Handle { + COMPONENT_FLEX_CONTAINER + } + + fn id(&self) -> Option { + self.id.get() + } + + fn weight(&self) -> Weight { + self.weight + } + + fn is_renderable(&self, cx: &Context) -> bool { + (self.renderable.check)(cx) + } + + fn before_prepare_component(&mut self, cx: &mut Context) { + run_actions_before_prepare_container(self, cx); + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + cx.set_param::(PARAM_INCLUDE_FLEX, true); + + let gap = match self.gap() { + flex::Gap::Default => None, + _ => Some(self.gap().to_string()), + }; + + PrepareMarkup::With(html! { + div id=[self.id()] class=[self.classes().get()] style=[gap] { + (self.items().prepare(cx)) + } + }) + } + + fn after_prepare_component(&mut self, cx: &mut Context) { + run_actions_after_prepare_container(self, cx); + } +} + +impl Container { + // Container BUILDER. + + #[fn_builder] + pub fn alter_weight(&mut self, value: Weight) -> &mut Self { + self.weight = value; + self + } + + #[fn_builder] + pub fn alter_renderable(&mut self, check: FnIsRenderable) -> &mut Self { + self.renderable.check = check; + self + } + + #[fn_builder] + pub fn alter_id(&mut self, id: &str) -> &mut Self { + self.id.alter_value(id); + self + } + + #[fn_builder] + pub fn alter_classes(&mut self, op: ClassesOp, classes: impl Into) -> &mut Self { + self.classes.alter_value(op, classes); + self + } + + pub fn with_item(mut self, item: flex::Item) -> Self { + self.items.alter(TypedOp::Add(TypedComponent::with(item))); + self + } + + #[fn_builder] + pub fn alter_items(&mut self, op: TypedOp) -> &mut Self { + self.items.alter(op); + self + } + + #[fn_builder] + pub fn alter_direction(&mut self, direction: flex::Direction) -> &mut Self { + self.classes.alter_value( + ClassesOp::Replace(self.direction.to_string()), + direction.to_string(), + ); + self.direction = direction; + self + } + + #[fn_builder] + pub fn alter_wrap_align(&mut self, wrap: flex::WrapAlign) -> &mut Self { + self.classes.alter_value( + ClassesOp::Replace(self.wrap_align.to_string()), + wrap.to_string(), + ); + self.wrap_align = wrap; + self + } + + #[fn_builder] + pub fn alter_content_justify(&mut self, justify: flex::ContentJustify) -> &mut Self { + self.classes.alter_value( + ClassesOp::Replace(self.content_justify.to_string()), + justify.to_string(), + ); + self.content_justify = justify; + self + } + + #[fn_builder] + pub fn alter_items_align(&mut self, align: flex::ItemAlign) -> &mut Self { + self.classes.alter_value( + ClassesOp::Replace(self.items_align.to_string()), + align.to_string(), + ); + self.items_align = align; + self + } + + #[fn_builder] + pub fn alter_gap(&mut self, gap: flex::Gap) -> &mut Self { + self.gap = gap; + self + } + + // Container GETTERS. + + pub fn classes(&self) -> &Classes { + &self.classes + } + + pub fn items(&self) -> &TypedComponents { + &self.items + } + + pub fn direction(&self) -> &flex::Direction { + &self.direction + } + + pub fn wrap_align(&self) -> &flex::WrapAlign { + &self.wrap_align + } + + pub fn content_justify(&self) -> &flex::ContentJustify { + &self.content_justify + } + + pub fn items_align(&self) -> &flex::ItemAlign { + &self.items_align + } + + pub fn gap(&self) -> &flex::Gap { + &self.gap + } +} diff --git a/pagetop/src/base/component/flex/item.rs b/pagetop/src/base/component/flex/item.rs new file mode 100644 index 00000000..ea99be0d --- /dev/null +++ b/pagetop/src/base/component/flex/item.rs @@ -0,0 +1,196 @@ +use crate::prelude::*; + +new_handle!(COMPONENT_FLEX_ITEM); + +actions_for_component!(Item); + +#[rustfmt::skip] +#[derive(Default)] +pub struct Item { + weight : Weight, + renderable : Renderable, + id : IdentifierValue, + item_classes : Classes, + inner_classes: Classes, + item_grow : flex::ItemGrow, + item_shrink : flex::ItemShrink, + item_size : flex::ItemSize, + item_offset : flex::ItemOffset, + item_align : flex::ItemAlign, + stuff : ArcComponents, +} + +impl ComponentTrait for Item { + fn new() -> Self { + Item::default() + .with_item_classes(ClassesOp::SetDefault, "flex-item") + .with_inner_classes(ClassesOp::SetDefault, "flex-item-inner") + } + + fn handle(&self) -> Handle { + COMPONENT_FLEX_ITEM + } + + fn id(&self) -> Option { + self.id.get() + } + + fn weight(&self) -> Weight { + self.weight + } + + fn is_renderable(&self, cx: &Context) -> bool { + (self.renderable.check)(cx) + } + + fn before_prepare_component(&mut self, cx: &mut Context) { + run_actions_before_prepare_item(self, cx); + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let order = match self.weight() { + 0 => None, + _ => Some(concat_string!("order: ", self.weight().to_string(), ";")), + }; + PrepareMarkup::With(html! { + div id=[self.id()] class=[self.item_classes().get()] style=[order] { + div class=[self.inner_classes().get()] { + (self.components().prepare(cx)) + } + } + }) + } + + fn after_prepare_component(&mut self, cx: &mut Context) { + run_actions_after_prepare_item(self, cx); + } +} + +impl Item { + // Item BUILDER. + + #[fn_builder] + pub fn alter_weight(&mut self, value: Weight) -> &mut Self { + self.weight = value; + self + } + + #[fn_builder] + pub fn alter_renderable(&mut self, check: FnIsRenderable) -> &mut Self { + self.renderable.check = check; + self + } + + #[fn_builder] + pub fn alter_id(&mut self, id: &str) -> &mut Self { + self.id.alter_value(id); + self + } + + #[fn_builder] + pub fn alter_item_classes(&mut self, op: ClassesOp, classes: impl Into) -> &mut Self { + self.item_classes.alter_value(op, classes); + self + } + + #[fn_builder] + pub fn alter_inner_classes(&mut self, op: ClassesOp, classes: impl Into) -> &mut Self { + self.inner_classes.alter_value(op, classes); + self + } + + #[fn_builder] + pub fn alter_grow(&mut self, grow: flex::ItemGrow) -> &mut Self { + self.item_classes.alter_value( + ClassesOp::Replace(self.item_grow.to_string()), + grow.to_string(), + ); + self.item_grow = grow; + self + } + + #[fn_builder] + pub fn alter_shrink(&mut self, shrink: flex::ItemShrink) -> &mut Self { + self.item_classes.alter_value( + ClassesOp::Replace(self.item_shrink.to_string()), + shrink.to_string(), + ); + self.item_shrink = shrink; + self + } + + #[fn_builder] + pub fn alter_size(&mut self, size: flex::ItemSize) -> &mut Self { + self.item_classes.alter_value( + ClassesOp::Replace(self.item_size.to_string()), + size.to_string(), + ); + self.item_size = size; + self + } + + #[fn_builder] + pub fn alter_offset(&mut self, offset: flex::ItemOffset) -> &mut Self { + self.item_classes.alter_value( + ClassesOp::Replace(self.item_offset.to_string()), + offset.to_string(), + ); + self.item_offset = offset; + self + } + + #[fn_builder] + pub fn alter_align(&mut self, align: flex::ItemAlign) -> &mut Self { + self.item_classes.alter_value( + ClassesOp::Replace(self.item_align.to_string()), + align.to_string(), + ); + self.item_align = align; + self + } + + pub fn with_component(mut self, component: impl ComponentTrait) -> Self { + self.stuff.alter(ArcOp::Add(ArcComponent::with(component))); + self + } + + #[fn_builder] + pub fn alter_components(&mut self, op: ArcOp) -> &mut Self { + self.stuff.alter(op); + self + } + + // Item GETTERS. + + pub fn item_classes(&self) -> &Classes { + &self.item_classes + } + + pub fn inner_classes(&self) -> &Classes { + &self.inner_classes + } + + pub fn grow(&self) -> &flex::ItemGrow { + &self.item_grow + } + + pub fn shrink(&self) -> &flex::ItemShrink { + &self.item_shrink + } + + pub fn size(&self) -> &flex::ItemSize { + &self.item_size + } + + pub fn offset(&self) -> &flex::ItemOffset { + &self.item_offset + } + + pub fn align(&self) -> &flex::ItemAlign { + &self.item_align + } + + pub fn components(&self) -> &ArcComponents { + &self.stuff + } +} diff --git a/pagetop/src/base/component/form_element.rs b/pagetop/src/base/component/form_element.rs new file mode 100644 index 00000000..bbd4e309 --- /dev/null +++ b/pagetop/src/base/component/form_element.rs @@ -0,0 +1,11 @@ +mod form; +pub use form::{Form, FormMethod, COMPONENT_FORM}; + +mod input; +pub use input::{Input, InputType, COMPONENT_INPUT}; +mod hidden; +pub use hidden::{Hidden, COMPONENT_HIDDEN}; +mod date; +pub use date::{Date, COMPONENT_DATE}; +mod button; +pub use button::{Button, ButtonType, COMPONENT_BUTTON}; diff --git a/pagetop/src/base/component/form_element/button.rs b/pagetop/src/base/component/form_element/button.rs new file mode 100644 index 00000000..ee869382 --- /dev/null +++ b/pagetop/src/base/component/form_element/button.rs @@ -0,0 +1,176 @@ +use crate::prelude::*; + +new_handle!(COMPONENT_BUTTON); + +#[derive(Default)] +pub enum ButtonType { + #[default] + Button, + Submit, + Reset, +} + +type ButtonValue = TypedComponent; + +#[rustfmt::skip] +#[derive(Default)] +pub struct Button { + weight : Weight, + renderable : Renderable, + classes : Classes, + button_type: ButtonType, + name : AttributeValue, + value : ButtonValue, + autofocus : AttributeValue, + disabled : AttributeValue, + template : String, +} + +impl ComponentTrait for Button { + fn new() -> Self { + Button::default().with_classes(ClassesOp::SetDefault, "btn btn-primary form-button") + } + + fn handle(&self) -> Handle { + COMPONENT_BUTTON + } + + fn weight(&self) -> Weight { + self.weight + } + + fn is_renderable(&self, cx: &Context) -> bool { + (self.renderable.check)(cx) + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let button_type = match self.button_type() { + ButtonType::Button => "button", + ButtonType::Submit => "submit", + ButtonType::Reset => "reset", + }; + let id = self.name().get().map(|name| concat_string!("edit-", name)); + let value = self.value().prepare(cx); + PrepareMarkup::With(html! { + button + type=(button_type) + id=[id] + class=[self.classes().get()] + name=[self.name().get()] + value=(value) + autofocus=[self.autofocus().get()] + disabled=[self.disabled().get()] + { + (value) + } + }) + } +} + +impl Button { + pub fn with(value: L10n) -> Self { + Button::new().with_value(value) + } + + pub fn submit(value: L10n) -> Self { + let mut button = Button::new() + .with_classes(ClassesOp::Replace("form-button".to_owned()), "form-submit") + .with_value(value); + button.button_type = ButtonType::Submit; + button + } + + pub fn reset(value: L10n) -> Self { + let mut button = Button::new() + .with_classes(ClassesOp::Replace("form-button".to_owned()), "form-reset") + .with_value(value); + button.button_type = ButtonType::Reset; + button + } + + // Button BUILDER. + + #[fn_builder] + pub fn alter_weight(&mut self, value: Weight) -> &mut Self { + self.weight = value; + self + } + + #[fn_builder] + pub fn alter_renderable(&mut self, check: FnIsRenderable) -> &mut Self { + self.renderable.check = check; + self + } + + #[fn_builder] + pub fn alter_classes(&mut self, op: ClassesOp, classes: impl Into) -> &mut Self { + self.classes.alter_value(op, classes); + self + } + + #[fn_builder] + pub fn alter_name(&mut self, name: &str) -> &mut Self { + self.name.alter_value(name); + self + } + + #[fn_builder] + pub fn alter_value(&mut self, value: L10n) -> &mut Self { + self.value.set(value); + self + } + + #[fn_builder] + pub fn alter_autofocus(&mut self, toggle: bool) -> &mut Self { + self.autofocus.alter_value(match toggle { + true => "autofocus", + false => "", + }); + self + } + + #[fn_builder] + pub fn alter_disabled(&mut self, toggle: bool) -> &mut Self { + self.disabled.alter_value(match toggle { + true => "disabled", + false => "", + }); + self + } + + #[fn_builder] + pub fn alter_template(&mut self, template: &str) -> &mut Self { + self.template = template.to_owned(); + self + } + + // Button GETTERS. + + pub fn classes(&self) -> &Classes { + &self.classes + } + + pub fn button_type(&self) -> &ButtonType { + &self.button_type + } + + pub fn name(&self) -> &AttributeValue { + &self.name + } + + pub fn value(&self) -> &ButtonValue { + &self.value + } + + pub fn autofocus(&self) -> &AttributeValue { + &self.autofocus + } + + pub fn disabled(&self) -> &AttributeValue { + &self.disabled + } + + pub fn template(&self) -> &str { + self.template.as_str() + } +} diff --git a/pagetop/src/base/component/form_element/date.rs b/pagetop/src/base/component/form_element/date.rs new file mode 100644 index 00000000..17e70845 --- /dev/null +++ b/pagetop/src/base/component/form_element/date.rs @@ -0,0 +1,226 @@ +use crate::prelude::*; + +new_handle!(COMPONENT_DATE); + +#[rustfmt::skip] +#[derive(Default)] +pub struct Date { + weight : Weight, + renderable : Renderable, + classes : Classes, + name : AttributeValue, + value : AttributeValue, + label : AttributeValue, + placeholder : AttributeValue, + autofocus : AttributeValue, + autocomplete: AttributeValue, + disabled : AttributeValue, + readonly : AttributeValue, + required : AttributeValue, + help_text : AttributeValue, + template : String, +} + +impl ComponentTrait for Date { + fn new() -> Self { + Date::default().with_classes(ClassesOp::SetDefault, "form-item form-type-date") + } + + fn handle(&self) -> Handle { + COMPONENT_DATE + } + + fn weight(&self) -> Weight { + self.weight + } + + fn is_renderable(&self, cx: &Context) -> bool { + (self.renderable.check)(cx) + } + + fn prepare_component(&self, _cx: &mut Context) -> PrepareMarkup { + let id = self.name().get().map(|name| concat_string!("edit-", name)); + PrepareMarkup::With(html! { + div class=[self.classes().get()] { + @if let Some(label) = self.label().get() { + label class="form-label" for=[&id] { + (label) " " + @if self.required().get().is_some() { + span + class="form-required" + title="Este campo es obligatorio." { "*" } " " + } + } + } + input + type="date" + id=[id] + class="form-control" + name=[self.name().get()] + value=[self.value().get()] + placeholder=[self.placeholder().get()] + autofocus=[self.autofocus().get()] + autocomplete=[self.autocomplete().get()] + readonly=[self.readonly().get()] + required=[self.required().get()] + disabled=[self.disabled().get()] {} + @if let Some(help_text) = self.help_text().get() { + div class="form-text" { (help_text) } + } + } + }) + } +} + +impl Date { + // Date BUILDER. + + #[fn_builder] + pub fn alter_weight(&mut self, value: Weight) -> &mut Self { + self.weight = value; + self + } + + #[fn_builder] + pub fn alter_renderable(&mut self, check: FnIsRenderable) -> &mut Self { + self.renderable.check = check; + self + } + + #[fn_builder] + pub fn alter_classes(&mut self, op: ClassesOp, classes: impl Into) -> &mut Self { + self.classes.alter_value(op, classes); + self + } + + #[fn_builder] + pub fn alter_name(&mut self, name: &str) -> &mut Self { + self.name.alter_value(name); + self + } + + #[fn_builder] + pub fn alter_value(&mut self, value: &str) -> &mut Self { + self.value.alter_value(value); + self + } + + #[fn_builder] + pub fn alter_label(&mut self, label: &str) -> &mut Self { + self.label.alter_value(label); + self + } + + #[fn_builder] + pub fn alter_placeholder(&mut self, placeholder: &str) -> &mut Self { + self.placeholder.alter_value(placeholder); + self + } + + #[fn_builder] + pub fn alter_autofocus(&mut self, toggle: bool) -> &mut Self { + self.autofocus.alter_value(match toggle { + true => "autofocus", + false => "", + }); + self + } + + #[fn_builder] + pub fn alter_autocomplete(&mut self, toggle: bool) -> &mut Self { + self.autocomplete.alter_value(match toggle { + true => "", + false => "off", + }); + self + } + + #[fn_builder] + pub fn alter_disabled(&mut self, toggle: bool) -> &mut Self { + self.disabled.alter_value(match toggle { + true => "disabled", + false => "", + }); + self + } + + #[fn_builder] + pub fn alter_readonly(&mut self, toggle: bool) -> &mut Self { + self.readonly.alter_value(match toggle { + true => "readonly", + false => "", + }); + self + } + + #[fn_builder] + pub fn alter_required(&mut self, toggle: bool) -> &mut Self { + self.required.alter_value(match toggle { + true => "required", + false => "", + }); + self + } + + #[fn_builder] + pub fn alter_help_text(&mut self, help_text: &str) -> &mut Self { + self.help_text.alter_value(help_text); + self + } + + #[fn_builder] + pub fn alter_template(&mut self, template: &str) -> &mut Self { + self.template = template.to_owned(); + self + } + + // Date GETTERS. + + pub fn classes(&self) -> &Classes { + &self.classes + } + + pub fn name(&self) -> &AttributeValue { + &self.name + } + + pub fn value(&self) -> &AttributeValue { + &self.value + } + + pub fn label(&self) -> &AttributeValue { + &self.label + } + + pub fn placeholder(&self) -> &AttributeValue { + &self.placeholder + } + + pub fn autofocus(&self) -> &AttributeValue { + &self.autofocus + } + + pub fn autocomplete(&self) -> &AttributeValue { + &self.autocomplete + } + + pub fn disabled(&self) -> &AttributeValue { + &self.disabled + } + + pub fn readonly(&self) -> &AttributeValue { + &self.readonly + } + + pub fn required(&self) -> &AttributeValue { + &self.required + } + + pub fn help_text(&self) -> &AttributeValue { + &self.help_text + } + + pub fn template(&self) -> &str { + self.template.as_str() + } +} diff --git a/pagetop/src/base/component/form_element/form.rs b/pagetop/src/base/component/form_element/form.rs new file mode 100644 index 00000000..f4da8da7 --- /dev/null +++ b/pagetop/src/base/component/form_element/form.rs @@ -0,0 +1,165 @@ +use crate::prelude::*; + +new_handle!(COMPONENT_FORM); + +actions_for_component!(Form); + +#[derive(Default)] +pub enum FormMethod { + #[default] + Post, + Get, +} + +#[rustfmt::skip] +#[derive(Default)] +pub struct Form { + weight : Weight, + renderable: Renderable, + id : IdentifierValue, + classes : Classes, + action : AttributeValue, + charset : AttributeValue, + method : FormMethod, + stuff : ArcComponents, + template : String, +} + +impl ComponentTrait for Form { + fn new() -> Self { + Form::default() + .with_classes(ClassesOp::SetDefault, "form") + .with_charset("UTF-8") + } + + fn handle(&self) -> Handle { + COMPONENT_FORM + } + + fn id(&self) -> Option { + self.id.get() + } + + fn weight(&self) -> Weight { + self.weight + } + + fn is_renderable(&self, cx: &Context) -> bool { + (self.renderable.check)(cx) + } + + fn before_prepare_component(&mut self, cx: &mut Context) { + run_actions_before_prepare_form(self, cx); + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let method = match self.method() { + FormMethod::Post => Some("post".to_owned()), + FormMethod::Get => None, + }; + PrepareMarkup::With(html! { + form + id=[self.id()] + class=[self.classes().get()] + action=[self.action().get()] + method=[method] + accept-charset=[self.charset().get()] + { + div { (self.elements().prepare(cx)) } + } + }) + } + + fn after_prepare_component(&mut self, cx: &mut Context) { + run_actions_after_prepare_form(self, cx); + } +} + +impl Form { + // Form BUILDER. + + #[fn_builder] + pub fn alter_weight(&mut self, value: Weight) -> &mut Self { + self.weight = value; + self + } + + #[fn_builder] + pub fn alter_renderable(&mut self, check: FnIsRenderable) -> &mut Self { + self.renderable.check = check; + self + } + + #[fn_builder] + pub fn alter_id(&mut self, id: &str) -> &mut Self { + self.id.alter_value(id); + self + } + + #[fn_builder] + pub fn alter_classes(&mut self, op: ClassesOp, classes: impl Into) -> &mut Self { + self.classes.alter_value(op, classes); + self + } + + #[fn_builder] + pub fn alter_action(&mut self, action: &str) -> &mut Self { + self.action.alter_value(action); + self + } + + #[fn_builder] + pub fn alter_charset(&mut self, charset: &str) -> &mut Self { + self.charset.alter_value(charset); + self + } + + #[fn_builder] + pub fn alter_method(&mut self, method: FormMethod) -> &mut Self { + self.method = method; + self + } + + pub fn with_element(mut self, element: impl ComponentTrait) -> Self { + self.stuff.alter(ArcOp::Add(ArcComponent::with(element))); + self + } + + #[fn_builder] + pub fn alter_elements(&mut self, op: ArcOp) -> &mut Self { + self.stuff.alter(op); + self + } + + #[fn_builder] + pub fn alter_template(&mut self, template: &str) -> &mut Self { + self.template = template.to_owned(); + self + } + + // Form GETTERS. + + pub fn classes(&self) -> &Classes { + &self.classes + } + + pub fn action(&self) -> &AttributeValue { + &self.action + } + + pub fn charset(&self) -> &AttributeValue { + &self.charset + } + + pub fn method(&self) -> &FormMethod { + &self.method + } + + pub fn elements(&self) -> &ArcComponents { + &self.stuff + } + + pub fn template(&self) -> &str { + self.template.as_str() + } +} diff --git a/pagetop/src/base/component/form_element/hidden.rs b/pagetop/src/base/component/form_element/hidden.rs new file mode 100644 index 00000000..79264c01 --- /dev/null +++ b/pagetop/src/base/component/form_element/hidden.rs @@ -0,0 +1,68 @@ +use crate::prelude::*; + +new_handle!(COMPONENT_HIDDEN); + +#[rustfmt::skip] +#[derive(Default)] +pub struct Hidden { + weight: Weight, + name : NameValue, + value : AttributeValue, +} + +impl ComponentTrait for Hidden { + fn new() -> Self { + Hidden::default() + } + + fn handle(&self) -> Handle { + COMPONENT_HIDDEN + } + + fn weight(&self) -> Weight { + self.weight + } + + fn prepare_component(&self, _cx: &mut Context) -> PrepareMarkup { + let id = self.name().get().map(|name| concat_string!("value-", name)); + PrepareMarkup::With(html! { + input type="hidden" id=[id] name=[self.name().get()] value=[self.value().get()] {} + }) + } +} + +impl Hidden { + pub fn set(name: &str, value: &str) -> Self { + Hidden::new().with_name(name).with_value(value) + } + + // Hidden BUILDER. + + #[fn_builder] + pub fn alter_weight(&mut self, value: Weight) -> &mut Self { + self.weight = value; + self + } + + #[fn_builder] + pub fn alter_name(&mut self, name: &str) -> &mut Self { + self.name.alter_value(name); + self + } + + #[fn_builder] + pub fn alter_value(&mut self, value: &str) -> &mut Self { + self.value.alter_value(value); + self + } + + // Hidden GETTERS. + + pub fn name(&self) -> &NameValue { + &self.name + } + + pub fn value(&self) -> &AttributeValue { + &self.value + } +} diff --git a/pagetop/src/base/component/form_element/input.rs b/pagetop/src/base/component/form_element/input.rs new file mode 100644 index 00000000..7a06dda5 --- /dev/null +++ b/pagetop/src/base/component/form_element/input.rs @@ -0,0 +1,348 @@ +use crate::prelude::*; + +new_handle!(COMPONENT_INPUT); + +#[derive(Default)] +pub enum InputType { + #[default] + Textfield, + Password, + Search, + Email, + Telephone, + Url, +} + +type InputLabel = TypedComponent; +type InputHelpText = TypedComponent; + +#[rustfmt::skip] +#[derive(Default)] +pub struct Input { + weight : Weight, + renderable : Renderable, + classes : Classes, + input_type : InputType, + name : NameValue, + value : AttributeValue, + label : InputLabel, + size : Option, + minlength : Option, + maxlength : Option, + placeholder : AttributeValue, + autofocus : AttributeValue, + autocomplete: AttributeValue, + disabled : AttributeValue, + readonly : AttributeValue, + required : AttributeValue, + help_text : InputHelpText, + template : String, +} + +impl ComponentTrait for Input { + fn new() -> Self { + Input::default() + .with_classes(ClassesOp::SetDefault, "form-item form-type-textfield") + .with_size(Some(60)) + .with_maxlength(Some(128)) + } + + fn handle(&self) -> Handle { + COMPONENT_INPUT + } + + fn weight(&self) -> Weight { + self.weight + } + + fn is_renderable(&self, cx: &Context) -> bool { + (self.renderable.check)(cx) + } + + #[rustfmt::skip] + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let type_input = match self.input_type() { + InputType::Textfield => "text", + InputType::Password => "password", + InputType::Search => "search", + InputType::Email => "email", + InputType::Telephone => "tel", + InputType::Url => "url", + }; + let id = self.name().get().map(|name| concat_string!("edit-", name)); + let label = self.label().prepare(cx); + let description = self.help_text().prepare(cx); + PrepareMarkup::With(html! { + div class=[self.classes().get()] { + @if !label.is_empty() { + label class="form-label" for=[&id] { + (label) " " + @if self.required().get().is_some() { + span + class="form-required" + title="Este campo es obligatorio." { "*" } " " + } + } + } + input + type=(type_input) + id=[id] + class="form-control" + name=[self.name().get()] + value=[self.value().get()] + size=[self.size()] + minlength=[self.minlength()] + maxlength=[self.maxlength()] + placeholder=[self.placeholder().get()] + autofocus=[self.autofocus().get()] + autocomplete=[self.autocomplete().get()] + readonly=[self.readonly().get()] + required=[self.required().get()] + disabled=[self.disabled().get()] {} + @if !description.is_empty() { + div class="form-text" { (description) } + } + } + }) + } +} + +impl Input { + pub fn textfield() -> Self { + Input::new() + } + + pub fn password() -> Self { + let mut input = Input::new().with_classes( + ClassesOp::Replace("form-type-textfield".to_owned()), + "form-type-password", + ); + input.input_type = InputType::Password; + input + } + + pub fn search() -> Self { + let mut input = Input::new().with_classes( + ClassesOp::Replace("form-type-textfield".to_owned()), + "form-type-search", + ); + input.input_type = InputType::Search; + input + } + + pub fn email() -> Self { + let mut input = Input::new().with_classes( + ClassesOp::Replace("form-type-textfield".to_owned()), + "form-type-email", + ); + input.input_type = InputType::Email; + input + } + + pub fn telephone() -> Self { + let mut input = Input::new().with_classes( + ClassesOp::Replace("form-type-textfield".to_owned()), + "form-type-telephone", + ); + input.input_type = InputType::Telephone; + input + } + + pub fn url() -> Self { + let mut input = Input::new().with_classes( + ClassesOp::Replace("form-type-textfield".to_owned()), + "form-type-url", + ); + input.input_type = InputType::Url; + input + } + + // Input BUILDER. + + #[fn_builder] + pub fn alter_weight(&mut self, value: Weight) -> &mut Self { + self.weight = value; + self + } + + #[fn_builder] + pub fn alter_renderable(&mut self, check: FnIsRenderable) -> &mut Self { + self.renderable.check = check; + self + } + + #[fn_builder] + pub fn alter_classes(&mut self, op: ClassesOp, classes: impl Into) -> &mut Self { + self.classes.alter_value(op, classes); + self + } + + #[fn_builder] + pub fn alter_name(&mut self, name: &str) -> &mut Self { + if let Some(previous) = self.name.get() { + self.alter_classes(ClassesOp::Remove, concat_string!("form-item-", previous)); + } + self.alter_classes(ClassesOp::AddDefault, concat_string!("form-item-", name)); + self.name.alter_value(name); + self + } + + #[fn_builder] + pub fn alter_value(&mut self, value: &str) -> &mut Self { + self.value.alter_value(value); + self + } + + #[fn_builder] + pub fn alter_label(&mut self, label: L10n) -> &mut Self { + self.label.set(label); + self + } + + #[fn_builder] + pub fn alter_size(&mut self, size: Option) -> &mut Self { + self.size = size; + self + } + + #[fn_builder] + pub fn alter_minlength(&mut self, minlength: Option) -> &mut Self { + self.minlength = minlength; + self + } + + #[fn_builder] + pub fn alter_maxlength(&mut self, maxlength: Option) -> &mut Self { + self.maxlength = maxlength; + self + } + + #[fn_builder] + pub fn alter_placeholder(&mut self, placeholder: &str) -> &mut Self { + self.placeholder.alter_value(placeholder); + self + } + + #[fn_builder] + pub fn alter_autofocus(&mut self, toggle: bool) -> &mut Self { + self.autofocus.alter_value(match toggle { + true => "autofocus", + false => "", + }); + self + } + + #[fn_builder] + pub fn alter_autocomplete(&mut self, toggle: bool) -> &mut Self { + self.autocomplete.alter_value(match toggle { + true => "", + false => "off", + }); + self + } + + #[fn_builder] + pub fn alter_disabled(&mut self, toggle: bool) -> &mut Self { + self.disabled.alter_value(match toggle { + true => "disabled", + false => "", + }); + self + } + + #[fn_builder] + pub fn alter_readonly(&mut self, toggle: bool) -> &mut Self { + self.readonly.alter_value(match toggle { + true => "readonly", + false => "", + }); + self + } + + #[fn_builder] + pub fn alter_required(&mut self, toggle: bool) -> &mut Self { + self.required.alter_value(match toggle { + true => "required", + false => "", + }); + self + } + + #[fn_builder] + pub fn alter_help_text(&mut self, help_text: L10n) -> &mut Self { + self.help_text.set(help_text); + self + } + + #[fn_builder] + pub fn alter_template(&mut self, template: &str) -> &mut Self { + self.template = template.to_owned(); + self + } + + // Input GETTERS. + + pub fn classes(&self) -> &Classes { + &self.classes + } + + pub fn input_type(&self) -> &InputType { + &self.input_type + } + + pub fn name(&self) -> &NameValue { + &self.name + } + + pub fn value(&self) -> &AttributeValue { + &self.value + } + + pub fn label(&self) -> &InputLabel { + &self.label + } + + pub fn size(&self) -> Option { + self.size + } + + pub fn minlength(&self) -> Option { + self.minlength + } + + pub fn maxlength(&self) -> Option { + self.maxlength + } + + pub fn placeholder(&self) -> &AttributeValue { + &self.placeholder + } + + pub fn autofocus(&self) -> &AttributeValue { + &self.autofocus + } + + pub fn autocomplete(&self) -> &AttributeValue { + &self.autocomplete + } + + pub fn disabled(&self) -> &AttributeValue { + &self.disabled + } + + pub fn readonly(&self) -> &AttributeValue { + &self.readonly + } + + pub fn required(&self) -> &AttributeValue { + &self.required + } + + pub fn help_text(&self) -> &InputHelpText { + &self.help_text + } + + pub fn template(&self) -> &str { + self.template.as_str() + } +} diff --git a/pagetop/src/base/component/heading.rs b/pagetop/src/base/component/heading.rs new file mode 100644 index 00000000..3b87dd5e --- /dev/null +++ b/pagetop/src/base/component/heading.rs @@ -0,0 +1,199 @@ +use crate::prelude::*; + +new_handle!(COMPONENT_HEADING); + +#[derive(Default)] +pub enum HeadingType { + #[default] + H1, + H2, + H3, + H4, + H5, + H6, +} + +#[derive(Default)] +pub enum HeadingDisplay { + #[default] + Normal, + XxLarge, + Large, + Medium, + Small, + XxSmall, + Subtitle, +} + +type HeadingText = TypedComponent; + +#[rustfmt::skip] +#[derive(Default)] +pub struct Heading { + weight : Weight, + renderable : Renderable, + id : IdentifierValue, + classes : Classes, + heading_type: HeadingType, + text : HeadingText, + display : HeadingDisplay, + template : String, +} + +impl ComponentTrait for Heading { + fn new() -> Self { + Heading::default() + } + + fn handle(&self) -> Handle { + COMPONENT_HEADING + } + + fn id(&self) -> Option { + self.id.get() + } + + fn weight(&self) -> Weight { + self.weight + } + + fn is_renderable(&self, cx: &Context) -> bool { + (self.renderable.check)(cx) + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let id = self.id(); + let classes = self.classes().get(); + PrepareMarkup::With(html! { @match &self.heading_type() { + HeadingType::H1 => h1 id=[id] class=[classes] { (self.text().prepare(cx)) }, + HeadingType::H2 => h2 id=[id] class=[classes] { (self.text().prepare(cx)) }, + HeadingType::H3 => h3 id=[id] class=[classes] { (self.text().prepare(cx)) }, + HeadingType::H4 => h4 id=[id] class=[classes] { (self.text().prepare(cx)) }, + HeadingType::H5 => h5 id=[id] class=[classes] { (self.text().prepare(cx)) }, + HeadingType::H6 => h6 id=[id] class=[classes] { (self.text().prepare(cx)) }, + }}) + } +} + +impl Heading { + pub fn h1(text: L10n) -> Self { + Heading::new() + .with_heading_type(HeadingType::H1) + .with_text(text) + } + + pub fn h2(text: L10n) -> Self { + Heading::new() + .with_heading_type(HeadingType::H2) + .with_text(text) + } + + pub fn h3(text: L10n) -> Self { + Heading::new() + .with_heading_type(HeadingType::H3) + .with_text(text) + } + + pub fn h4(text: L10n) -> Self { + Heading::new() + .with_heading_type(HeadingType::H4) + .with_text(text) + } + + pub fn h5(text: L10n) -> Self { + Heading::new() + .with_heading_type(HeadingType::H5) + .with_text(text) + } + + pub fn h6(text: L10n) -> Self { + Heading::new() + .with_heading_type(HeadingType::H6) + .with_text(text) + } + + // Heading BUILDER. + + #[fn_builder] + pub fn alter_weight(&mut self, value: Weight) -> &mut Self { + self.weight = value; + self + } + + #[fn_builder] + pub fn alter_renderable(&mut self, check: FnIsRenderable) -> &mut Self { + self.renderable.check = check; + self + } + + #[fn_builder] + pub fn alter_id(&mut self, id: &str) -> &mut Self { + self.id.alter_value(id); + self + } + + #[fn_builder] + pub fn alter_classes(&mut self, op: ClassesOp, classes: impl Into) -> &mut Self { + self.classes.alter_value(op, classes); + self + } + + #[fn_builder] + pub fn alter_heading_type(&mut self, heading_type: HeadingType) -> &mut Self { + self.heading_type = heading_type; + self + } + + #[fn_builder] + pub fn alter_text(&mut self, text: L10n) -> &mut Self { + self.text.set(text); + self + } + + #[rustfmt::skip] + #[fn_builder] + pub fn alter_display(&mut self, display: HeadingDisplay) -> &mut Self { + self.alter_classes( + ClassesOp::SetDefault, + match display { + HeadingDisplay::XxLarge => "display-2", + HeadingDisplay::Large => "display-3", + HeadingDisplay::Medium => "display-4", + HeadingDisplay::Small => "display-5", + HeadingDisplay::XxSmall => "display-6", + HeadingDisplay::Normal => "", + HeadingDisplay::Subtitle => "", + }, + ); + self.display = display; + self + } + + #[fn_builder] + pub fn alter_template(&mut self, template: &str) -> &mut Self { + self.template = template.to_owned(); + self + } + + // Paragraph GETTERS. + + pub fn classes(&self) -> &Classes { + &self.classes + } + + pub fn heading_type(&self) -> &HeadingType { + &self.heading_type + } + + pub fn text(&self) -> &HeadingText { + &self.text + } + + pub fn display(&self) -> &HeadingDisplay { + &self.display + } + + pub fn template(&self) -> &str { + self.template.as_str() + } +} diff --git a/pagetop/src/core/component/html.rs b/pagetop/src/base/component/html.rs similarity index 81% rename from pagetop/src/core/component/html.rs rename to pagetop/src/base/component/html.rs index 1c926f5c..76b9acb1 100644 --- a/pagetop/src/core/component/html.rs +++ b/pagetop/src/base/component/html.rs @@ -1,6 +1,4 @@ -use crate::core::component::{ComponentTrait, Context}; -use crate::html::{html, Markup, PrepareMarkup}; -use crate::{fn_builder, new_handle, Handle}; +use crate::prelude::*; new_handle!(COMPONENT_HTML); diff --git a/pagetop/src/base/component/icon.rs b/pagetop/src/base/component/icon.rs new file mode 100644 index 00000000..0b45ea08 --- /dev/null +++ b/pagetop/src/base/component/icon.rs @@ -0,0 +1,79 @@ +use crate::prelude::*; + +new_handle!(COMPONENT_ICON); + +#[rustfmt::skip] +#[derive(Default)] +pub struct Icon { + weight : Weight, + renderable: Renderable, + icon_name : String, + classes : Classes, +} + +impl ComponentTrait for Icon { + fn new() -> Self { + Icon::default().with_classes(ClassesOp::SetDefault, "bi-question-circle-fill") + } + + fn handle(&self) -> Handle { + COMPONENT_ICON + } + + fn weight(&self) -> Weight { + self.weight + } + + fn is_renderable(&self, cx: &Context) -> bool { + (self.renderable.check)(cx) + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + cx.set_param::(PARAM_INCLUDE_ICONS, true); + + PrepareMarkup::With(html! { i class=[self.classes().get()] {} }) + } +} + +impl Icon { + pub fn with(icon_name: &str) -> Self { + Icon::new().with_icon_name(icon_name) + } + + // Icon BUILDER. + + #[fn_builder] + pub fn alter_weight(&mut self, value: Weight) -> &mut Self { + self.weight = value; + self + } + + #[fn_builder] + pub fn alter_renderable(&mut self, check: FnIsRenderable) -> &mut Self { + self.renderable.check = check; + self + } + + #[fn_builder] + pub fn alter_icon_name(&mut self, name: &str) -> &mut Self { + self.alter_classes(ClassesOp::SetDefault, concat_string!("bi-", name)); + self.icon_name = name.to_owned(); + self + } + + #[fn_builder] + pub fn alter_classes(&mut self, op: ClassesOp, classes: impl Into) -> &mut Self { + self.classes.alter_value(op, classes); + self + } + + // Icon GETTERS. + + pub fn icon_name(&self) -> &str { + self.icon_name.as_str() + } + + pub fn classes(&self) -> &Classes { + &self.classes + } +} diff --git a/pagetop/src/base/component/image.rs b/pagetop/src/base/component/image.rs new file mode 100644 index 00000000..ddff8144 --- /dev/null +++ b/pagetop/src/base/component/image.rs @@ -0,0 +1,140 @@ +use crate::prelude::*; + +new_handle!(COMPONENT_IMAGE); + +const IMG_FLUID: &str = "img-fluid"; +const IMG_FIXED: &str = "img-fixed"; + +#[derive(Default)] +pub enum ImageSize { + #[default] + Auto, + Size(u16, u16), + Width(u16), + Height(u16), + Both(u16), +} + +#[rustfmt::skip] +#[derive(Default)] +pub struct Image { + weight : Weight, + renderable: Renderable, + id : IdentifierValue, + classes : Classes, + source : AttributeValue, + size : ImageSize, +} + +impl ComponentTrait for Image { + fn new() -> Self { + Image::default().with_classes(ClassesOp::SetDefault, IMG_FLUID) + } + + fn handle(&self) -> Handle { + COMPONENT_IMAGE + } + + fn id(&self) -> Option { + self.id.get() + } + + fn weight(&self) -> Weight { + self.weight + } + + fn is_renderable(&self, cx: &Context) -> bool { + (self.renderable.check)(cx) + } + + fn prepare_component(&self, _cx: &mut Context) -> PrepareMarkup { + let (width, height) = match self.size() { + ImageSize::Auto => (None, None), + ImageSize::Size(width, height) => (Some(width), Some(height)), + ImageSize::Width(width) => (Some(width), None), + ImageSize::Height(height) => (None, Some(height)), + ImageSize::Both(value) => (Some(value), Some(value)), + }; + PrepareMarkup::With(html! { + img + src=[self.source().get()] + id=[self.id()] + class=[self.classes().get()] + width=[width] + height=[height] {} + }) + } +} + +impl Image { + pub fn with(source: &str) -> Self { + Image::default() + .with_source(source) + .with_classes(ClassesOp::SetDefault, IMG_FLUID) + } + + pub fn fixed(source: &str) -> Self { + Image::default() + .with_source(source) + .with_classes(ClassesOp::SetDefault, IMG_FIXED) + } + + pub fn pagetop() -> Self { + Image::default() + .with_source("/theme/pagetop-logo.svg") + .with_classes(ClassesOp::SetDefault, IMG_FIXED) + .with_size(ImageSize::Size(64, 64)) + } + + // Image BUILDER. + + #[fn_builder] + pub fn alter_weight(&mut self, value: Weight) -> &mut Self { + self.weight = value; + self + } + + #[fn_builder] + pub fn alter_renderable(&mut self, check: FnIsRenderable) -> &mut Self { + self.renderable.check = check; + self + } + + #[fn_builder] + pub fn alter_id(&mut self, id: &str) -> &mut Self { + self.id.alter_value(id); + self + } + + #[fn_builder] + pub fn alter_classes(&mut self, op: ClassesOp, classes: impl Into) -> &mut Self { + self.classes.alter_value(op, classes); + self + } + + #[fn_builder] + pub fn alter_source(&mut self, source: &str) -> &mut Self { + self.source.alter_value(source); + self + } + + #[fn_builder] + pub fn alter_size(&mut self, size: ImageSize) -> &mut Self { + self.size = size; + self + } + + // Image GETTERS. + + pub fn classes(&self) -> &Classes { + &self.classes + } + + pub fn source(&self) -> &AttributeValue { + &self.source + } + + pub fn size(&self) -> &ImageSize { + &self.size + } +} diff --git a/pagetop/src/core/component/l10n.rs b/pagetop/src/base/component/l10n.rs similarity index 94% rename from pagetop/src/core/component/l10n.rs rename to pagetop/src/base/component/l10n.rs index dcde3362..70ac0035 100644 --- a/pagetop/src/core/component/l10n.rs +++ b/pagetop/src/base/component/l10n.rs @@ -1,7 +1,4 @@ -use crate::core::component::{ComponentTrait, Context}; -use crate::html::{html, PreEscaped, PrepareMarkup}; -use crate::locale::{Loader, Locales}; -use crate::{fn_builder, new_handle, Handle}; +use crate::prelude::*; use std::collections::HashMap; diff --git a/pagetop/src/base/component/paragraph.rs b/pagetop/src/base/component/paragraph.rs new file mode 100644 index 00000000..b968140f --- /dev/null +++ b/pagetop/src/base/component/paragraph.rs @@ -0,0 +1,144 @@ +use crate::prelude::*; + +new_handle!(COMPONENT_PARAGRAPH); + +#[derive(Default)] +pub enum ParagraphDisplay { + #[default] + Normal, + XxLarge, + Large, + Medium, + Small, + XxSmall, +} + +#[rustfmt::skip] +#[derive(Default)] +pub struct Paragraph { + weight : Weight, + renderable: Renderable, + id : IdentifierValue, + classes : Classes, + stuff : ArcComponents, + display : ParagraphDisplay, + template : String, +} + +impl ComponentTrait for Paragraph { + fn new() -> Self { + Paragraph::default() + } + + fn handle(&self) -> Handle { + COMPONENT_PARAGRAPH + } + + fn id(&self) -> Option { + self.id.get() + } + + fn weight(&self) -> Weight { + self.weight + } + + fn is_renderable(&self, cx: &Context) -> bool { + (self.renderable.check)(cx) + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + PrepareMarkup::With(html! { + p + id=[self.id()] + class=[self.classes().get()] + { + (self.components().prepare(cx)) + } + }) + } +} + +impl Paragraph { + pub fn with(component: impl ComponentTrait) -> Self { + Paragraph::new().with_component(component) + } + + // Paragraph BUILDER. + + #[fn_builder] + pub fn alter_weight(&mut self, value: Weight) -> &mut Self { + self.weight = value; + self + } + + #[fn_builder] + pub fn alter_renderable(&mut self, check: FnIsRenderable) -> &mut Self { + self.renderable.check = check; + self + } + + #[fn_builder] + pub fn alter_id(&mut self, id: &str) -> &mut Self { + self.id.alter_value(id); + self + } + + #[fn_builder] + pub fn alter_classes(&mut self, op: ClassesOp, classes: impl Into) -> &mut Self { + self.classes.alter_value(op, classes); + self + } + + pub fn with_component(mut self, component: impl ComponentTrait) -> Self { + self.stuff.alter(ArcOp::Add(ArcComponent::with(component))); + self + } + + #[fn_builder] + pub fn alter_components(&mut self, op: ArcOp) -> &mut Self { + self.stuff.alter(op); + self + } + + #[rustfmt::skip] + #[fn_builder] + pub fn alter_display(&mut self, display: ParagraphDisplay) -> &mut Self { + self.alter_classes( + ClassesOp::SetDefault, + match display { + ParagraphDisplay::XxLarge => "fs-2", + ParagraphDisplay::Large => "fs-3", + ParagraphDisplay::Medium => "fs-4", + ParagraphDisplay::Small => "fs-5", + ParagraphDisplay::XxSmall => "fs-6", + ParagraphDisplay::Normal => "", + }, + ); + self.display = display; + self + } + + #[fn_builder] + pub fn alter_template(&mut self, template: &str) -> &mut Self { + self.template = template.to_owned(); + self + } + + // Paragraph GETTERS. + + pub fn classes(&self) -> &Classes { + &self.classes + } + + pub fn components(&self) -> &ArcComponents { + &self.stuff + } + + pub fn display(&self) -> &ParagraphDisplay { + &self.display + } + + pub fn template(&self) -> &str { + self.template.as_str() + } +} diff --git a/pagetop/src/base/component/powered_by.rs b/pagetop/src/base/component/powered_by.rs new file mode 100644 index 00000000..68594cd9 --- /dev/null +++ b/pagetop/src/base/component/powered_by.rs @@ -0,0 +1,135 @@ +use crate::prelude::*; +use crate::LOCALES_PAGETOP; + +new_handle!(COMPONENT_POWEREDBY); + +#[derive(Default, Eq, PartialEq)] +pub enum PoweredByLogo { + #[default] + None, + Color, + LineDark, + LineLight, + LineRGB(u8, u8, u8), +} + +#[rustfmt::skip] +#[derive(Default)] +pub struct PoweredBy { + weight : Weight, + renderable: Renderable, + copyright : Option, + logo : PoweredByLogo, +} + +impl ComponentTrait for PoweredBy { + fn new() -> Self { + let year = Utc::now().format("%Y").to_string(); + let c = concat_string!(year, " © ", config::SETTINGS.app.name); + PoweredBy { + copyright: Some(c), + ..Default::default() + } + } + + fn handle(&self) -> Handle { + COMPONENT_POWEREDBY + } + + fn id(&self) -> Option { + Some("powered-by".to_owned()) + } + + fn weight(&self) -> Weight { + self.weight + } + + fn is_renderable(&self, cx: &Context) -> bool { + (self.renderable.check)(cx) + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let logo = match self.logo() { + PoweredByLogo::Color => { + let logo_txt = L10n::t("pagetop_logo", &LOCALES_PAGETOP); + html! { + span class="pagetop-logo" aria-label=[logo_txt.into_string(cx)] { + img src="/theme/pagetop-logo.svg" alt=[logo_txt.into_string(cx)] {} + } + } + } + PoweredByLogo::LineDark => self.logo_line(10, 11, 9, cx), + PoweredByLogo::LineLight => self.logo_line(255, 255, 255, cx), + PoweredByLogo::LineRGB(r, g, b) => self.logo_line(*r, *g, *b, cx), + _ => html! {}, + }; + + let mut credits = L10n::e("poweredby_pagetop", &LOCALES_PAGETOP).with_arg( + "pagetop_link", + "PageTop", + ); + + PrepareMarkup::With(html! { + div id=[self.id()] { + @if let Some(c) = self.copyright() { + span class="copyright" { (c) "." } " " + } + span class="powered" { (credits.prepare(cx)) " " (logo) } + } + }) + } +} + +impl PoweredBy { + // PoweredBy BUILDER. + + #[fn_builder] + pub fn alter_weight(&mut self, value: Weight) -> &mut Self { + self.weight = value; + self + } + + #[fn_builder] + pub fn alter_renderable(&mut self, check: FnIsRenderable) -> &mut Self { + self.renderable.check = check; + self + } + + #[fn_builder] + pub fn alter_copyright(&mut self, copyright: Option>) -> &mut Self { + self.copyright = copyright.map(|c| c.into()); + self + } + + #[fn_builder] + pub fn alter_logo(&mut self, logo: PoweredByLogo) -> &mut Self { + self.logo = logo; + self + } + + // PoweredBy GETTERS. + + pub fn copyright(&self) -> &Option { + &self.copyright + } + + pub fn logo(&self) -> &PoweredByLogo { + &self.logo + } + + // PoweredBy PRIVATE. + + fn logo_line(&self, r: u8, g: u8, b: u8, cx: &mut Context) -> Markup { + let logo_txt = L10n::t("pagetop_logo", &LOCALES_PAGETOP); + let logo_rgb = format!("rgb({},{},{})", r, g, b); + html! { + span class="pagetop-logo" aria-label=[logo_txt.into_string(cx)] { + svg viewBox="0 0 1614 1614" xmlns="http://www.w3.org/2000/svg" role="img" { + path fill=(logo_rgb) d="M 1573,357 L 1415,357 C 1400,357 1388,369 1388,383 L 1388,410 1335,410 1335,357 C 1335,167 1181,13 992,13 L 621,13 C 432,13 278,167 278,357 L 278,410 225,410 225,383 C 225,369 213,357 198,357 L 40,357 C 25,357 13,369 13,383 L 13,648 C 13,662 25,674 40,674 L 198,674 C 213,674 225,662 225,648 L 225,621 278,621 278,1256 C 278,1446 432,1600 621,1600 L 992,1600 C 1181,1600 1335,1446 1335,1256 L 1335,621 1388,621 1388,648 C 1388,662 1400,674 1415,674 L 1573,674 C 1588,674 1600,662 1600,648 L 1600,383 C 1600,369 1588,357 1573,357 L 1573,357 1573,357 Z M 66,410 L 172,410 172,621 66,621 66,410 66,410 Z M 1282,357 L 1282,488 C 1247,485 1213,477 1181,464 L 1196,437 C 1203,425 1199,409 1186,401 1174,394 1158,398 1150,411 L 1133,440 C 1105,423 1079,401 1056,376 L 1075,361 C 1087,352 1089,335 1079,324 1070,313 1054,311 1042,320 L 1023,335 C 1000,301 981,263 967,221 L 1011,196 C 1023,189 1028,172 1021,160 1013,147 997,143 984,150 L 953,168 C 945,136 941,102 940,66 L 992,66 C 1152,66 1282,197 1282,357 L 1282,357 1282,357 Z M 621,66 L 674,66 674,225 648,225 C 633,225 621,237 621,251 621,266 633,278 648,278 L 674,278 674,357 648,357 C 633,357 621,369 621,383 621,398 633,410 648,410 L 674,410 674,489 648,489 C 633,489 621,501 621,516 621,530 633,542 648,542 L 664,542 C 651,582 626,623 600,662 583,653 563,648 542,648 469,648 410,707 410,780 410,787 411,794 412,801 388,805 361,806 331,806 L 331,357 C 331,197 461,66 621,66 L 621,66 621,66 Z M 621,780 C 621,824 586,859 542,859 498,859 463,824 463,780 463,736 498,701 542,701 586,701 621,736 621,780 L 621,780 621,780 Z M 225,463 L 278,463 278,569 225,569 225,463 225,463 Z M 992,1547 L 621,1547 C 461,1547 331,1416 331,1256 L 331,859 C 367,859 400,858 431,851 454,888 495,912 542,912 615,912 674,853 674,780 674,747 662,718 642,695 675,645 706,594 720,542 L 780,542 C 795,542 807,530 807,516 807,501 795,489 780,489 L 727,489 727,410 780,410 C 795,410 807,398 807,383 807,369 795,357 780,357 L 727,357 727,278 780,278 C 795,278 807,266 807,251 807,237 795,225 780,225 L 727,225 727,66 887,66 C 889,111 895,155 905,196 L 869,217 C 856,224 852,240 859,253 864,261 873,266 882,266 887,266 891,265 895,263 L 921,248 C 937,291 958,331 983,367 L 938,403 C 926,412 925,429 934,440 939,447 947,450 954,450 960,450 966,448 971,444 L 1016,408 C 1043,438 1074,465 1108,485 L 1084,527 C 1076,539 1081,555 1093,563 1098,565 1102,566 1107,566 1116,566 1125,561 1129,553 L 1155,509 C 1194,527 1237,538 1282,541 L 1282,1256 C 1282,1416 1152,1547 992,1547 L 992,1547 992,1547 Z M 1335,463 L 1388,463 1388,569 1335,569 1335,463 1335,463 Z M 1441,410 L 1547,410 1547,621 1441,621 1441,410 1441,410 Z" {} + path fill=(logo_rgb) d="M 1150,1018 L 463,1018 C 448,1018 436,1030 436,1044 L 436,1177 C 436,1348 545,1468 701,1468 L 912,1468 C 1068,1468 1177,1348 1177,1177 L 1177,1044 C 1177,1030 1165,1018 1150,1018 L 1150,1018 1150,1018 Z M 912,1071 L 1018,1071 1018,1124 912,1124 912,1071 912,1071 Z M 489,1071 L 542,1071 542,1124 489,1124 489,1071 489,1071 Z M 701,1415 L 700,1415 C 701,1385 704,1352 718,1343 731,1335 759,1341 795,1359 802,1363 811,1363 818,1359 854,1341 882,1335 895,1343 909,1352 912,1385 913,1415 L 912,1415 701,1415 701,1415 701,1415 Z M 1124,1177 C 1124,1296 1061,1384 966,1408 964,1365 958,1320 922,1298 894,1281 856,1283 807,1306 757,1283 719,1281 691,1298 655,1320 649,1365 647,1408 552,1384 489,1296 489,1177 L 569,1177 C 583,1177 595,1165 595,1150 L 595,1071 859,1071 859,1150 C 859,1165 871,1177 886,1177 L 1044,1177 C 1059,1177 1071,1165 1071,1150 L 1071,1071 1124,1071 1124,1177 1124,1177 1124,1177 Z" {} + path fill=(logo_rgb) d="M 1071,648 C 998,648 939,707 939,780 939,853 998,912 1071,912 1144,912 1203,853 1203,780 1203,707 1144,648 1071,648 L 1071,648 1071,648 Z M 1071,859 C 1027,859 992,824 992,780 992,736 1027,701 1071,701 1115,701 1150,736 1150,780 1150,824 1115,859 1071,859 L 1071,859 1071,859 Z" {} + } + } + } + } +} diff --git a/pagetop/src/base/component/site_branding.rs b/pagetop/src/base/component/site_branding.rs new file mode 100644 index 00000000..a1e9d6ba --- /dev/null +++ b/pagetop/src/base/component/site_branding.rs @@ -0,0 +1,135 @@ +use crate::prelude::*; +use crate::LOCALES_PAGETOP; + +new_handle!(COMPONENT_BRANDING); + +type SiteSlogan = TypedComponent; +type SiteLogo = TypedComponent; + +#[rustfmt::skip] +pub struct SiteBranding { + weight : Weight, + renderable: Renderable, + name : String, + slogan : SiteSlogan, + logo : SiteLogo, + frontpage : FnContextualPath, +} + +#[rustfmt::skip] +impl Default for SiteBranding { + fn default() -> Self { + SiteBranding { + weight : Weight::default(), + renderable: Renderable::default(), + name : config::SETTINGS.app.name.to_owned(), + slogan : SiteSlogan::default(), + logo : SiteLogo::default(), + frontpage : |_| "/", + } + } +} + +impl ComponentTrait for SiteBranding { + fn new() -> Self { + SiteBranding::default() + } + + fn handle(&self) -> Handle { + COMPONENT_BRANDING + } + + fn id(&self) -> Option { + Some("site-branding".to_owned()) + } + + fn weight(&self) -> Weight { + self.weight + } + + fn is_renderable(&self, cx: &Context) -> bool { + (self.renderable.check)(cx) + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let title = L10n::t("site_home", &LOCALES_PAGETOP).prepare(cx); + let slogan = self.slogan().prepare(cx); + PrepareMarkup::With(html! { + div id=[self.id()] { + div class="site-branding-wrapper" { + div class="site-branding-logo" { + (self.logo().prepare(cx)) + } + div class="site-branding-text" { + div class="site-branding-name" { + a href=(self.frontpage()(cx)) title=(title) rel="home" { (self.name()) } + } + @if !slogan.is_empty() { + div class="site-branding-slogan" { + (slogan) + } + } + } + } + } + }) + } +} + +impl SiteBranding { + // SiteBranding BUILDER. + + #[fn_builder] + pub fn alter_weight(&mut self, value: Weight) -> &mut Self { + self.weight = value; + self + } + + #[fn_builder] + pub fn alter_renderable(&mut self, check: FnIsRenderable) -> &mut Self { + self.renderable.check = check; + self + } + + #[fn_builder] + pub fn alter_name(&mut self, name: impl Into) -> &mut Self { + self.name = name.into(); + self + } + + #[fn_builder] + pub fn alter_slogan(&mut self, slogan: L10n) -> &mut Self { + self.slogan = SiteSlogan::with(slogan); + self + } + + #[fn_builder] + pub fn alter_logo(&mut self, logo: Image) -> &mut Self { + self.logo.set(logo); + self + } + + #[fn_builder] + pub fn alter_frontpage(&mut self, frontpage: FnContextualPath) -> &mut Self { + self.frontpage = frontpage; + self + } + + // SiteBranding GETTERS. + + pub fn name(&self) -> &String { + &self.name + } + + pub fn slogan(&self) -> &SiteSlogan { + &self.slogan + } + + pub fn logo(&self) -> &SiteLogo { + &self.logo + } + + pub fn frontpage(&self) -> &FnContextualPath { + &self.frontpage + } +} diff --git a/pagetop/src/base/component/wrapper.rs b/pagetop/src/base/component/wrapper.rs new file mode 100644 index 00000000..339302fc --- /dev/null +++ b/pagetop/src/base/component/wrapper.rs @@ -0,0 +1,195 @@ +use crate::prelude::*; + +new_handle!(COMPONENT_WRAPPER); + +actions_for_component!(Wrapper); + +#[derive(Default)] +pub enum WrapperType { + #[default] + Container, + Header, + Footer, + Main, + Section, +} + +#[rustfmt::skip] +#[derive(Default)] +pub struct Wrapper { + weight : Weight, + renderable : Renderable, + id : IdentifierValue, + classes : Classes, + inner_classes: Classes, + wrapper_type : WrapperType, + stuff : ArcComponents, + template : String, +} + +impl ComponentTrait for Wrapper { + fn new() -> Self { + Wrapper::default() + .with_classes(ClassesOp::SetDefault, "container") + .with_inner_classes(ClassesOp::SetDefault, "container") + } + + fn handle(&self) -> Handle { + COMPONENT_WRAPPER + } + + fn id(&self) -> Option { + self.id.get() + } + + fn weight(&self) -> Weight { + self.weight + } + + fn is_renderable(&self, cx: &Context) -> bool { + (self.renderable.check)(cx) + } + + fn before_prepare_component(&mut self, cx: &mut Context) { + run_actions_before_prepare_wrapper(self, cx); + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + match self.wrapper_type() { + WrapperType::Header => PrepareMarkup::With(html! { + header id=[self.id()] class=[self.classes().get()] { + div class=[self.inner_classes().get()] { + (self.components().prepare(cx)) + } + } + }), + WrapperType::Footer => PrepareMarkup::With(html! { + footer id=[self.id()] class=[self.classes().get()] { + div class=[self.inner_classes().get()] { + (self.components().prepare(cx)) + } + } + }), + WrapperType::Main => PrepareMarkup::With(html! { + main id=[self.id()] class=[self.classes().get()] { + div class=[self.inner_classes().get()] { + (self.components().prepare(cx)) + } + } + }), + WrapperType::Section => PrepareMarkup::With(html! { + section id=[self.id()] class=[self.classes().get()] { + div class=[self.inner_classes().get()] { + (self.components().prepare(cx)) + } + } + }), + _ => PrepareMarkup::With(html! { + div id=[self.id()] class=[self.classes().get()] { + (self.components().prepare(cx)) + } + }), + } + } + + fn after_prepare_component(&mut self, cx: &mut Context) { + run_actions_after_prepare_wrapper(self, cx); + } +} + +impl Wrapper { + pub fn header() -> Self { + let mut c = Wrapper::new().with_classes(ClassesOp::SetDefault, "header"); + c.wrapper_type = WrapperType::Header; + c + } + + pub fn footer() -> Self { + let mut c = Wrapper::new().with_classes(ClassesOp::SetDefault, "footer"); + c.wrapper_type = WrapperType::Footer; + c + } + + pub fn main() -> Self { + let mut c = Wrapper::new().with_classes(ClassesOp::SetDefault, "main"); + c.wrapper_type = WrapperType::Main; + c + } + + pub fn section() -> Self { + let mut c = Wrapper::new().with_classes(ClassesOp::SetDefault, "section"); + c.wrapper_type = WrapperType::Section; + c + } + + // Wrapper BUILDER. + + #[fn_builder] + pub fn alter_weight(&mut self, value: Weight) -> &mut Self { + self.weight = value; + self + } + + #[fn_builder] + pub fn alter_renderable(&mut self, check: FnIsRenderable) -> &mut Self { + self.renderable.check = check; + self + } + + #[fn_builder] + pub fn alter_id(&mut self, id: &str) -> &mut Self { + self.id.alter_value(id); + self + } + + #[fn_builder] + pub fn alter_classes(&mut self, op: ClassesOp, classes: impl Into) -> &mut Self { + self.classes.alter_value(op, classes); + self + } + + #[fn_builder] + pub fn alter_inner_classes(&mut self, op: ClassesOp, classes: impl Into) -> &mut Self { + self.inner_classes.alter_value(op, classes); + self + } + + pub fn with_component(mut self, component: impl ComponentTrait) -> Self { + self.stuff.alter(ArcOp::Add(ArcComponent::with(component))); + self + } + + #[fn_builder] + pub fn alter_components(&mut self, op: ArcOp) -> &mut Self { + self.stuff.alter(op); + self + } + + #[fn_builder] + pub fn alter_template(&mut self, template: &str) -> &mut Self { + self.template = template.to_owned(); + self + } + + // Wrapper GETTERS. + + pub fn classes(&self) -> &Classes { + &self.classes + } + + pub fn inner_classes(&self) -> &Classes { + &self.inner_classes + } + + pub fn wrapper_type(&self) -> &WrapperType { + &self.wrapper_type + } + + pub fn components(&self) -> &ArcComponents { + &self.stuff + } + + pub fn template(&self) -> &str { + self.template.as_str() + } +} diff --git a/pagetop/src/base/theme.rs b/pagetop/src/base/theme.rs new file mode 100644 index 00000000..7c75ca33 --- /dev/null +++ b/pagetop/src/base/theme.rs @@ -0,0 +1,2 @@ +mod inception; +pub use inception::{InceptionTheme, THEME_INCEPTION}; diff --git a/pagetop/src/base/theme/inception.rs b/pagetop/src/base/theme/inception.rs new file mode 100644 index 00000000..b7e7a29b --- /dev/null +++ b/pagetop/src/base/theme/inception.rs @@ -0,0 +1,55 @@ +use crate::prelude::*; + +new_handle!(THEME_INCEPTION); + +static_files!(theme); + +const VERSION_INCEPTION: &str = "0.0.0"; + +pub struct InceptionTheme; + +impl ModuleTrait for InceptionTheme { + fn handle(&self) -> Handle { + THEME_INCEPTION + } + + fn name(&self) -> L10n { + L10n::n("Default") + } + + fn theme(&self) -> Option { + Some(&InceptionTheme) + } + + fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { + static_files_service!(scfg, "/theme", theme); + } +} + +impl ThemeTrait for InceptionTheme { + fn after_prepare_body(&self, page: &mut Page) { + page.alter_favicon(Some(Favicon::new().with_icon("/theme/favicon.ico"))) + .alter_context(ContextOp::AddStyleSheet( + StyleSheet::at("/theme/css/normalize.min.css") + .with_version("8.0.1") + .with_weight(-99), + )) + .alter_context(ContextOp::AddStyleSheet( + StyleSheet::at("/theme/css/root.css").with_version(VERSION_INCEPTION), + )); + + if let Some(true) = page.context().get_param::(PARAM_INCLUDE_FLEX) { + page.alter_context(ContextOp::AddStyleSheet( + StyleSheet::at("/theme/css/flex.css").with_version(VERSION_INCEPTION), + )); + } + if let Some(true) = page.context().get_param::(PARAM_INCLUDE_ICONS) { + page.alter_context(ContextOp::AddStyleSheet( + StyleSheet::at("/theme/icons/bootstrap-icons.css").with_version("1.8.2"), + )); + } + page.alter_context(ContextOp::AddStyleSheet( + StyleSheet::at("/theme/css/styles.css").with_version(VERSION_INCEPTION), + )); + } +} diff --git a/pagetop/src/core/component.rs b/pagetop/src/core/component.rs index 038de2e5..dcb171bd 100644 --- a/pagetop/src/core/component.rs +++ b/pagetop/src/core/component.rs @@ -13,142 +13,3 @@ pub use arc::{ArcComponent, ArcComponents, ArcOp}; mod typed; pub use typed::{TypedComponent, TypedComponents, TypedOp}; - -mod html; -pub use html::{Html, COMPONENT_HTML}; - -mod l10n; -pub use l10n::{L10n, COMPONENT_L10N}; - -#[macro_export] -macro_rules! actions_for_component { - ( $Component:ty ) => { - $crate::paste! { - use $crate::prelude::*; - - pub type [] = fn(component: &$Component, cx: &mut Context); - - // ************************************************************************************* - // ACTION BEFORE PREPARE COMPONENT - // ************************************************************************************* - - $crate::new_handle!([] for Crate); - - pub struct [] { - action: Option<[]>, - weight: Weight, - } - - impl ActionTrait for [] { - fn new() -> Self { - [] { - action: None, - weight: 0, - } - } - - fn handle(&self) -> Handle { - [] - } - - fn weight(&self) -> Weight { - self.weight - } - } - - impl [] { - #[allow(dead_code)] - pub fn with(action: []) -> Self { - [] { - action: Some(action), - weight: 0, - } - } - - #[allow(dead_code)] - pub fn with_weight(mut self, value: Weight) -> Self { - self.weight = value; - self - } - - pub(crate) fn run(&self, component: &mut $Component, cx: &mut Context) { - if let Some(action) = self.action { - action(component, cx) - } - } - } - - #[inline(always)] - pub(crate) fn []( - component: &mut $Component, - cx: &mut Context - ) { - run_actions([], |action| - action_ref::<[]>(&**action) - .run(component, cx) - ); - } - - // ************************************************************************************* - // ACTION AFTER PREPARE COMPONENT - // ************************************************************************************* - - $crate::new_handle!([] for Crate); - - pub struct [] { - action: Option<[]>, - weight: Weight, - } - - impl ActionTrait for [] { - fn new() -> Self { - [] { - action: None, - weight: 0, - } - } - - fn handle(&self) -> Handle { - [] - } - - fn weight(&self) -> Weight { - self.weight - } - } - - impl [] { - #[allow(dead_code)] - pub fn with(action: []) -> Self { - [] { - action: Some(action), - weight: 0, - } - } - - #[allow(dead_code)] - pub fn with_weight(mut self, value: Weight) -> Self { - self.weight = value; - self - } - - pub(crate) fn run(&self, component: &mut $Component, cx: &mut Context) { - if let Some(action) = self.action { - action(component, cx) - } - } - } - - #[inline(always)] - pub(crate) fn []( - component: &mut $Component, - cx: &mut Context - ) { - run_actions([], |action| - action_ref::<[]>(&**action) - .run(component, cx) - ); - } - } - }; -} diff --git a/pagetop/src/core/module/all.rs b/pagetop/src/core/module/all.rs index 1eeb653b..24c0ecb2 100644 --- a/pagetop/src/core/module/all.rs +++ b/pagetop/src/core/module/all.rs @@ -28,7 +28,7 @@ pub fn register_modules(app: ModuleRef) { let mut list: Vec = Vec::new(); // Enable default theme. - add_to_enabled(&mut list, &crate::core::theme::DefaultTheme); + add_to_enabled(&mut list, &crate::base::theme::InceptionTheme); // Enable application modules. add_to_enabled(&mut list, app); diff --git a/pagetop/src/core/module/definition.rs b/pagetop/src/core/module/definition.rs index ade9e045..b81fe75d 100644 --- a/pagetop/src/core/module/definition.rs +++ b/pagetop/src/core/module/definition.rs @@ -1,5 +1,5 @@ +use crate::base::component::L10n; use crate::core::action::Action; -use crate::core::component::L10n; use crate::core::theme::ThemeRef; use crate::{actions, service, util, Handle}; diff --git a/pagetop/src/core/theme.rs b/pagetop/src/core/theme.rs index 7a59d60e..713017c3 100644 --- a/pagetop/src/core/theme.rs +++ b/pagetop/src/core/theme.rs @@ -5,7 +5,4 @@ mod regions; pub(crate) use regions::ComponentsRegions; pub use regions::{add_component_in, Region}; -mod default; -pub use default::DefaultTheme; - pub(crate) mod all; diff --git a/pagetop/src/core/theme/all.rs b/pagetop/src/core/theme/all.rs index f8214b05..3283cc40 100644 --- a/pagetop/src/core/theme/all.rs +++ b/pagetop/src/core/theme/all.rs @@ -13,7 +13,7 @@ pub static THEMES: LazyStatic>> = LazyStatic::new(|| RwLock pub static THEME: LazyStatic = LazyStatic::new(|| match theme_by_single_name(&config::SETTINGS.app.theme) { Some(theme) => theme, - None => &crate::core::theme::DefaultTheme, + None => &crate::base::theme::InceptionTheme, }); // THEME BY NAME *********************************************************************************** diff --git a/pagetop/src/core/theme/default.rs b/pagetop/src/core/theme/default.rs deleted file mode 100644 index deea8dad..00000000 --- a/pagetop/src/core/theme/default.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::core::component::{ContextOp, L10n}; -use crate::core::module::ModuleTrait; -use crate::core::theme::{ThemeRef, ThemeTrait}; -use crate::html::{Favicon, StyleSheet}; -use crate::response::page::Page; -use crate::service; -use crate::{new_handle, static_files, static_files_service, Handle}; - -new_handle!(THEME_DEFAULT); - -static_files!(theme); - -pub struct DefaultTheme; - -impl ModuleTrait for DefaultTheme { - fn handle(&self) -> Handle { - THEME_DEFAULT - } - - fn name(&self) -> L10n { - L10n::n("Default") - } - - fn theme(&self) -> Option { - Some(&DefaultTheme) - } - - fn configure_service(&self, scfg: &mut service::web::ServiceConfig) { - static_files_service!(scfg, "/theme", theme); - } -} - -impl ThemeTrait for DefaultTheme { - fn before_prepare_body(&self, page: &mut Page) { - page.alter_favicon(Some(Favicon::new().with_icon("/theme/favicon.ico"))) - .alter_context(ContextOp::AddStyleSheet( - StyleSheet::at("/theme/css/normalize.min.css") - .with_version("8.0.1") - .with_weight(-99), - )); - } -} diff --git a/pagetop/src/core/theme/definition.rs b/pagetop/src/core/theme/definition.rs index 3650f00e..8321d56f 100644 --- a/pagetop/src/core/theme/definition.rs +++ b/pagetop/src/core/theme/definition.rs @@ -1,4 +1,5 @@ -use crate::core::component::{ComponentTrait, Context, L10n}; +use crate::base::component::L10n; +use crate::core::component::{ComponentTrait, Context}; use crate::core::module::ModuleTrait; use crate::html::{html, Favicon, Markup}; use crate::response::page::Page; diff --git a/pagetop/src/lib.rs b/pagetop/src/lib.rs index 5d3b9509..f3bdb78e 100644 --- a/pagetop/src/lib.rs +++ b/pagetop/src/lib.rs @@ -157,6 +157,9 @@ pub mod core; // Tipos de respuestas a peticiones web. pub mod response; +// Base de acciones, componentes, módulos y temas. +pub mod base; + // Prepara y ejecuta la aplicación. pub mod app; diff --git a/pagetop/src/locale/en-US/base.ftl b/pagetop/src/locale/en-US/base.ftl new file mode 100644 index 00000000..31b03402 --- /dev/null +++ b/pagetop/src/locale/en-US/base.ftl @@ -0,0 +1,6 @@ +# SiteBranding component. +site_home = Home + +# PoweredBy component. +poweredby_pagetop = Powered by {$pagetop_link} +pagetop_logo = PageTop logo diff --git a/pagetop/src/locale/es-ES/base.ftl b/pagetop/src/locale/es-ES/base.ftl new file mode 100644 index 00000000..5f7e3c15 --- /dev/null +++ b/pagetop/src/locale/es-ES/base.ftl @@ -0,0 +1,6 @@ +# SiteBranding component. +site_home = Inicio + +# PoweredBy component. +poweredby_pagetop = Funciona con {$pagetop_link} +pagetop_logo = Logotipo de PageTop diff --git a/pagetop/src/prelude.rs b/pagetop/src/prelude.rs index ec44d267..9e363bf8 100644 --- a/pagetop/src/prelude.rs +++ b/pagetop/src/prelude.rs @@ -20,7 +20,7 @@ pub use crate::static_locales; pub use crate::{static_files, static_files_service}; // crate::core::actions pub use crate::actions; -// crate::core::component +// crate::base::action::component pub use crate::actions_for_component; // API. @@ -49,4 +49,8 @@ pub use crate::core::theme::*; pub use crate::response::fatal_error::*; pub use crate::response::{page::*, redirect::*, ResponseError}; +pub use crate::base::action; +pub use crate::base::component::*; +pub use crate::base::theme; + pub use crate::app::Application; diff --git a/pagetop/src/response/fatal_error.rs b/pagetop/src/response/fatal_error.rs index 5725e601..3994b91c 100644 --- a/pagetop/src/response/fatal_error.rs +++ b/pagetop/src/response/fatal_error.rs @@ -3,7 +3,7 @@ pub use error403::ERROR_403; mod error404; pub use error404::ERROR_404; -use crate::core::component::L10n; +use crate::base::component::L10n; use crate::response::{page::Page, ResponseError}; use crate::service::http::{header::ContentType, StatusCode}; use crate::service::{HttpRequest, HttpResponse}; diff --git a/pagetop/src/response/page.rs b/pagetop/src/response/page.rs index 6510534a..0425dbf0 100644 --- a/pagetop/src/response/page.rs +++ b/pagetop/src/response/page.rs @@ -1,8 +1,7 @@ -mod action; -pub use action::*; - +use crate::base::action::page::{run_actions_after_prepare_body, run_actions_before_prepare_body}; +use crate::base::component::L10n; use crate::core::component::{ArcComponent, ComponentTrait, TypedComponent}; -use crate::core::component::{Context, ContextOp, L10n}; +use crate::core::component::{Context, ContextOp}; use crate::core::theme::ComponentsRegions; use crate::html::{html, Classes, ClassesOp, Favicon, Markup, DOCTYPE}; use crate::response::fatal_error::FatalError; diff --git a/pagetop/static/theme/css/flex.css b/pagetop/static/theme/css/flex.css new file mode 100644 index 00000000..b1145f9d --- /dev/null +++ b/pagetop/static/theme/css/flex.css @@ -0,0 +1,361 @@ +/* CONTAINERS */ + +.flex-container { + display: flex; + flex-wrap: nowrap; + justify-content: flex-start; + position: relative; + max-width: 100%; + width: 100%; + padding: 0 !important; +} +.flex-row, +.flex-col.bp-no { + flex-direction: column; +} +.flex-row.flex-reverse, +.flex-col.flex-reverse.bp-no { + flex-direction: column-reverse; +} +.flex-col, +.flex-row.bp-no { + flex-direction: row; +} +.flex-col.flex-reverse, +.flex-row.flex-reverse.bp-no { + flex-direction: row-reverse; +} + +.flex-container.flex-wrap { + flex-wrap: wrap; + align-content: flex-start; +} +.flex-container.flex-wrap-reverse { + flex-wrap: wrap-reverse; + align-content: flex-start; +} + +.flex-container.flex-align-end { + align-content: flex-end; +} +.flex-container.flex-align-center { + align-content: center; +} +.flex-container.flex-align-stretch { + align-content: stretch; +} +.flex-container.flex-align-space-between { + align-content: space-between; +} +.flex-container.flex-align-space-around { + align-content: space-around; +} + +.flex-container.flex-justify-end { + justify-content: flex-end; +} +.flex-container.flex-justify-center { + justify-content: center; +} +.flex-container.flex-justify-space-between { + justify-content: space-between; +} +.flex-container.flex-justify-space-around { + justify-content: space-around; +} +.flex-container.flex-justify-space-evenly { + justify-content: space-evenly; +} + +.flex-container.flex-item-bottom { + align-items: flex-end; +} +.flex-container.flex-item-middle { + align-items: center; +} +.flex-container.flex-item-stretch { + align-items: stretch; +} +.flex-container.flex-item-baseline { + align-items: baseline; +} + +/* ITEMS */ + +.flex-item { + padding: 0 !important; +} + +.flex-item.flex-grow-1 { + flex-grow: 1; +} +.flex-item.flex-grow-2 { + flex-grow: 2; +} +.flex-item.flex-grow-3 { + flex-grow: 3; +} +.flex-item.flex-grow-4 { + flex-grow: 4; +} +.flex-item.flex-grow-5 { + flex-grow: 5; +} +.flex-item.flex-grow-6 { + flex-grow: 6; +} +.flex-item.flex-grow-7 { + flex-grow: 7; +} +.flex-item.flex-grow-8 { + flex-grow: 8; +} +.flex-item.flex-grow-9 { + flex-grow: 9; +} + +.flex-item.flex-shrink-1 { + flex-shrink: 1; +} +.flex-item.flex-shrink-2 { + flex-shrink: 2; +} +.flex-item.flex-shrink-3 { + flex-shrink: 3; +} +.flex-item.flex-shrink-4 { + flex-shrink: 4; +} +.flex-item.flex-shrink-5 { + flex-shrink: 5; +} +.flex-item.flex-shrink-6 { + flex-shrink: 6; +} +.flex-item.flex-shrink-7 { + flex-shrink: 7; +} +.flex-item.flex-shrink-8 { + flex-shrink: 8; +} +.flex-item.flex-shrink-9 { + flex-shrink: 9; +} + +.flex-item.flex-width-10 { + flex: 0 0 10%; + max-width: 10%; +} +.flex-item.flex-width-20 { + flex: 0 0 20%; + max-width: 20%; +} +.flex-item.flex-width-25 { + flex: 0 0 25%; + max-width: 25%; +} +.flex-item.flex-width-33 { + flex: 0 0 33.3333%; + max-width: 33.3333%; +} +.flex-item.flex-width-40 { + flex: 0 0 40%; + max-width: 40%; +} +.flex-item.flex-width-50 { + flex: 0 0 60%; + max-width: 50%; +} +.flex-item.flex-width-60 { + flex: 0 0 60%; + max-width: 60%; +} +.flex-item.flex-width-66 { + flex: 0 0 66.6666%; + max-width: 66.6666%; +} +.flex-item.flex-width-75 { + flex: 0 0 75%; + max-width: 75%; +} +.flex-item.flex-width-80 { + flex: 0 0 80%; + max-width: 80%; +} +.flex-item.flex-width-90 { + flex: 0 0 90%; + max-width: 90%; +} + +.flex-item.flex-offset-10 { + margin-left: 10%; +} +.flex-item.flex-offset-20 { + margin-left: 20%; +} +.flex-item.flex-offset-25 { + margin-left: 25%; +} +.flex-item.flex-offset-33 { + margin-left: 33.3333%; +} +.flex-item.flex-offset-40 { + margin-left: 40%; +} +.flex-item.flex-offset-50 { + margin-left: 50%; +} +.flex-item.flex-offset-60 { + margin-left: 60%; +} +.flex-item.flex-offset-66 { + margin-left: 66.6666%; +} +.flex-item.flex-offset-75 { + margin-left: 75%; +} +.flex-item.flex-offset-80 { + margin-left: 80%; +} +.flex-item.flex-offset-90 { + margin-left: 90%; +} + +.flex-item.flex-item-top { + align-self: flex-start; +} +.flex-item.flex-item-bottom { + align-self: flex-end; +} +.flex-item.flex-item-middle { + align-self: center; +} +.flex-item.flex-item-stretch { + align-self: stretch; +} +.flex-item.flex-item-baseline { + align-self: baseline; +} + +/* BREAKPOINTS */ + +/* SM - Applies <= 568px */ +@media screen and (max-width: 35.5em) { + .flex-row.bp-sm { + flex-direction: row; + } + .flex-row.flex-reverse.bp-sm { + flex-direction: row-reverse; + } + .flex-col.bp-sm { + flex-direction: column; + } + .flex-col.flex-reverse.bp-sm { + flex-direction: column-reverse; + } + .flex-col.bp-sm .flex-item { + flex: 1 1 auto; + max-width: 100%; + margin-left: 0; + } +} +/* MD - Applies <= 768px */ +@media screen and (max-width: 48em) { + .flex-row.bp-md { + flex-direction: row; + } + .flex-row.flex-reverse.bp-md { + flex-direction: row-reverse; + } + .flex-col.bp-md { + flex-direction: column; + } + .flex-col.flex-reverse.bp-md { + flex-direction: column-reverse; + } + .flex-col.bp-md .flex-item { + flex: 1 1 auto; + max-width: 100%; + margin-left: 0; + } +} +/* LG - Applies <= 1024px */ +@media screen and (max-width: 64em) { + .flex-row.bp-lg { + flex-direction: row; + } + .flex-row.flex-reverse.bp-lg { + flex-direction: row-reverse; + } + .flex-col.bp-lg { + flex-direction: column; + } + .flex-col.flex-reverse.bp-lg { + flex-direction: column-reverse; + } + .flex-col.bp-lg .flex-item { + flex: 1 1 auto; + max-width: 100%; + margin-left: 0; + } +} +/* XL - Applies <= 1280px */ +@media screen and (max-width: 80em) { + .flex-row.bp-xl { + flex-direction: row; + } + .flex-row.flex-reverse.bp-xl { + flex-direction: row-reverse; + } + .flex-col.bp-xl { + flex-direction: column; + } + .flex-col.flex-reverse.bp-xl { + flex-direction: column-reverse; + } + .flex-col.bp-xl .flex-item { + flex: 1 1 auto; + max-width: 100%; + margin-left: 0; + } +} +/* X2L - Applies <= 1920px */ +@media screen and (max-width: 120em) { + .flex-row.bp-x2l { + flex-direction: row; + } + .flex-row.flex-reverse.bp-x2l { + flex-direction: row-reverse; + } + .flex-col.bp-x2l { + flex-direction: column; + } + .flex-col.flex-reverse.bp-x2l { + flex-direction: column-reverse; + } + .flex-col.bp-x2l .flex-item { + flex: 1 1 auto; + max-width: 100%; + margin-left: 0; + } +} +/* X3L - Applies <= 2560px */ +@media screen and (max-width: 160em) { + .flex-row.bp-x3l { + flex-direction: row; + } + .flex-row.flex-reverse.bp-x3l { + flex-direction: row-reverse; + } + .flex-col.bp-x3l { + flex-direction: column; + } + .flex-col.flex-reverse.bp-x3l { + flex-direction: column-reverse; + } + .flex-col.bp-x3l .flex-item { + flex: 1 1 auto; + max-width: 100%; + margin-left: 0; + } +} diff --git a/pagetop/static/theme/css/root.css b/pagetop/static/theme/css/root.css new file mode 100644 index 00000000..6a651354 --- /dev/null +++ b/pagetop/static/theme/css/root.css @@ -0,0 +1,122 @@ +:root { +/* + --font-sans: "metropolis",sans-serif; + --font-serif: "Lora","georgia",serif; + --font-size-base: 1rem; +*/ + --font-size-xxl: 1.875rem; + --font-size-xl: 1.425rem; + --font-size-l: 1.125rem; +/* + --font-size-s: 0.875rem; + --font-size-xs: 0.8125rem; + --font-size-xxs: 0.75rem; + --line-height-base: 1.6875rem; + --line-height-s: 1.125rem; + --max-width: 84.375rem; + --max-bg-color: 98.125rem; +*/ + --sp: 1.125rem; +/* + --content-left: 5.625rem; + --site-header-height-wide: var(--sp10); + --container-padding: var(--sp); +*/ +} +/* +@media (min-width: 75rem) { + :root { + --container-padding:var(--sp2); + } +} + +:root { + --scrollbar-width: 0px; + --grid-col-count: 6; + --grid-gap: var(--sp); + --grid-gap-count: calc(var(--grid-col-count) - 1); + --grid-full-width: calc(100vw - var(--sp2) - var(--scrollbar-width)); + --grid-col-width: calc((var(--grid-full-width) - (var(--grid-gap-count) * var(--grid-gap))) / var(--grid-col-count)); +} + +@media (min-width: 43.75rem) { + :root { + --grid-col-count:14; + --grid-gap: var(--sp2); + } +} + +@media (min-width: 62.5rem) { + :root { + --scrollbar-width:0.9375rem; + } +} + +@media (min-width: 75rem) { + :root { + --grid-full-width:calc(100vw - var(--scrollbar-width) - var(--content-left) - var(--sp4)); + } +} + +@media (min-width: 90rem) { + :root { + --grid-full-width:calc(var(--max-width) - var(--sp4)); + } +} +*/ +:root { + --sp0-25: calc(0.25 * var(--sp)); +/* + --sp0-5: calc(0.5 * var(--sp)); +*/ + --sp0-75: calc(0.75 * var(--sp)); +/* + --sp1: calc(1 * var(--sp)); + --sp1-5: calc(1.5 * var(--sp)); + --sp2: calc(2 * var(--sp)); + --sp2-5: calc(2.5 * var(--sp)); + --sp3: calc(3 * var(--sp)); + --sp4: calc(4 * var(--sp)); + --sp5: calc(5 * var(--sp)); + --sp6: calc(6 * var(--sp)); + --sp7: calc(7 * var(--sp)); + --sp8: calc(8 * var(--sp)); + --sp9: calc(9 * var(--sp)); + --sp10: calc(10 * var(--sp)); + --sp11: calc(11 * var(--sp)); + --sp12: calc(12 * var(--sp)); + --color--gray-hue: 201; + --color--gray-saturation: 15%; + --color--gray-5: hsl(var(--color--gray-hue),var(--color--gray-saturation),5%); + --color--gray-10: hsl(var(--color--gray-hue),var(--color--gray-saturation),11%); + --color--gray-20: hsl(var(--color--gray-hue),var(--color--gray-saturation),20%); + --color--gray-45: hsl(var(--color--gray-hue),var(--color--gray-saturation),44%); + --color--gray-60: hsl(var(--color--gray-hue),var(--color--gray-saturation),57%); + --color--gray-65: hsl(var(--color--gray-hue),var(--color--gray-saturation),63%); + --color--gray-70: hsl(var(--color--gray-hue),var(--color--gray-saturation),72%); + --color--gray-90: hsl(var(--color--gray-hue),var(--color--gray-saturation),88%); + --color--gray-95: hsl(var(--color--gray-hue),var(--color--gray-saturation),93%); + --color--gray-100: hsl(var(--color--gray-hue),var(--color--gray-saturation),97%); + --color--primary-hue: 202; + --color--primary-saturation: 79%; + --color--primary-lightness: 50; + --color--primary-30: hsl(var(--color--primary-hue),var(--color--primary-saturation),calc(1% * (var(--color--primary-lightness) - (0.36 * var(--color--primary-lightness))))); + --color--primary-40: hsl(var(--color--primary-hue),var(--color--primary-saturation),calc(1% * (var(--color--primary-lightness) - (0.24 * var(--color--primary-lightness))))); + --color--primary-50: hsl(var(--color--primary-hue),var(--color--primary-saturation),calc(1% * var(--color--primary-lightness))); + --color--primary-60: hsl(var(--color--primary-hue),var(--color--primary-saturation),calc(1% * (var(--color--primary-lightness) + (0.24 * (100 - var(--color--primary-lightness)))))); + --color--primary-80: hsl(var(--color--primary-hue),var(--color--primary-saturation),calc(1% * (var(--color--primary-lightness) + (0.85 * (100 - var(--color--primary-lightness)))))); + --color-text-neutral-soft: var(--color--gray-45); + --color-text-neutral-medium: var(--color--gray-20); + --color-text-neutral-loud: var(--color--gray-5); + --color-text-primary-medium: var(--color--primary-40); + --color-text-primary-loud: var(--color--primary-30); + --color--black: #000; + --color--white: #fff; + --color--red: #e33f1e; + --color--gold: #fdca40; + --color--green: #3fa21c; + --header-height-wide-when-fixed: calc(6 * var(--sp)); + --mobile-nav-width: 31.25rem; + --border-radius: 0.1875rem; +*/ +} diff --git a/pagetop/static/theme/css/styles.css b/pagetop/static/theme/css/styles.css new file mode 100644 index 00000000..a81af6ad --- /dev/null +++ b/pagetop/static/theme/css/styles.css @@ -0,0 +1,46 @@ +/* Image component */ + +.img-fluid { + max-width: 100%; + height: auto; +} + +/* SiteBranding component */ + +.site-branding-wrapper { + display: flex; + align-items: flex-end; + column-gap: var(--sp0-75); +} +.site-branding-name { + letter-spacing: 0.02em; + font-size: var(--font-size-xxl); +} +.site-branding-slogan { + font-size: var(--font-size-xl); +} + +/* SM - Applies <= 568px */ +@media (max-width: 35.5em) { + .site-branding-logo { + display: none; + } +} +/* LG - Applies <= 1024px */ +@media (max-width: 64em) { + .site-branding-slogan { + font-size: var(--font-size-l); + } +} + +/* PoweredBy component */ + +#powered-by { + text-align: center; +} +#powered-by .pagetop-logo img, +#powered-by .pagetop-logo svg { + margin-left: .275em; + height: 1.275em; + vertical-align: middle; +} diff --git a/pagetop/static/theme/icons/bootstrap-icons.css b/pagetop/static/theme/icons/bootstrap-icons.css new file mode 100644 index 00000000..9a7d8ae2 --- /dev/null +++ b/pagetop/static/theme/icons/bootstrap-icons.css @@ -0,0 +1,1705 @@ +@font-face { + font-family: "bootstrap-icons"; + src: url("bootstrap-icons.woff2?ver=1.8.2") format("woff2"), + url("bootstrap-icons.woff?ver=1.8.2") format("woff"); +} + +.bi::before, +[class^="bi-"]::before, +[class*=" bi-"]::before { + display: inline-block; + font-display: 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-1::before { content: "\f2a5"; } +.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-1::before { content: "\f68a"; } +.bi-envelope-check-fill::before { content: "\f68b"; } +.bi-envelope-check::before { content: "\f68c"; } +.bi-envelope-dash-1::before { content: "\f68d"; } +.bi-envelope-dash-fill::before { content: "\f68e"; } +.bi-envelope-dash::before { content: "\f68f"; } +.bi-envelope-exclamation-1::before { content: "\f690"; } +.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-1::before { content: "\f695"; } +.bi-envelope-slash-fill::before { content: "\f696"; } +.bi-envelope-slash::before { content: "\f697"; } +.bi-envelope-x-1::before { content: "\f698"; } +.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-mortorboard-fill::before { content: "\f6a2"; } +.bi-mortorboard::before { content: "\f6a3"; } +.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-1::before { content: "\f6b6"; } +.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-1::before { content: "\f6c2"; } +.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-1::before { content: "\f6e0"; } +.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-ssd-fill::before { content: "\f6ed"; } +.bi-ssd::before { content: "\f6ee"; } +.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-1::before { content: "\f759"; } +.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-1::before { content: "\f769"; } +.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"; } diff --git a/pagetop/static/theme/icons/bootstrap-icons.woff b/pagetop/static/theme/icons/bootstrap-icons.woff new file mode 100644 index 00000000..4cd66b71 Binary files /dev/null and b/pagetop/static/theme/icons/bootstrap-icons.woff differ diff --git a/pagetop/static/theme/icons/bootstrap-icons.woff2 b/pagetop/static/theme/icons/bootstrap-icons.woff2 new file mode 100644 index 00000000..de01cad9 Binary files /dev/null and b/pagetop/static/theme/icons/bootstrap-icons.woff2 differ diff --git a/pagetop/static/theme/pagetop-logo.svg b/pagetop/static/theme/pagetop-logo.svg new file mode 100644 index 00000000..3666bf3a --- /dev/null +++ b/pagetop/static/theme/pagetop-logo.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + +