From f2fb7bd73278f9727d259d8a1cb19ca8de4242b2 Mon Sep 17 00:00:00 2001 From: Harrison Deng Date: Fri, 23 Apr 2021 05:12:43 -0500 Subject: [PATCH 001/167] Initial commit. --- .gitignore | 283 +++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 3 + 2 files changed, 286 insertions(+) create mode 100644 .gitignore create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..21a3504 --- /dev/null +++ b/.gitignore @@ -0,0 +1,283 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/vscode,aspnetcore +# Edit at https://www.toptal.com/developers/gitignore?templates=vscode,aspnetcore + +### ASPNETCore ### +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/ + +### vscode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# End of https://www.toptal.com/developers/gitignore/api/vscode,aspnetcore \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..5278d56 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# MultiShop + +A simple web app whose main purpose is to allows users to query multiple online stores for a product and compare their products information. \ No newline at end of file From 3057bb8dfc11c6531496cc3893c782b52082f168 Mon Sep 17 00:00:00 2001 From: Harrison Deng Date: Fri, 23 Apr 2021 15:11:49 -0500 Subject: [PATCH 002/167] Completed AliExpress interfacing module. Began work on assembly loading. --- .vscode/launch.json | 11 + .vscode/tasks.json | 42 ++ SimpleLogger | 1 + src/AliExpressShop/AliExpressShop.csproj | 12 + src/AliExpressShop/Shop.cs | 259 +++++++++ src/MultiShop.ShopFramework/Currency.cs | 8 + src/MultiShop.ShopFramework/IShop.cs | 16 + .../MultiShop.ShopFramework.csproj | 7 + src/MultiShop.ShopFramework/ProductListing.cs | 15 + src/MultiShop/App.razor | 10 + src/MultiShop/MultiShop.csproj | 18 + src/MultiShop/Pages/Counter.razor | 16 + src/MultiShop/Pages/FetchData.razor | 55 ++ src/MultiShop/Pages/Index.razor | 7 + src/MultiShop/Program.cs | 27 + src/MultiShop/Properties/launchSettings.json | 30 + src/MultiShop/Shared/MainLayout.razor | 17 + src/MultiShop/Shared/MainLayout.razor.css | 70 +++ src/MultiShop/Shared/NavMenu.razor | 37 ++ src/MultiShop/Shared/NavMenu.razor.css | 62 ++ src/MultiShop/Shared/SurveyPrompt.razor | 16 + src/MultiShop/Shops/LoaderContext.cs | 28 + src/MultiShop/Shops/ShopLoader.cs | 41 ++ src/MultiShop/_Imports.razor | 10 + src/MultiShop/wwwroot/css/app.css | 50 ++ .../wwwroot/css/bootstrap/bootstrap.min.css | 7 + .../css/bootstrap/bootstrap.min.css.map | 1 + .../wwwroot/css/open-iconic/FONT-LICENSE | 86 +++ .../wwwroot/css/open-iconic/ICON-LICENSE | 21 + .../wwwroot/css/open-iconic/README.md | 114 ++++ .../font/css/open-iconic-bootstrap.min.css | 1 + .../open-iconic/font/fonts/open-iconic.eot | Bin 0 -> 28196 bytes .../open-iconic/font/fonts/open-iconic.otf | Bin 0 -> 20996 bytes .../open-iconic/font/fonts/open-iconic.svg | 543 ++++++++++++++++++ .../open-iconic/font/fonts/open-iconic.ttf | Bin 0 -> 28028 bytes .../open-iconic/font/fonts/open-iconic.woff | Bin 0 -> 14984 bytes src/MultiShop/wwwroot/favicon.ico | Bin 0 -> 5430 bytes src/MultiShop/wwwroot/index.html | 25 + .../wwwroot/sample-data/weather.json | 27 + .../AliExpressShop.Tests.csproj | 27 + test/AliExpressShop.Tests/ShopTest.cs | 73 +++ test/AliExpressShop.Tests/XUnitLogger.cs | 33 ++ 42 files changed, 1823 insertions(+) create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 160000 SimpleLogger create mode 100644 src/AliExpressShop/AliExpressShop.csproj create mode 100644 src/AliExpressShop/Shop.cs create mode 100644 src/MultiShop.ShopFramework/Currency.cs create mode 100644 src/MultiShop.ShopFramework/IShop.cs create mode 100644 src/MultiShop.ShopFramework/MultiShop.ShopFramework.csproj create mode 100644 src/MultiShop.ShopFramework/ProductListing.cs create mode 100644 src/MultiShop/App.razor create mode 100644 src/MultiShop/MultiShop.csproj create mode 100644 src/MultiShop/Pages/Counter.razor create mode 100644 src/MultiShop/Pages/FetchData.razor create mode 100644 src/MultiShop/Pages/Index.razor create mode 100644 src/MultiShop/Program.cs create mode 100644 src/MultiShop/Properties/launchSettings.json create mode 100644 src/MultiShop/Shared/MainLayout.razor create mode 100644 src/MultiShop/Shared/MainLayout.razor.css create mode 100644 src/MultiShop/Shared/NavMenu.razor create mode 100644 src/MultiShop/Shared/NavMenu.razor.css create mode 100644 src/MultiShop/Shared/SurveyPrompt.razor create mode 100644 src/MultiShop/Shops/LoaderContext.cs create mode 100644 src/MultiShop/Shops/ShopLoader.cs create mode 100644 src/MultiShop/_Imports.razor create mode 100644 src/MultiShop/wwwroot/css/app.css create mode 100644 src/MultiShop/wwwroot/css/bootstrap/bootstrap.min.css create mode 100644 src/MultiShop/wwwroot/css/bootstrap/bootstrap.min.css.map create mode 100644 src/MultiShop/wwwroot/css/open-iconic/FONT-LICENSE create mode 100644 src/MultiShop/wwwroot/css/open-iconic/ICON-LICENSE create mode 100644 src/MultiShop/wwwroot/css/open-iconic/README.md create mode 100644 src/MultiShop/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css create mode 100644 src/MultiShop/wwwroot/css/open-iconic/font/fonts/open-iconic.eot create mode 100644 src/MultiShop/wwwroot/css/open-iconic/font/fonts/open-iconic.otf create mode 100644 src/MultiShop/wwwroot/css/open-iconic/font/fonts/open-iconic.svg create mode 100644 src/MultiShop/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf create mode 100644 src/MultiShop/wwwroot/css/open-iconic/font/fonts/open-iconic.woff create mode 100644 src/MultiShop/wwwroot/favicon.ico create mode 100644 src/MultiShop/wwwroot/index.html create mode 100644 src/MultiShop/wwwroot/sample-data/weather.json create mode 100644 test/AliExpressShop.Tests/AliExpressShop.Tests.csproj create mode 100644 test/AliExpressShop.Tests/ShopTest.cs create mode 100644 test/AliExpressShop.Tests/XUnitLogger.cs diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..6e97436 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch and Debug Standalone Blazor WebAssembly App", + "type": "blazorwasm", + "request": "launch", + "cwd": "${workspaceFolder}/src/MultiShop" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..1ef2bb7 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,42 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/src/MultiShop/MultiShop.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/src/MultiShop/MultiShop.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "${workspaceFolder}/src/MultiShop/MultiShop.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/SimpleLogger b/SimpleLogger new file mode 160000 index 0000000..c1c14d9 --- /dev/null +++ b/SimpleLogger @@ -0,0 +1 @@ +Subproject commit c1c14d96ea5ab91a45acced9bb342ed228347eab diff --git a/src/AliExpressShop/AliExpressShop.csproj b/src/AliExpressShop/AliExpressShop.csproj new file mode 100644 index 0000000..c7f0d6c --- /dev/null +++ b/src/AliExpressShop/AliExpressShop.csproj @@ -0,0 +1,12 @@ + + + + + + + + + net5.0 + + + diff --git a/src/AliExpressShop/Shop.cs b/src/AliExpressShop/Shop.cs new file mode 100644 index 0000000..8d98108 --- /dev/null +++ b/src/AliExpressShop/Shop.cs @@ -0,0 +1,259 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Http; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using MultiShop.ShopFramework; +using SimpleLogger; + +namespace AliExpressShop +{ + public class Shop : IShop + { + private const string ALIEXPRESS_BASE_URL = "https://www.aliexpress.com"; + private const string ALIEXPRESS_QUERY_FORMAT = "/wholesale?trafficChannel=main&d=y&CatId=0&SearchText={0}<ype=wholesale&SortType=default&page={1}"; + private const char SPACE_CHAR = '+'; + private const int DELAY = 500; + + //Regex + private Regex dataLineRegex = new Regex("^ +window.runParams = .+\"items\":.+;$"); + private Regex pageCountRegex = new Regex("\"maxPage\":(\\d+)"); + private Regex itemRatingRegex = new Regex("\"starRating\":\"(\\d*.\\d*)\""); + private Regex itemsSoldRegex = new Regex("\"tradeDesc\":\"(\\d+) sold\""); + private const string SHIPPING_REGEX_FORMAT = "Shipping: {0} ?\\$ (\\d*.\\d*)"; + private Regex shippingPriceRegex; + private readonly string freeShippingStr = "\"logisticsDesc\":\"Free Shipping\""; + private const string PRICE_REGEX_FORMAT = "\"price\":\"{0} ?\\$ ?(\\d*.\\d*)( - (\\d+.\\d+))?\","; + private Regex itemPriceRegex; + + //Sequences + private const string ITEM_LIST_SEQ = "\"items\":"; + private const string TITLE_SEQ = "\"title\":"; + private const string IMAGE_URL_SEQ = "\"imageUrl\":"; + private const string PRODUCT_URL_SEQ = "\"productDetailUrl\":"; + private HttpClientHandler handler; + private HttpClient client; + private bool disposedValue; + + public string ShopName => "AliExpress"; + + public string ShopDescription => "A China based online store."; + + public string ShopModuleAuthor => null; + + public void Initiate(Currency currency) + { + if (disposedValue) throw new ObjectDisposedException("Shop"); + if (client != null) throw new InvalidOperationException("Already initiated."); + Logger.AddLogListener(new ConsoleLogReceiver()); + itemPriceRegex = new Regex(string.Format(PRICE_REGEX_FORMAT, CurrencyToDisplayStr(currency))); + shippingPriceRegex = new Regex(string.Format(SHIPPING_REGEX_FORMAT, CurrencyToDisplayStr(currency))); + + Uri baseAddress = new Uri(ALIEXPRESS_BASE_URL); + CookieContainer container = new CookieContainer(); + handler = new HttpClientHandler(); + handler.CookieContainer = container; + client = new HttpClient(handler); + client.BaseAddress = baseAddress; + client.Send(new HttpRequestMessage()); + container.Add(baseAddress, new Cookie("aep_usuc_f", string.Format("site=glo&c_tp={0}®ion=CA&b_locale=en_US", currency))); + } + + public async Task> Search(string query, int maxPage = -1) + { + if (disposedValue) throw new ObjectDisposedException("Shop"); + if (client == null) throw new InvalidOperationException("HTTP client is not initiated."); + List listings = new List(); + + string modifiedQuery = query.Replace(' ', SPACE_CHAR); + Logger.Log($"Searching {ShopName} with query \"{query}\".", LogLevel.INFO); + + int? length = null; + for (int i = 1; i <= (length != null ? length : 1); i++) + { + if (maxPage != -1 && i > maxPage) break; + HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, string.Format(ALIEXPRESS_QUERY_FORMAT, modifiedQuery, i)); + Logger.Log($"Sending GET request with uri: {request.RequestUri}", LogLevel.DEBUG); + HttpResponseMessage response = await client.SendAsync(request); + + string data = null; + using (StreamReader reader = new StreamReader(await response.Content.ReadAsStreamAsync())) + { + string line = null; + while ((line = await reader.ReadLineAsync()) != null && (data == null || length == null)) + { + if (dataLineRegex.IsMatch(line)) { + data = line.Trim(); + Logger.Log($"Found line with listing data.", LogLevel.DEBUG); + } else if (length == null && pageCountRegex.IsMatch(line)) { + Match match = pageCountRegex.Match(line); + length = int.Parse(match.Groups[1].Captures[0].Value); + Logger.Log($"Found {length} pages.", LogLevel.DEBUG); + } + } + } + if (data == null) return listings; + string itemsString = GetBracketSet(data, data.IndexOf(ITEM_LIST_SEQ) + ITEM_LIST_SEQ.Length, '[', ']'); + IEnumerable listingsStrs = GetItemsFromString(itemsString); + foreach (string listingStr in listingsStrs) + { + listings.Add(GenerateListingFromString(listingStr)); + } + Logger.Log($"Delaying next page by {DELAY}ms.", LogLevel.DEBUG); + await Task.Delay(DELAY); + } + return listings; + } + + private ProductListing GenerateListingFromString(string str) { + ProductListing listing = new ProductListing(); + string name = GetQuoteSet(str, str.IndexOf(TITLE_SEQ) + TITLE_SEQ.Length); + if (name != null) { + Logger.Log($"Found name: {name}", LogLevel.DEBUG); + listing.Name = name; + } else { + Logger.Log($"Unable to get listing name from: \n {str}", LogLevel.WARNING); + } + Match ratingMatch = itemRatingRegex.Match(str); + if (ratingMatch.Success) { + Logger.Log($"Found rating: {ratingMatch.Groups[1].Value}", LogLevel.DEBUG); + listing.Rating = float.Parse(ratingMatch.Groups[1].Value); + } + Match numberSoldMatch = itemsSoldRegex.Match(str); + if (numberSoldMatch.Success) { + Logger.Log($"Found quantity sold: {numberSoldMatch.Groups[1].Value}", LogLevel.DEBUG); + listing.PurchaseCount = int.Parse(numberSoldMatch.Groups[1].Value); + } + + Match priceMatch = itemPriceRegex.Match(str); + if (priceMatch.Success) { + Logger.Log($"Found price: {priceMatch.Groups[1].Value}", LogLevel.DEBUG); + listing.LowerPrice = float.Parse(priceMatch.Groups[1].Value); + if (priceMatch.Groups[3].Success) { + Logger.Log($"Found a price range: {priceMatch.Groups[3].Value}", LogLevel.DEBUG); + listing.UpperPrice = float.Parse(priceMatch.Groups[3].Value); + } else { + listing.UpperPrice = listing.LowerPrice.Value; + } + } else { + Logger.Log($"Unable to get listing price from: \n {str}", LogLevel.WARNING); + } + + string prodUrl = GetQuoteSet(str, str.IndexOf(PRODUCT_URL_SEQ) + PRODUCT_URL_SEQ.Length).Substring(2); + if (prodUrl != null) { + Logger.Log($"Found URL: {prodUrl}", LogLevel.DEBUG); + listing.URL = prodUrl; + } else { + Logger.Log($"Unable to get item URL from: \n {str}", LogLevel.WARNING); + } + string imageUrl = GetQuoteSet(str, str.IndexOf(IMAGE_URL_SEQ) + IMAGE_URL_SEQ.Length).Substring(2); + if (imageUrl != null) { + Logger.Log($"Found image URL: {imageUrl}", LogLevel.DEBUG); + listing.ImageURL = imageUrl; + } + Match shippingMatch = shippingPriceRegex.Match(str); + if (shippingMatch.Success) { + Logger.Log($"Found shipping price: {shippingMatch.Groups[1].Value}", LogLevel.DEBUG); + listing.Shipping = float.Parse(shippingMatch.Groups[1].Value); + } else if (str.Contains(freeShippingStr)) { + listing.Shipping = 0; + } else { + listing.Shipping = null; + } + return listing; + } + + private string GetQuoteSet(string str, int start) { + char[] cs = str.ToCharArray(); + int quoteCount = 0; + int a = -1; + if (start < 0) return null; + for (int b = start; b < cs.Length; b++) + { + if (cs[b] == '"' && !(b >= 1 && cs[b - 1] == '\\')) { + if (a == -1) { + a = b + 1; + } + quoteCount += 1; + + if (quoteCount >= 2) { + return str.Substring(a, b - a); + } + } + } + return null; + } + + private string GetBracketSet(string str, int start, char open = '{', char close = '}') { + if (start < 0) return null; + + char[] cs = str.ToCharArray(); + int bracketDepth = 0; + int a = -1; + for (int i = start; i < cs.Length; i++) + { + char c = cs[i]; + if (c == open) { + if (a < 0) { + a = i; + } + bracketDepth += 1; + } else if (c == close) { + bracketDepth -= 1; + if (bracketDepth == 0) { + if (i + 1 >= cs.Length) { + return str.Substring(a); + } + return str.Substring(a, i - a + 1); + } else if (bracketDepth < 0) { + return null; + } + } + } + return null; + } + + private IEnumerable GetItemsFromString(string str) { + int startPos = 0; + string itemString = null; + while ((itemString = GetBracketSet(str, startPos)) != null) + { + startPos += itemString.Length + 1; + yield return itemString; + } + } + + private string CurrencyToDisplayStr(Currency currency) { + switch (currency) + { + case Currency.CAD: + return "C"; + case Currency.USD: + return "US"; + default: + throw new InvalidOperationException($"Currency \"{currency}\" is not supported."); + } + } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + client.Dispose(); + handler.Dispose(); + } + disposedValue = true; + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/MultiShop.ShopFramework/Currency.cs b/src/MultiShop.ShopFramework/Currency.cs new file mode 100644 index 0000000..7a40e4b --- /dev/null +++ b/src/MultiShop.ShopFramework/Currency.cs @@ -0,0 +1,8 @@ +namespace MultiShop.ShopFramework +{ + public enum Currency + { + CAD, + USD + } +} \ No newline at end of file diff --git a/src/MultiShop.ShopFramework/IShop.cs b/src/MultiShop.ShopFramework/IShop.cs new file mode 100644 index 0000000..1ace891 --- /dev/null +++ b/src/MultiShop.ShopFramework/IShop.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace MultiShop.ShopFramework +{ + public interface IShop : IDisposable + { + string ShopName { get; } + string ShopDescription { get; } + string ShopModuleAuthor { get; } + + void Initiate(Currency currency); + Task> Search(string query, int maxPage = -1); + } +} \ No newline at end of file diff --git a/src/MultiShop.ShopFramework/MultiShop.ShopFramework.csproj b/src/MultiShop.ShopFramework/MultiShop.ShopFramework.csproj new file mode 100644 index 0000000..f208d30 --- /dev/null +++ b/src/MultiShop.ShopFramework/MultiShop.ShopFramework.csproj @@ -0,0 +1,7 @@ + + + + net5.0 + + + diff --git a/src/MultiShop.ShopFramework/ProductListing.cs b/src/MultiShop.ShopFramework/ProductListing.cs new file mode 100644 index 0000000..a7e38c4 --- /dev/null +++ b/src/MultiShop.ShopFramework/ProductListing.cs @@ -0,0 +1,15 @@ +namespace MultiShop.ShopFramework +{ + public struct ProductListing + { + public float? LowerPrice { get; set; } + public float UpperPrice { get; set; } + public float? Shipping { get; set; } + public string Name { get; set; } + public string URL { get; set; } + public string ImageURL { get; set; } + public float? Rating { get; set; } + public int? PurchaseCount { get; set; } + public int? ReviewCount { get; set; } + } +} \ No newline at end of file diff --git a/src/MultiShop/App.razor b/src/MultiShop/App.razor new file mode 100644 index 0000000..b941644 --- /dev/null +++ b/src/MultiShop/App.razor @@ -0,0 +1,10 @@ + + + + + + +

Sorry, there's nothing at this address.

+
+
+
diff --git a/src/MultiShop/MultiShop.csproj b/src/MultiShop/MultiShop.csproj new file mode 100644 index 0000000..8031445 --- /dev/null +++ b/src/MultiShop/MultiShop.csproj @@ -0,0 +1,18 @@ + + + + net5.0 + + + + + + + + + + + + + + diff --git a/src/MultiShop/Pages/Counter.razor b/src/MultiShop/Pages/Counter.razor new file mode 100644 index 0000000..bd823e5 --- /dev/null +++ b/src/MultiShop/Pages/Counter.razor @@ -0,0 +1,16 @@ +@page "/counter" + +

Counter

+ +

Current count: @currentCount

+ + + +@code { + private int currentCount = 0; + + private void IncrementCount() + { + currentCount++; + } +} diff --git a/src/MultiShop/Pages/FetchData.razor b/src/MultiShop/Pages/FetchData.razor new file mode 100644 index 0000000..4432ee5 --- /dev/null +++ b/src/MultiShop/Pages/FetchData.razor @@ -0,0 +1,55 @@ +@page "/fetchdata" +@inject HttpClient Http + +

Weather forecast

+ +

This component demonstrates fetching data from the server.

+ +@if (forecasts == null) +{ +

Loading...

+} +else +{ + + + + + + + + + + + @foreach (var forecast in forecasts) + { + + + + + + + } + +
DateTemp. (C)Temp. (F)Summary
@forecast.Date.ToShortDateString()@forecast.TemperatureC@forecast.TemperatureF@forecast.Summary
+} + +@code { + private WeatherForecast[] forecasts; + + protected override async Task OnInitializedAsync() + { + forecasts = await Http.GetFromJsonAsync("sample-data/weather.json"); + } + + public class WeatherForecast + { + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public string Summary { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + } +} diff --git a/src/MultiShop/Pages/Index.razor b/src/MultiShop/Pages/Index.razor new file mode 100644 index 0000000..e54d914 --- /dev/null +++ b/src/MultiShop/Pages/Index.razor @@ -0,0 +1,7 @@ +@page "/" + +

Hello, world!

+ +Welcome to your new app. + + diff --git a/src/MultiShop/Program.cs b/src/MultiShop/Program.cs new file mode 100644 index 0000000..8a1ee24 --- /dev/null +++ b/src/MultiShop/Program.cs @@ -0,0 +1,27 @@ +using System; +using System.Net.Http; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Text; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using SimpleLogger; + +namespace MultiShop +{ + public class Program + { + public static async Task Main(string[] args) + { + Logger.AddLogListener(new ConsoleLogReceiver()); + var builder = WebAssemblyHostBuilder.CreateDefault(args); + builder.RootComponents.Add("#app"); + + builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); + + await builder.Build().RunAsync(); + } + } +} diff --git a/src/MultiShop/Properties/launchSettings.json b/src/MultiShop/Properties/launchSettings.json new file mode 100644 index 0000000..4b243ab --- /dev/null +++ b/src/MultiShop/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:46072", + "sslPort": 44317 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "MultiShop": { + "commandName": "Project", + "dotnetRunMessages": "true", + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/MultiShop/Shared/MainLayout.razor b/src/MultiShop/Shared/MainLayout.razor new file mode 100644 index 0000000..a76e097 --- /dev/null +++ b/src/MultiShop/Shared/MainLayout.razor @@ -0,0 +1,17 @@ +@inherits LayoutComponentBase + +
+ + +
+
+ About +
+ +
+ @Body +
+
+
diff --git a/src/MultiShop/Shared/MainLayout.razor.css b/src/MultiShop/Shared/MainLayout.razor.css new file mode 100644 index 0000000..43c355a --- /dev/null +++ b/src/MultiShop/Shared/MainLayout.razor.css @@ -0,0 +1,70 @@ +.page { + position: relative; + display: flex; + flex-direction: column; +} + +.main { + flex: 1; +} + +.sidebar { + background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); +} + +.top-row { + background-color: #f7f7f7; + border-bottom: 1px solid #d6d5d5; + justify-content: flex-end; + height: 3.5rem; + display: flex; + align-items: center; +} + + .top-row ::deep a, .top-row .btn-link { + white-space: nowrap; + margin-left: 1.5rem; + } + + .top-row a:first-child { + overflow: hidden; + text-overflow: ellipsis; + } + +@media (max-width: 640.98px) { + .top-row:not(.auth) { + display: none; + } + + .top-row.auth { + justify-content: space-between; + } + + .top-row a, .top-row .btn-link { + margin-left: 0; + } +} + +@media (min-width: 641px) { + .page { + flex-direction: row; + } + + .sidebar { + width: 250px; + height: 100vh; + position: sticky; + top: 0; + } + + .top-row { + position: sticky; + top: 0; + z-index: 1; + } + + .main > div { + padding-left: 2rem !important; + padding-right: 1.5rem !important; + } +} diff --git a/src/MultiShop/Shared/NavMenu.razor b/src/MultiShop/Shared/NavMenu.razor new file mode 100644 index 0000000..667fc4c --- /dev/null +++ b/src/MultiShop/Shared/NavMenu.razor @@ -0,0 +1,37 @@ + + +
+ +
+ +@code { + private bool collapseNavMenu = true; + + private string NavMenuCssClass => collapseNavMenu ? "collapse" : null; + + private void ToggleNavMenu() + { + collapseNavMenu = !collapseNavMenu; + } +} diff --git a/src/MultiShop/Shared/NavMenu.razor.css b/src/MultiShop/Shared/NavMenu.razor.css new file mode 100644 index 0000000..acc5f9f --- /dev/null +++ b/src/MultiShop/Shared/NavMenu.razor.css @@ -0,0 +1,62 @@ +.navbar-toggler { + background-color: rgba(255, 255, 255, 0.1); +} + +.top-row { + height: 3.5rem; + background-color: rgba(0,0,0,0.4); +} + +.navbar-brand { + font-size: 1.1rem; +} + +.oi { + width: 2rem; + font-size: 1.1rem; + vertical-align: text-top; + top: -2px; +} + +.nav-item { + font-size: 0.9rem; + padding-bottom: 0.5rem; +} + + .nav-item:first-of-type { + padding-top: 1rem; + } + + .nav-item:last-of-type { + padding-bottom: 1rem; + } + + .nav-item ::deep a { + color: #d7d7d7; + border-radius: 4px; + height: 3rem; + display: flex; + align-items: center; + line-height: 3rem; + } + +.nav-item ::deep a.active { + background-color: rgba(255,255,255,0.25); + color: white; +} + +.nav-item ::deep a:hover { + background-color: rgba(255,255,255,0.1); + color: white; +} + +@media (min-width: 641px) { + .navbar-toggler { + display: none; + } + + .collapse { + /* Never collapse the sidebar for wide screens */ + display: block; + } +} diff --git a/src/MultiShop/Shared/SurveyPrompt.razor b/src/MultiShop/Shared/SurveyPrompt.razor new file mode 100644 index 0000000..1ec40da --- /dev/null +++ b/src/MultiShop/Shared/SurveyPrompt.razor @@ -0,0 +1,16 @@ + + +@code { + // Demonstrates how a parent component can supply parameters + [Parameter] + public string Title { get; set; } +} diff --git a/src/MultiShop/Shops/LoaderContext.cs b/src/MultiShop/Shops/LoaderContext.cs new file mode 100644 index 0000000..1669393 --- /dev/null +++ b/src/MultiShop/Shops/LoaderContext.cs @@ -0,0 +1,28 @@ +using System; +using System.Reflection; +using System.Runtime.Loader; + +// from https://docs.microsoft.com/en-us/dotnet/core/tutorials/creating-app-with-plugin-support#load-plugins +namespace MultiShop.Shops +{ + public class LoaderContext : AssemblyLoadContext + { + private AssemblyDependencyResolver resolver; + public LoaderContext(string path) + { + resolver = new AssemblyDependencyResolver(path); + } + + protected override Assembly Load(AssemblyName assemblyName) + { + string path = resolver.ResolveAssemblyToPath(assemblyName); + return path == null ? LoadFromAssemblyPath(path) : null; + } + + protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) + { + string path = resolver.ResolveUnmanagedDllToPath(unmanagedDllName); + return path == null ? LoadUnmanagedDllFromPath(path) : IntPtr.Zero; + } + } +} \ No newline at end of file diff --git a/src/MultiShop/Shops/ShopLoader.cs b/src/MultiShop/Shops/ShopLoader.cs new file mode 100644 index 0000000..4caa68e --- /dev/null +++ b/src/MultiShop/Shops/ShopLoader.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using MultiShop.ShopFramework; + +namespace MultiShop.Shops +{ + public class ShopLoader + { + public IEnumerable LoadShops(string shop) { + return InstantiateShops(LoadAssembly(shop)); + } + + public IReadOnlyDictionary> LoadAllShops(IEnumerable directories) { + Dictionary> res = new Dictionary>(); + foreach (string dir in directories) + { + res.Add(dir, LoadShops(dir)); + } + return res; + } + + private IEnumerable InstantiateShops(Assembly assembly) { + foreach (Type type in assembly.GetTypes()) + { + if (typeof(IShop).IsAssignableFrom(type)) { + IShop shop = Activator.CreateInstance(type) as IShop; + if (shop != null) { + yield return shop; + } + } + } + } + + private Assembly LoadAssembly(string path) { + LoaderContext context = new LoaderContext(path); + return context.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(path))); + } + } +} \ No newline at end of file diff --git a/src/MultiShop/_Imports.razor b/src/MultiShop/_Imports.razor new file mode 100644 index 0000000..eb007ac --- /dev/null +++ b/src/MultiShop/_Imports.razor @@ -0,0 +1,10 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.AspNetCore.Components.WebAssembly.Http +@using Microsoft.JSInterop +@using MultiShop +@using MultiShop.Shared diff --git a/src/MultiShop/wwwroot/css/app.css b/src/MultiShop/wwwroot/css/app.css new file mode 100644 index 0000000..caebf2a --- /dev/null +++ b/src/MultiShop/wwwroot/css/app.css @@ -0,0 +1,50 @@ +@import url('open-iconic/font/css/open-iconic-bootstrap.min.css'); + +html, body { + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +a, .btn-link { + color: #0366d6; +} + +.btn-primary { + color: #fff; + background-color: #1b6ec2; + border-color: #1861ac; +} + +.content { + padding-top: 1.1rem; +} + +.valid.modified:not([type=checkbox]) { + outline: 1px solid #26b050; +} + +.invalid { + outline: 1px solid red; +} + +.validation-message { + color: red; +} + +#blazor-error-ui { + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + display: none; + left: 0; + padding: 0.6rem 1.25rem 0.7rem 1.25rem; + position: fixed; + width: 100%; + z-index: 1000; +} + + #blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; + } diff --git a/src/MultiShop/wwwroot/css/bootstrap/bootstrap.min.css b/src/MultiShop/wwwroot/css/bootstrap/bootstrap.min.css new file mode 100644 index 0000000..92e3fe8 --- /dev/null +++ b/src/MultiShop/wwwroot/css/bootstrap/bootstrap.min.css @@ -0,0 +1,7 @@ +/*! + * Bootstrap v4.3.1 (https://getbootstrap.com/) + * Copyright 2011-2019 The Bootstrap Authors + * Copyright 2011-2019 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */:root{--blue:#007bff;--indigo:#6610f2;--purple:#6f42c1;--pink:#e83e8c;--red:#dc3545;--orange:#fd7e14;--yellow:#ffc107;--green:#28a745;--teal:#20c997;--cyan:#17a2b8;--white:#fff;--gray:#6c757d;--gray-dark:#343a40;--primary:#007bff;--secondary:#6c757d;--success:#28a745;--info:#17a2b8;--warning:#ffc107;--danger:#dc3545;--light:#f8f9fa;--dark:#343a40;--breakpoint-xs:0;--breakpoint-sm:576px;--breakpoint-md:768px;--breakpoint-lg:992px;--breakpoint-xl:1200px;--font-family-sans-serif:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-family-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace}*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:2.5rem}.h2,h2{font-size:2rem}.h3,h3{font-size:1.75rem}.h4,h4{font-size:1.5rem}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:6rem;font-weight:300;line-height:1.2}.display-2{font-size:5.5rem;font-weight:300;line-height:1.2}.display-3{font-size:4.5rem;font-weight:300;line-height:1.2}.display-4{font-size:3.5rem;font-weight:300;line-height:1.2}hr{margin-top:1rem;margin-bottom:1rem;border:0;border-top:1px solid rgba(0,0,0,.1)}.small,small{font-size:80%;font-weight:400}.mark,mark{padding:.2em;background-color:#fcf8e3}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:90%;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote-footer{display:block;font-size:80%;color:#6c757d}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:90%;color:#6c757d}code{font-size:87.5%;color:#e83e8c;word-break:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:87.5%;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:100%;font-weight:700}pre{display:block;font-size:87.5%;color:#212529}pre code{font-size:inherit;color:inherit;word-break:normal}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:576px){.container{max-width:540px}}@media (min-width:768px){.container{max-width:720px}}@media (min-width:992px){.container{max-width:960px}}@media (min-width:1200px){.container{max-width:1140px}}.container-fluid{width:100%;padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-15px;margin-left:-15px}.no-gutters{margin-right:0;margin-left:0}.no-gutters>.col,.no-gutters>[class*=col-]{padding-right:0;padding-left:0}.col,.col-1,.col-10,.col-11,.col-12,.col-2,.col-3,.col-4,.col-5,.col-6,.col-7,.col-8,.col-9,.col-auto,.col-lg,.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-auto,.col-md,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-auto,.col-sm,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-auto,.col-xl,.col-xl-1,.col-xl-10,.col-xl-11,.col-xl-12,.col-xl-2,.col-xl-3,.col-xl-4,.col-xl-5,.col-xl-6,.col-xl-7,.col-xl-8,.col-xl-9,.col-xl-auto{position:relative;width:100%;padding-right:15px;padding-left:15px}.col{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-first{-ms-flex-order:-1;order:-1}.order-last{-ms-flex-order:13;order:13}.order-0{-ms-flex-order:0;order:0}.order-1{-ms-flex-order:1;order:1}.order-2{-ms-flex-order:2;order:2}.order-3{-ms-flex-order:3;order:3}.order-4{-ms-flex-order:4;order:4}.order-5{-ms-flex-order:5;order:5}.order-6{-ms-flex-order:6;order:6}.order-7{-ms-flex-order:7;order:7}.order-8{-ms-flex-order:8;order:8}.order-9{-ms-flex-order:9;order:9}.order-10{-ms-flex-order:10;order:10}.order-11{-ms-flex-order:11;order:11}.order-12{-ms-flex-order:12;order:12}.offset-1{margin-left:8.333333%}.offset-2{margin-left:16.666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.333333%}.offset-5{margin-left:41.666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.333333%}.offset-8{margin-left:66.666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.333333%}.offset-11{margin-left:91.666667%}@media (min-width:576px){.col-sm{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-sm-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-sm-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-sm-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-sm-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-sm-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-sm-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-sm-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-sm-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-sm-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-sm-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-sm-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-sm-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-sm-first{-ms-flex-order:-1;order:-1}.order-sm-last{-ms-flex-order:13;order:13}.order-sm-0{-ms-flex-order:0;order:0}.order-sm-1{-ms-flex-order:1;order:1}.order-sm-2{-ms-flex-order:2;order:2}.order-sm-3{-ms-flex-order:3;order:3}.order-sm-4{-ms-flex-order:4;order:4}.order-sm-5{-ms-flex-order:5;order:5}.order-sm-6{-ms-flex-order:6;order:6}.order-sm-7{-ms-flex-order:7;order:7}.order-sm-8{-ms-flex-order:8;order:8}.order-sm-9{-ms-flex-order:9;order:9}.order-sm-10{-ms-flex-order:10;order:10}.order-sm-11{-ms-flex-order:11;order:11}.order-sm-12{-ms-flex-order:12;order:12}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.333333%}.offset-sm-2{margin-left:16.666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.333333%}.offset-sm-5{margin-left:41.666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.333333%}.offset-sm-8{margin-left:66.666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.333333%}.offset-sm-11{margin-left:91.666667%}}@media (min-width:768px){.col-md{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-md-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-md-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-md-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-md-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-md-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-md-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-md-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-md-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-md-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-md-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-md-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-md-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-md-first{-ms-flex-order:-1;order:-1}.order-md-last{-ms-flex-order:13;order:13}.order-md-0{-ms-flex-order:0;order:0}.order-md-1{-ms-flex-order:1;order:1}.order-md-2{-ms-flex-order:2;order:2}.order-md-3{-ms-flex-order:3;order:3}.order-md-4{-ms-flex-order:4;order:4}.order-md-5{-ms-flex-order:5;order:5}.order-md-6{-ms-flex-order:6;order:6}.order-md-7{-ms-flex-order:7;order:7}.order-md-8{-ms-flex-order:8;order:8}.order-md-9{-ms-flex-order:9;order:9}.order-md-10{-ms-flex-order:10;order:10}.order-md-11{-ms-flex-order:11;order:11}.order-md-12{-ms-flex-order:12;order:12}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.333333%}.offset-md-2{margin-left:16.666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.333333%}.offset-md-5{margin-left:41.666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.333333%}.offset-md-8{margin-left:66.666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.333333%}.offset-md-11{margin-left:91.666667%}}@media (min-width:992px){.col-lg{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-lg-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-lg-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-lg-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-lg-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-lg-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-lg-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-lg-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-lg-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-lg-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-lg-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-lg-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-lg-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-lg-first{-ms-flex-order:-1;order:-1}.order-lg-last{-ms-flex-order:13;order:13}.order-lg-0{-ms-flex-order:0;order:0}.order-lg-1{-ms-flex-order:1;order:1}.order-lg-2{-ms-flex-order:2;order:2}.order-lg-3{-ms-flex-order:3;order:3}.order-lg-4{-ms-flex-order:4;order:4}.order-lg-5{-ms-flex-order:5;order:5}.order-lg-6{-ms-flex-order:6;order:6}.order-lg-7{-ms-flex-order:7;order:7}.order-lg-8{-ms-flex-order:8;order:8}.order-lg-9{-ms-flex-order:9;order:9}.order-lg-10{-ms-flex-order:10;order:10}.order-lg-11{-ms-flex-order:11;order:11}.order-lg-12{-ms-flex-order:12;order:12}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.333333%}.offset-lg-2{margin-left:16.666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.333333%}.offset-lg-5{margin-left:41.666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.333333%}.offset-lg-8{margin-left:66.666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.333333%}.offset-lg-11{margin-left:91.666667%}}@media (min-width:1200px){.col-xl{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;max-width:100%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto;max-width:100%}.col-xl-1{-ms-flex:0 0 8.333333%;flex:0 0 8.333333%;max-width:8.333333%}.col-xl-2{-ms-flex:0 0 16.666667%;flex:0 0 16.666667%;max-width:16.666667%}.col-xl-3{-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}.col-xl-4{-ms-flex:0 0 33.333333%;flex:0 0 33.333333%;max-width:33.333333%}.col-xl-5{-ms-flex:0 0 41.666667%;flex:0 0 41.666667%;max-width:41.666667%}.col-xl-6{-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.col-xl-7{-ms-flex:0 0 58.333333%;flex:0 0 58.333333%;max-width:58.333333%}.col-xl-8{-ms-flex:0 0 66.666667%;flex:0 0 66.666667%;max-width:66.666667%}.col-xl-9{-ms-flex:0 0 75%;flex:0 0 75%;max-width:75%}.col-xl-10{-ms-flex:0 0 83.333333%;flex:0 0 83.333333%;max-width:83.333333%}.col-xl-11{-ms-flex:0 0 91.666667%;flex:0 0 91.666667%;max-width:91.666667%}.col-xl-12{-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.order-xl-first{-ms-flex-order:-1;order:-1}.order-xl-last{-ms-flex-order:13;order:13}.order-xl-0{-ms-flex-order:0;order:0}.order-xl-1{-ms-flex-order:1;order:1}.order-xl-2{-ms-flex-order:2;order:2}.order-xl-3{-ms-flex-order:3;order:3}.order-xl-4{-ms-flex-order:4;order:4}.order-xl-5{-ms-flex-order:5;order:5}.order-xl-6{-ms-flex-order:6;order:6}.order-xl-7{-ms-flex-order:7;order:7}.order-xl-8{-ms-flex-order:8;order:8}.order-xl-9{-ms-flex-order:9;order:9}.order-xl-10{-ms-flex-order:10;order:10}.order-xl-11{-ms-flex-order:11;order:11}.order-xl-12{-ms-flex-order:12;order:12}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.333333%}.offset-xl-2{margin-left:16.666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.333333%}.offset-xl-5{margin-left:41.666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.333333%}.offset-xl-8{margin-left:66.666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.333333%}.offset-xl-11{margin-left:91.666667%}}.table{width:100%;margin-bottom:1rem;color:#212529}.table td,.table th{padding:.75rem;vertical-align:top;border-top:1px solid #dee2e6}.table thead th{vertical-align:bottom;border-bottom:2px solid #dee2e6}.table tbody+tbody{border-top:2px solid #dee2e6}.table-sm td,.table-sm th{padding:.3rem}.table-bordered{border:1px solid #dee2e6}.table-bordered td,.table-bordered th{border:1px solid #dee2e6}.table-bordered thead td,.table-bordered thead th{border-bottom-width:2px}.table-borderless tbody+tbody,.table-borderless td,.table-borderless th,.table-borderless thead th{border:0}.table-striped tbody tr:nth-of-type(odd){background-color:rgba(0,0,0,.05)}.table-hover tbody tr:hover{color:#212529;background-color:rgba(0,0,0,.075)}.table-primary,.table-primary>td,.table-primary>th{background-color:#b8daff}.table-primary tbody+tbody,.table-primary td,.table-primary th,.table-primary thead th{border-color:#7abaff}.table-hover .table-primary:hover{background-color:#9fcdff}.table-hover .table-primary:hover>td,.table-hover .table-primary:hover>th{background-color:#9fcdff}.table-secondary,.table-secondary>td,.table-secondary>th{background-color:#d6d8db}.table-secondary tbody+tbody,.table-secondary td,.table-secondary th,.table-secondary thead th{border-color:#b3b7bb}.table-hover .table-secondary:hover{background-color:#c8cbcf}.table-hover .table-secondary:hover>td,.table-hover .table-secondary:hover>th{background-color:#c8cbcf}.table-success,.table-success>td,.table-success>th{background-color:#c3e6cb}.table-success tbody+tbody,.table-success td,.table-success th,.table-success thead th{border-color:#8fd19e}.table-hover .table-success:hover{background-color:#b1dfbb}.table-hover .table-success:hover>td,.table-hover .table-success:hover>th{background-color:#b1dfbb}.table-info,.table-info>td,.table-info>th{background-color:#bee5eb}.table-info tbody+tbody,.table-info td,.table-info th,.table-info thead th{border-color:#86cfda}.table-hover .table-info:hover{background-color:#abdde5}.table-hover .table-info:hover>td,.table-hover .table-info:hover>th{background-color:#abdde5}.table-warning,.table-warning>td,.table-warning>th{background-color:#ffeeba}.table-warning tbody+tbody,.table-warning td,.table-warning th,.table-warning thead th{border-color:#ffdf7e}.table-hover .table-warning:hover{background-color:#ffe8a1}.table-hover .table-warning:hover>td,.table-hover .table-warning:hover>th{background-color:#ffe8a1}.table-danger,.table-danger>td,.table-danger>th{background-color:#f5c6cb}.table-danger tbody+tbody,.table-danger td,.table-danger th,.table-danger thead th{border-color:#ed969e}.table-hover .table-danger:hover{background-color:#f1b0b7}.table-hover .table-danger:hover>td,.table-hover .table-danger:hover>th{background-color:#f1b0b7}.table-light,.table-light>td,.table-light>th{background-color:#fdfdfe}.table-light tbody+tbody,.table-light td,.table-light th,.table-light thead th{border-color:#fbfcfc}.table-hover .table-light:hover{background-color:#ececf6}.table-hover .table-light:hover>td,.table-hover .table-light:hover>th{background-color:#ececf6}.table-dark,.table-dark>td,.table-dark>th{background-color:#c6c8ca}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#95999c}.table-hover .table-dark:hover{background-color:#b9bbbe}.table-hover .table-dark:hover>td,.table-hover .table-dark:hover>th{background-color:#b9bbbe}.table-active,.table-active>td,.table-active>th{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover{background-color:rgba(0,0,0,.075)}.table-hover .table-active:hover>td,.table-hover .table-active:hover>th{background-color:rgba(0,0,0,.075)}.table .thead-dark th{color:#fff;background-color:#343a40;border-color:#454d55}.table .thead-light th{color:#495057;background-color:#e9ecef;border-color:#dee2e6}.table-dark{color:#fff;background-color:#343a40}.table-dark td,.table-dark th,.table-dark thead th{border-color:#454d55}.table-dark.table-bordered{border:0}.table-dark.table-striped tbody tr:nth-of-type(odd){background-color:rgba(255,255,255,.05)}.table-dark.table-hover tbody tr:hover{color:#fff;background-color:rgba(255,255,255,.075)}@media (max-width:575.98px){.table-responsive-sm{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-sm>.table-bordered{border:0}}@media (max-width:767.98px){.table-responsive-md{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-md>.table-bordered{border:0}}@media (max-width:991.98px){.table-responsive-lg{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-lg>.table-bordered{border:0}}@media (max-width:1199.98px){.table-responsive-xl{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive-xl>.table-bordered{border:0}}.table-responsive{display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-responsive>.table-bordered{border:0}.form-control{display:block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control::-ms-expand{background-color:transparent;border:0}.form-control:focus{color:#495057;background-color:#fff;border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.form-control::-webkit-input-placeholder{color:#6c757d;opacity:1}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control:-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::-ms-input-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}select.form-control:focus::-ms-value{color:#495057;background-color:#fff}.form-control-file,.form-control-range{display:block;width:100%}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem;line-height:1.5}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem;line-height:1.5}.form-control-plaintext{display:block;width:100%;padding-top:.375rem;padding-bottom:.375rem;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.form-control-lg{height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}select.form-control[multiple],select.form-control[size]{height:auto}textarea.form-control{height:auto}.form-group{margin-bottom:1rem}.form-text{display:block;margin-top:.25rem}.form-row{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-right:-5px;margin-left:-5px}.form-row>.col,.form-row>[class*=col-]{padding-right:5px;padding-left:5px}.form-check{position:relative;display:block;padding-left:1.25rem}.form-check-input{position:absolute;margin-top:.3rem;margin-left:-1.25rem}.form-check-input:disabled~.form-check-label{color:#6c757d}.form-check-label{margin-bottom:0}.form-check-inline{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center;padding-left:0;margin-right:.75rem}.form-check-inline .form-check-input{position:static;margin-top:0;margin-right:.3125rem;margin-left:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#28a745}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(40,167,69,.9);border-radius:.25rem}.form-control.is-valid,.was-validated .form-control:valid{border-color:#28a745;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:center right calc(.375em + .1875rem);background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.form-control.is-valid~.valid-feedback,.form-control.is-valid~.valid-tooltip,.was-validated .form-control:valid~.valid-feedback,.was-validated .form-control:valid~.valid-tooltip{display:block}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-valid,.was-validated .custom-select:valid{border-color:#28a745;padding-right:calc((1em + .75rem) * 3 / 4 + 1.75rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-valid:focus,.was-validated .custom-select:valid:focus{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-select.is-valid~.valid-feedback,.custom-select.is-valid~.valid-tooltip,.was-validated .custom-select:valid~.valid-feedback,.was-validated .custom-select:valid~.valid-tooltip{display:block}.form-control-file.is-valid~.valid-feedback,.form-control-file.is-valid~.valid-tooltip,.was-validated .form-control-file:valid~.valid-feedback,.was-validated .form-control-file:valid~.valid-tooltip{display:block}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#28a745}.form-check-input.is-valid~.valid-feedback,.form-check-input.is-valid~.valid-tooltip,.was-validated .form-check-input:valid~.valid-feedback,.was-validated .form-check-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid~.custom-control-label,.was-validated .custom-control-input:valid~.custom-control-label{color:#28a745}.custom-control-input.is-valid~.custom-control-label::before,.was-validated .custom-control-input:valid~.custom-control-label::before{border-color:#28a745}.custom-control-input.is-valid~.valid-feedback,.custom-control-input.is-valid~.valid-tooltip,.was-validated .custom-control-input:valid~.valid-feedback,.was-validated .custom-control-input:valid~.valid-tooltip{display:block}.custom-control-input.is-valid:checked~.custom-control-label::before,.was-validated .custom-control-input:valid:checked~.custom-control-label::before{border-color:#34ce57;background-color:#34ce57}.custom-control-input.is-valid:focus~.custom-control-label::before,.was-validated .custom-control-input:valid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.custom-control-input.is-valid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:valid:focus:not(:checked)~.custom-control-label::before{border-color:#28a745}.custom-file-input.is-valid~.custom-file-label,.was-validated .custom-file-input:valid~.custom-file-label{border-color:#28a745}.custom-file-input.is-valid~.valid-feedback,.custom-file-input.is-valid~.valid-tooltip,.was-validated .custom-file-input:valid~.valid-feedback,.was-validated .custom-file-input:valid~.valid-tooltip{display:block}.custom-file-input.is-valid:focus~.custom-file-label,.was-validated .custom-file-input:valid:focus~.custom-file-label{border-color:#28a745;box-shadow:0 0 0 .2rem rgba(40,167,69,.25)}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:80%;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;line-height:1.5;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23dc3545' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23dc3545' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E");background-repeat:no-repeat;background-position:center right calc(.375em + .1875rem);background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-control.is-invalid~.invalid-feedback,.form-control.is-invalid~.invalid-tooltip,.was-validated .form-control:invalid~.invalid-feedback,.was-validated .form-control:invalid~.invalid-tooltip{display:block}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.custom-select.is-invalid,.was-validated .custom-select:invalid{border-color:#dc3545;padding-right:calc((1em + .75rem) * 3 / 4 + 1.75rem);background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px,url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23dc3545' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23dc3545' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E") #fff no-repeat center right 1.75rem/calc(.75em + .375rem) calc(.75em + .375rem)}.custom-select.is-invalid:focus,.was-validated .custom-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-select.is-invalid~.invalid-feedback,.custom-select.is-invalid~.invalid-tooltip,.was-validated .custom-select:invalid~.invalid-feedback,.was-validated .custom-select:invalid~.invalid-tooltip{display:block}.form-control-file.is-invalid~.invalid-feedback,.form-control-file.is-invalid~.invalid-tooltip,.was-validated .form-control-file:invalid~.invalid-feedback,.was-validated .form-control-file:invalid~.invalid-tooltip{display:block}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-input.is-invalid~.invalid-feedback,.form-check-input.is-invalid~.invalid-tooltip,.was-validated .form-check-input:invalid~.invalid-feedback,.was-validated .form-check-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid~.custom-control-label,.was-validated .custom-control-input:invalid~.custom-control-label{color:#dc3545}.custom-control-input.is-invalid~.custom-control-label::before,.was-validated .custom-control-input:invalid~.custom-control-label::before{border-color:#dc3545}.custom-control-input.is-invalid~.invalid-feedback,.custom-control-input.is-invalid~.invalid-tooltip,.was-validated .custom-control-input:invalid~.invalid-feedback,.was-validated .custom-control-input:invalid~.invalid-tooltip{display:block}.custom-control-input.is-invalid:checked~.custom-control-label::before,.was-validated .custom-control-input:invalid:checked~.custom-control-label::before{border-color:#e4606d;background-color:#e4606d}.custom-control-input.is-invalid:focus~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.custom-control-input.is-invalid:focus:not(:checked)~.custom-control-label::before,.was-validated .custom-control-input:invalid:focus:not(:checked)~.custom-control-label::before{border-color:#dc3545}.custom-file-input.is-invalid~.custom-file-label,.was-validated .custom-file-input:invalid~.custom-file-label{border-color:#dc3545}.custom-file-input.is-invalid~.invalid-feedback,.custom-file-input.is-invalid~.invalid-tooltip,.was-validated .custom-file-input:invalid~.invalid-feedback,.was-validated .custom-file-input:invalid~.invalid-tooltip{display:block}.custom-file-input.is-invalid:focus~.custom-file-label,.was-validated .custom-file-input:invalid:focus~.custom-file-label{border-color:#dc3545;box-shadow:0 0 0 .2rem rgba(220,53,69,.25)}.form-inline{display:-ms-flexbox;display:flex;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center}.form-inline .form-check{width:100%}@media (min-width:576px){.form-inline label{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;margin-bottom:0}.form-inline .form-group{display:-ms-flexbox;display:flex;-ms-flex:0 0 auto;flex:0 0 auto;-ms-flex-flow:row wrap;flex-flow:row wrap;-ms-flex-align:center;align-items:center;margin-bottom:0}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-plaintext{display:inline-block}.form-inline .custom-select,.form-inline .input-group{width:auto}.form-inline .form-check{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:auto;padding-left:0}.form-inline .form-check-input{position:relative;-ms-flex-negative:0;flex-shrink:0;margin-top:0;margin-right:.25rem;margin-left:0}.form-inline .custom-control{-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center}.form-inline .custom-control-label{margin-bottom:0}}.btn{display:inline-block;font-weight:400;color:#212529;text-align:center;vertical-align:middle;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;line-height:1.5;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529;text-decoration:none}.btn.focus,.btn:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.btn.disabled,.btn:disabled{opacity:.65}a.btn.disabled,fieldset:disabled a.btn{pointer-events:none}.btn-primary{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:hover{color:#fff;background-color:#0069d9;border-color:#0062cc}.btn-primary.focus,.btn-primary:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#007bff;border-color:#007bff}.btn-primary:not(:disabled):not(.disabled).active,.btn-primary:not(:disabled):not(.disabled):active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0062cc;border-color:#005cbf}.btn-primary:not(:disabled):not(.disabled).active:focus,.btn-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(38,143,255,.5)}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5a6268;border-color:#545b62}.btn-secondary.focus,.btn-secondary:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:not(:disabled):not(.disabled).active,.btn-secondary:not(:disabled):not(.disabled):active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#545b62;border-color:#4e555b}.btn-secondary:not(:disabled):not(.disabled).active:focus,.btn-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(130,138,145,.5)}.btn-success{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:hover{color:#fff;background-color:#218838;border-color:#1e7e34}.btn-success.focus,.btn-success:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#28a745;border-color:#28a745}.btn-success:not(:disabled):not(.disabled).active,.btn-success:not(:disabled):not(.disabled):active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#1e7e34;border-color:#1c7430}.btn-success:not(:disabled):not(.disabled).active:focus,.btn-success:not(:disabled):not(.disabled):active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(72,180,97,.5)}.btn-info{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:hover{color:#fff;background-color:#138496;border-color:#117a8b}.btn-info.focus,.btn-info:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-info:not(:disabled):not(.disabled).active,.btn-info:not(:disabled):not(.disabled):active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#117a8b;border-color:#10707f}.btn-info:not(:disabled):not(.disabled).active:focus,.btn-info:not(:disabled):not(.disabled):active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(58,176,195,.5)}.btn-warning{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#212529;background-color:#e0a800;border-color:#d39e00}.btn-warning.focus,.btn-warning:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-warning:not(:disabled):not(.disabled).active,.btn-warning:not(:disabled):not(.disabled):active,.show>.btn-warning.dropdown-toggle{color:#212529;background-color:#d39e00;border-color:#c69500}.btn-warning:not(:disabled):not(.disabled).active:focus,.btn-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(222,170,12,.5)}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#c82333;border-color:#bd2130}.btn-danger.focus,.btn-danger:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:not(:disabled):not(.disabled).active,.btn-danger:not(:disabled):not(.disabled):active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#bd2130;border-color:#b21f2d}.btn-danger:not(:disabled):not(.disabled).active:focus,.btn-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(225,83,97,.5)}.btn-light{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#212529;background-color:#e2e6ea;border-color:#dae0e5}.btn-light.focus,.btn-light:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-light.disabled,.btn-light:disabled{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:not(:disabled):not(.disabled).active,.btn-light:not(:disabled):not(.disabled):active,.show>.btn-light.dropdown-toggle{color:#212529;background-color:#dae0e5;border-color:#d3d9df}.btn-light:not(:disabled):not(.disabled).active:focus,.btn-light:not(:disabled):not(.disabled):active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(216,217,219,.5)}.btn-dark{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:hover{color:#fff;background-color:#23272b;border-color:#1d2124}.btn-dark.focus,.btn-dark:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#343a40;border-color:#343a40}.btn-dark:not(:disabled):not(.disabled).active,.btn-dark:not(:disabled):not(.disabled):active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1d2124;border-color:#171a1d}.btn-dark:not(:disabled):not(.disabled).active:focus,.btn-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(82,88,93,.5)}.btn-outline-primary{color:#007bff;border-color:#007bff}.btn-outline-primary:hover{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary.focus,.btn-outline-primary:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#007bff;background-color:transparent}.btn-outline-primary:not(:disabled):not(.disabled).active,.btn-outline-primary:not(:disabled):not(.disabled):active,.show>.btn-outline-primary.dropdown-toggle{color:#fff;background-color:#007bff;border-color:#007bff}.btn-outline-primary:not(:disabled):not(.disabled).active:focus,.btn-outline-primary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-primary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary.focus,.btn-outline-secondary:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-secondary:not(:disabled):not(.disabled).active,.btn-outline-secondary:not(:disabled):not(.disabled):active,.show>.btn-outline-secondary.dropdown-toggle{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-outline-secondary:not(:disabled):not(.disabled).active:focus,.btn-outline-secondary:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.btn-outline-success{color:#28a745;border-color:#28a745}.btn-outline-success:hover{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success.focus,.btn-outline-success:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#28a745;background-color:transparent}.btn-outline-success:not(:disabled):not(.disabled).active,.btn-outline-success:not(:disabled):not(.disabled):active,.show>.btn-outline-success.dropdown-toggle{color:#fff;background-color:#28a745;border-color:#28a745}.btn-outline-success:not(:disabled):not(.disabled).active:focus,.btn-outline-success:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-success.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.btn-outline-info{color:#17a2b8;border-color:#17a2b8}.btn-outline-info:hover{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info.focus,.btn-outline-info:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#17a2b8;background-color:transparent}.btn-outline-info:not(:disabled):not(.disabled).active,.btn-outline-info:not(:disabled):not(.disabled):active,.show>.btn-outline-info.dropdown-toggle{color:#fff;background-color:#17a2b8;border-color:#17a2b8}.btn-outline-info:not(:disabled):not(.disabled).active:focus,.btn-outline-info:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-info.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning.focus,.btn-outline-warning:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-warning:not(:disabled):not(.disabled).active,.btn-outline-warning:not(:disabled):not(.disabled):active,.show>.btn-outline-warning.dropdown-toggle{color:#212529;background-color:#ffc107;border-color:#ffc107}.btn-outline-warning:not(:disabled):not(.disabled).active:focus,.btn-outline-warning:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-warning.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger.focus,.btn-outline-danger:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-danger:not(:disabled):not(.disabled).active,.btn-outline-danger:not(:disabled):not(.disabled):active,.show>.btn-outline-danger.dropdown-toggle{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-outline-danger:not(:disabled):not(.disabled).active:focus,.btn-outline-danger:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-danger.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light.focus,.btn-outline-light:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-light:not(:disabled):not(.disabled).active,.btn-outline-light:not(:disabled):not(.disabled):active,.show>.btn-outline-light.dropdown-toggle{color:#212529;background-color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:not(:disabled):not(.disabled).active:focus,.btn-outline-light:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-light.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.btn-outline-dark{color:#343a40;border-color:#343a40}.btn-outline-dark:hover{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark.focus,.btn-outline-dark:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#343a40;background-color:transparent}.btn-outline-dark:not(:disabled):not(.disabled).active,.btn-outline-dark:not(:disabled):not(.disabled):active,.show>.btn-outline-dark.dropdown-toggle{color:#fff;background-color:#343a40;border-color:#343a40}.btn-outline-dark:not(:disabled):not(.disabled).active:focus,.btn-outline-dark:not(:disabled):not(.disabled):active:focus,.show>.btn-outline-dark.dropdown-toggle:focus{box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.btn-link{font-weight:400;color:#007bff;text-decoration:none}.btn-link:hover{color:#0056b3;text-decoration:underline}.btn-link.focus,.btn-link:focus{text-decoration:underline;box-shadow:none}.btn-link.disabled,.btn-link:disabled{color:#6c757d;pointer-events:none}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:.5rem}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{position:relative;height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropleft,.dropright,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:10rem;padding:.5rem 0;margin:.125rem 0 0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu-left{right:auto;left:0}.dropdown-menu-right{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-left{right:auto;left:0}.dropdown-menu-sm-right{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-left{right:auto;left:0}.dropdown-menu-md-right{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-left{right:auto;left:0}.dropdown-menu-lg-right{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-left{right:auto;left:0}.dropdown-menu-xl-right{right:0;left:auto}}.dropup .dropdown-menu{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-menu{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropright .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropright .dropdown-toggle:empty::after{margin-left:0}.dropright .dropdown-toggle::after{vertical-align:0}.dropleft .dropdown-menu{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropleft .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropleft .dropdown-toggle::after{display:none}.dropleft .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropleft .dropdown-toggle:empty::after{margin-left:0}.dropleft .dropdown-toggle::before{vertical-align:0}.dropdown-menu[x-placement^=bottom],.dropdown-menu[x-placement^=left],.dropdown-menu[x-placement^=right],.dropdown-menu[x-placement^=top]{right:auto;bottom:auto}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid #e9ecef}.dropdown-item{display:block;width:100%;padding:.25rem 1.5rem;clear:both;font-weight:400;color:#212529;text-align:inherit;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#16181b;text-decoration:none;background-color:#f8f9fa}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#007bff}.dropdown-item.disabled,.dropdown-item:disabled{color:#6c757d;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1.5rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1.5rem;color:#212529}.btn-group,.btn-group-vertical{position:relative;display:-ms-inline-flexbox;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;-ms-flex:1 1 auto;flex:1 1 auto}.btn-group-vertical>.btn:hover,.btn-group>.btn:hover{z-index:1}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus{z-index:1}.btn-toolbar{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:start;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropright .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropleft .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{-ms-flex-direction:column;flex-direction:column;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:center;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn:not(:first-child){border-top-left-radius:0;border-top-right-radius:0}.btn-group-toggle>.btn,.btn-group-toggle>.btn-group>.btn{margin-bottom:0}.btn-group-toggle>.btn input[type=checkbox],.btn-group-toggle>.btn input[type=radio],.btn-group-toggle>.btn-group>.btn input[type=checkbox],.btn-group-toggle>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:stretch;align-items:stretch;width:100%}.input-group>.custom-file,.input-group>.custom-select,.input-group>.form-control,.input-group>.form-control-plaintext{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;margin-bottom:0}.input-group>.custom-file+.custom-file,.input-group>.custom-file+.custom-select,.input-group>.custom-file+.form-control,.input-group>.custom-select+.custom-file,.input-group>.custom-select+.custom-select,.input-group>.custom-select+.form-control,.input-group>.form-control+.custom-file,.input-group>.form-control+.custom-select,.input-group>.form-control+.form-control,.input-group>.form-control-plaintext+.custom-file,.input-group>.form-control-plaintext+.custom-select,.input-group>.form-control-plaintext+.form-control{margin-left:-1px}.input-group>.custom-file .custom-file-input:focus~.custom-file-label,.input-group>.custom-select:focus,.input-group>.form-control:focus{z-index:3}.input-group>.custom-file .custom-file-input:focus{z-index:4}.input-group>.custom-select:not(:last-child),.input-group>.form-control:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-select:not(:first-child),.input-group>.form-control:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.input-group>.custom-file{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center}.input-group>.custom-file:not(:last-child) .custom-file-label,.input-group>.custom-file:not(:last-child) .custom-file-label::after{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.custom-file:not(:first-child) .custom-file-label{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-append,.input-group-prepend{display:-ms-flexbox;display:flex}.input-group-append .btn,.input-group-prepend .btn{position:relative;z-index:2}.input-group-append .btn:focus,.input-group-prepend .btn:focus{z-index:3}.input-group-append .btn+.btn,.input-group-append .btn+.input-group-text,.input-group-append .input-group-text+.btn,.input-group-append .input-group-text+.input-group-text,.input-group-prepend .btn+.btn,.input-group-prepend .btn+.input-group-text,.input-group-prepend .input-group-text+.btn,.input-group-prepend .input-group-text+.input-group-text{margin-left:-1px}.input-group-prepend{margin-right:-1px}.input-group-append{margin-left:-1px}.input-group-text{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.375rem .75rem;margin-bottom:0;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-text input[type=checkbox],.input-group-text input[type=radio]{margin-top:0}.input-group-lg>.custom-select,.input-group-lg>.form-control:not(textarea){height:calc(1.5em + 1rem + 2px)}.input-group-lg>.custom-select,.input-group-lg>.form-control,.input-group-lg>.input-group-append>.btn,.input-group-lg>.input-group-append>.input-group-text,.input-group-lg>.input-group-prepend>.btn,.input-group-lg>.input-group-prepend>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;line-height:1.5;border-radius:.3rem}.input-group-sm>.custom-select,.input-group-sm>.form-control:not(textarea){height:calc(1.5em + .5rem + 2px)}.input-group-sm>.custom-select,.input-group-sm>.form-control,.input-group-sm>.input-group-append>.btn,.input-group-sm>.input-group-append>.input-group-text,.input-group-sm>.input-group-prepend>.btn,.input-group-sm>.input-group-prepend>.input-group-text{padding:.25rem .5rem;font-size:.875rem;line-height:1.5;border-radius:.2rem}.input-group-lg>.custom-select,.input-group-sm>.custom-select{padding-right:1.75rem}.input-group>.input-group-append:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group>.input-group-append:last-child>.input-group-text:not(:last-child),.input-group>.input-group-append:not(:last-child)>.btn,.input-group>.input-group-append:not(:last-child)>.input-group-text,.input-group>.input-group-prepend>.btn,.input-group>.input-group-prepend>.input-group-text{border-top-right-radius:0;border-bottom-right-radius:0}.input-group>.input-group-append>.btn,.input-group>.input-group-append>.input-group-text,.input-group>.input-group-prepend:first-child>.btn:not(:first-child),.input-group>.input-group-prepend:first-child>.input-group-text:not(:first-child),.input-group>.input-group-prepend:not(:first-child)>.btn,.input-group>.input-group-prepend:not(:first-child)>.input-group-text{border-top-left-radius:0;border-bottom-left-radius:0}.custom-control{position:relative;display:block;min-height:1.5rem;padding-left:1.5rem}.custom-control-inline{display:-ms-inline-flexbox;display:inline-flex;margin-right:1rem}.custom-control-input{position:absolute;z-index:-1;opacity:0}.custom-control-input:checked~.custom-control-label::before{color:#fff;border-color:#007bff;background-color:#007bff}.custom-control-input:focus~.custom-control-label::before{box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-control-input:focus:not(:checked)~.custom-control-label::before{border-color:#80bdff}.custom-control-input:not(:disabled):active~.custom-control-label::before{color:#fff;background-color:#b3d7ff;border-color:#b3d7ff}.custom-control-input:disabled~.custom-control-label{color:#6c757d}.custom-control-input:disabled~.custom-control-label::before{background-color:#e9ecef}.custom-control-label{position:relative;margin-bottom:0;vertical-align:top}.custom-control-label::before{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;pointer-events:none;content:"";background-color:#fff;border:#adb5bd solid 1px}.custom-control-label::after{position:absolute;top:.25rem;left:-1.5rem;display:block;width:1rem;height:1rem;content:"";background:no-repeat 50%/50% 50%}.custom-checkbox .custom-control-label::before{border-radius:.25rem}.custom-checkbox .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::before{border-color:#007bff;background-color:#007bff}.custom-checkbox .custom-control-input:indeterminate~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e")}.custom-checkbox .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-checkbox .custom-control-input:disabled:indeterminate~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-radio .custom-control-label::before{border-radius:50%}.custom-radio .custom-control-input:checked~.custom-control-label::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.custom-radio .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-switch{padding-left:2.25rem}.custom-switch .custom-control-label::before{left:-2.25rem;width:1.75rem;pointer-events:all;border-radius:.5rem}.custom-switch .custom-control-label::after{top:calc(.25rem + 2px);left:calc(-2.25rem + 2px);width:calc(1rem - 4px);height:calc(1rem - 4px);background-color:#adb5bd;border-radius:.5rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:transform .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,-webkit-transform .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-switch .custom-control-label::after{transition:none}}.custom-switch .custom-control-input:checked~.custom-control-label::after{background-color:#fff;-webkit-transform:translateX(.75rem);transform:translateX(.75rem)}.custom-switch .custom-control-input:disabled:checked~.custom-control-label::before{background-color:rgba(0,123,255,.5)}.custom-select{display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);padding:.375rem 1.75rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#495057;vertical-align:middle;background:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right .75rem center/8px 10px;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-select:focus{border-color:#80bdff;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-select:focus::-ms-value{color:#495057;background-color:#fff}.custom-select[multiple],.custom-select[size]:not([size="1"]){height:auto;padding-right:.75rem;background-image:none}.custom-select:disabled{color:#6c757d;background-color:#e9ecef}.custom-select::-ms-expand{display:none}.custom-select-sm{height:calc(1.5em + .5rem + 2px);padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.custom-select-lg{height:calc(1.5em + 1rem + 2px);padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.custom-file{position:relative;display:inline-block;width:100%;height:calc(1.5em + .75rem + 2px);margin-bottom:0}.custom-file-input{position:relative;z-index:2;width:100%;height:calc(1.5em + .75rem + 2px);margin:0;opacity:0}.custom-file-input:focus~.custom-file-label{border-color:#80bdff;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.custom-file-input:disabled~.custom-file-label{background-color:#e9ecef}.custom-file-input:lang(en)~.custom-file-label::after{content:"Browse"}.custom-file-input~.custom-file-label[data-browse]::after{content:attr(data-browse)}.custom-file-label{position:absolute;top:0;right:0;left:0;z-index:1;height:calc(1.5em + .75rem + 2px);padding:.375rem .75rem;font-weight:400;line-height:1.5;color:#495057;background-color:#fff;border:1px solid #ced4da;border-radius:.25rem}.custom-file-label::after{position:absolute;top:0;right:0;bottom:0;z-index:3;display:block;height:calc(1.5em + .75rem);padding:.375rem .75rem;line-height:1.5;color:#495057;content:"Browse";background-color:#e9ecef;border-left:inherit;border-radius:0 .25rem .25rem 0}.custom-range{width:100%;height:calc(1rem + .4rem);padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.custom-range:focus{outline:0}.custom-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range:focus::-ms-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .2rem rgba(0,123,255,.25)}.custom-range::-moz-focus-outer{border:0}.custom-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-webkit-slider-thumb{transition:none}}.custom-range::-webkit-slider-thumb:active{background-color:#b3d7ff}.custom-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-moz-range-thumb{transition:none}}.custom-range::-moz-range-thumb:active{background-color:#b3d7ff}.custom-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.custom-range::-ms-thumb{width:1rem;height:1rem;margin-top:0;margin-right:.2rem;margin-left:.2rem;background-color:#007bff;border:0;border-radius:1rem;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;appearance:none}@media (prefers-reduced-motion:reduce){.custom-range::-ms-thumb{transition:none}}.custom-range::-ms-thumb:active{background-color:#b3d7ff}.custom-range::-ms-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:transparent;border-color:transparent;border-width:.5rem}.custom-range::-ms-fill-lower{background-color:#dee2e6;border-radius:1rem}.custom-range::-ms-fill-upper{margin-right:15px;background-color:#dee2e6;border-radius:1rem}.custom-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.custom-range:disabled::-webkit-slider-runnable-track{cursor:default}.custom-range:disabled::-moz-range-thumb{background-color:#adb5bd}.custom-range:disabled::-moz-range-track{cursor:default}.custom-range:disabled::-ms-thumb{background-color:#adb5bd}.custom-control-label::before,.custom-file-label,.custom-select{transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.custom-control-label::before,.custom-file-label,.custom-select{transition:none}}.nav{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem}.nav-link:focus,.nav-link:hover{text-decoration:none}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-item{margin-bottom:-1px}.nav-tabs .nav-link{border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#007bff}.nav-fill .nav-item{-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;text-align:center}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding:.5rem 1rem}.navbar>.container,.navbar>.container-fluid{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{display:inline-block;padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;line-height:inherit;white-space:nowrap}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-nav{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static;float:none}.navbar-text{display:inline-block;padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{-ms-flex-preferred-size:100%;flex-basis:100%;-ms-flex-positive:1;flex-grow:1;-ms-flex-align:center;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem}.navbar-toggler:focus,.navbar-toggler:hover{text-decoration:none}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;content:"";background:no-repeat center center;background-size:100% 100%}@media (max-width:575.98px){.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:576px){.navbar-expand-sm{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-sm .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm>.container,.navbar-expand-sm>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-sm .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}}@media (max-width:767.98px){.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:768px){.navbar-expand-md{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-md .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md>.container,.navbar-expand-md>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-md .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}}@media (max-width:991.98px){.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:992px){.navbar-expand-lg{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-lg .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg>.container,.navbar-expand-lg>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-lg .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}}@media (max-width:1199.98px){.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{padding-right:0;padding-left:0}}@media (min-width:1200px){.navbar-expand-xl{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xl .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl>.container,.navbar-expand-xl>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand-xl .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}}.navbar-expand{-ms-flex-flow:row nowrap;flex-flow:row nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand>.container,.navbar-expand>.container-fluid{padding-right:0;padding-left:0}.navbar-expand .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand>.container,.navbar-expand>.container-fluid{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.navbar-expand .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.5)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .active>.nav-link,.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .nav-link.show,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.5);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.5)}.navbar-light .navbar-text a{color:rgba(0,0,0,.9)}.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.5)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .active>.nav-link,.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .nav-link.show,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.5);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.5)}.navbar-dark .navbar-text a{color:#fff}.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group:first-child .list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card>.list-group:last-child .list-group-item:last-child{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.card-body{-ms-flex:1 1 auto;flex:1 1 auto;padding:1.25rem}.card-title{margin-bottom:.75rem}.card-subtitle{margin-top:-.375rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1.25rem}.card-header{padding:.75rem 1.25rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-header+.list-group .list-group-item:first-child{border-top:0}.card-footer{padding:.75rem 1.25rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.625rem;margin-bottom:-.75rem;margin-left:-.625rem;border-bottom:0}.card-header-pills{margin-right:-.625rem;margin-left:-.625rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1.25rem}.card-img{width:100%;border-radius:calc(.25rem - 1px)}.card-img-top{width:100%;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img-bottom{width:100%;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-deck{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card-deck .card{margin-bottom:15px}@media (min-width:576px){.card-deck{-ms-flex-flow:row wrap;flex-flow:row wrap;margin-right:-15px;margin-left:-15px}.card-deck .card{display:-ms-flexbox;display:flex;-ms-flex:1 0 0%;flex:1 0 0%;-ms-flex-direction:column;flex-direction:column;margin-right:15px;margin-bottom:0;margin-left:15px}}.card-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.card-group>.card{margin-bottom:15px}@media (min-width:576px){.card-group{-ms-flex-flow:row wrap;flex-flow:row wrap}.card-group>.card{-ms-flex:1 0 0%;flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.card-columns .card{margin-bottom:.75rem}@media (min-width:576px){.card-columns{-webkit-column-count:3;-moz-column-count:3;column-count:3;-webkit-column-gap:1.25rem;-moz-column-gap:1.25rem;column-gap:1.25rem;orphans:1;widows:1}.card-columns .card{display:inline-block;width:100%}}.accordion>.card{overflow:hidden}.accordion>.card:not(:first-of-type) .card-header:first-child{border-radius:0}.accordion>.card:not(:first-of-type):not(:last-of-type){border-bottom:0;border-radius:0}.accordion>.card:first-of-type{border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.accordion>.card:last-of-type{border-top-left-radius:0;border-top-right-radius:0}.accordion>.card .card-header{margin-bottom:-1px}.breadcrumb{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:.75rem 1rem;margin-bottom:1rem;list-style:none;background-color:#e9ecef;border-radius:.25rem}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{display:inline-block;padding-right:.5rem;color:#6c757d;content:"/"}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:underline}.breadcrumb-item+.breadcrumb-item:hover::before{text-decoration:none}.breadcrumb-item.active{color:#6c757d}.pagination{display:-ms-flexbox;display:flex;padding-left:0;list-style:none;border-radius:.25rem}.page-link{position:relative;display:block;padding:.5rem .75rem;margin-left:-1px;line-height:1.25;color:#007bff;background-color:#fff;border:1px solid #dee2e6}.page-link:hover{z-index:2;color:#0056b3;text-decoration:none;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:2;outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.25)}.page-item:first-child .page-link{margin-left:0;border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.page-item.active .page-link{z-index:1;color:#fff;background-color:#007bff;border-color:#007bff}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;cursor:auto;background-color:#fff;border-color:#dee2e6}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem;line-height:1.5}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem;line-height:1.5}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.25em .4em;font-size:75%;font-weight:700;line-height:1;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.badge{transition:none}}a.badge:focus,a.badge:hover{text-decoration:none}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.badge-pill{padding-right:.6em;padding-left:.6em;border-radius:10rem}.badge-primary{color:#fff;background-color:#007bff}a.badge-primary:focus,a.badge-primary:hover{color:#fff;background-color:#0062cc}a.badge-primary.focus,a.badge-primary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(0,123,255,.5)}.badge-secondary{color:#fff;background-color:#6c757d}a.badge-secondary:focus,a.badge-secondary:hover{color:#fff;background-color:#545b62}a.badge-secondary.focus,a.badge-secondary:focus{outline:0;box-shadow:0 0 0 .2rem rgba(108,117,125,.5)}.badge-success{color:#fff;background-color:#28a745}a.badge-success:focus,a.badge-success:hover{color:#fff;background-color:#1e7e34}a.badge-success.focus,a.badge-success:focus{outline:0;box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.badge-info{color:#fff;background-color:#17a2b8}a.badge-info:focus,a.badge-info:hover{color:#fff;background-color:#117a8b}a.badge-info.focus,a.badge-info:focus{outline:0;box-shadow:0 0 0 .2rem rgba(23,162,184,.5)}.badge-warning{color:#212529;background-color:#ffc107}a.badge-warning:focus,a.badge-warning:hover{color:#212529;background-color:#d39e00}a.badge-warning.focus,a.badge-warning:focus{outline:0;box-shadow:0 0 0 .2rem rgba(255,193,7,.5)}.badge-danger{color:#fff;background-color:#dc3545}a.badge-danger:focus,a.badge-danger:hover{color:#fff;background-color:#bd2130}a.badge-danger.focus,a.badge-danger:focus{outline:0;box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.badge-light{color:#212529;background-color:#f8f9fa}a.badge-light:focus,a.badge-light:hover{color:#212529;background-color:#dae0e5}a.badge-light.focus,a.badge-light:focus{outline:0;box-shadow:0 0 0 .2rem rgba(248,249,250,.5)}.badge-dark{color:#fff;background-color:#343a40}a.badge-dark:focus,a.badge-dark:hover{color:#fff;background-color:#1d2124}a.badge-dark.focus,a.badge-dark:focus{outline:0;box-shadow:0 0 0 .2rem rgba(52,58,64,.5)}.jumbotron{padding:2rem 1rem;margin-bottom:2rem;background-color:#e9ecef;border-radius:.3rem}@media (min-width:576px){.jumbotron{padding:4rem 2rem}}.jumbotron-fluid{padding-right:0;padding-left:0;border-radius:0}.alert{position:relative;padding:.75rem 1.25rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:4rem}.alert-dismissible .close{position:absolute;top:0;right:0;padding:.75rem 1.25rem;color:inherit}.alert-primary{color:#004085;background-color:#cce5ff;border-color:#b8daff}.alert-primary hr{border-top-color:#9fcdff}.alert-primary .alert-link{color:#002752}.alert-secondary{color:#383d41;background-color:#e2e3e5;border-color:#d6d8db}.alert-secondary hr{border-top-color:#c8cbcf}.alert-secondary .alert-link{color:#202326}.alert-success{color:#155724;background-color:#d4edda;border-color:#c3e6cb}.alert-success hr{border-top-color:#b1dfbb}.alert-success .alert-link{color:#0b2e13}.alert-info{color:#0c5460;background-color:#d1ecf1;border-color:#bee5eb}.alert-info hr{border-top-color:#abdde5}.alert-info .alert-link{color:#062c33}.alert-warning{color:#856404;background-color:#fff3cd;border-color:#ffeeba}.alert-warning hr{border-top-color:#ffe8a1}.alert-warning .alert-link{color:#533f03}.alert-danger{color:#721c24;background-color:#f8d7da;border-color:#f5c6cb}.alert-danger hr{border-top-color:#f1b0b7}.alert-danger .alert-link{color:#491217}.alert-light{color:#818182;background-color:#fefefe;border-color:#fdfdfe}.alert-light hr{border-top-color:#ececf6}.alert-light .alert-link{color:#686868}.alert-dark{color:#1b1e21;background-color:#d6d8d9;border-color:#c6c8ca}.alert-dark hr{border-top-color:#b9bbbe}.alert-dark .alert-link{color:#040505}@-webkit-keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:1rem 0}to{background-position:0 0}}.progress{display:-ms-flexbox;display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;color:#fff;text-align:center;white-space:nowrap;background-color:#007bff;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:progress-bar-stripes 1s linear infinite;animation:progress-bar-stripes 1s linear infinite}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.media{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start}.media-body{-ms-flex:1;flex:1}.list-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.75rem 1.25rem;margin-bottom:-1px;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#007bff;border-color:#007bff}.list-group-horizontal{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}@media (min-width:576px){.list-group-horizontal-sm{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-sm .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-sm .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}@media (min-width:768px){.list-group-horizontal-md{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-md .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-md .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}@media (min-width:992px){.list-group-horizontal-lg{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-lg .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-lg .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}@media (min-width:1200px){.list-group-horizontal-xl{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-xl .list-group-item{margin-right:-1px;margin-bottom:0}.list-group-horizontal-xl .list-group-item:first-child{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl .list-group-item:last-child{margin-right:0;border-top-right-radius:.25rem;border-bottom-right-radius:.25rem;border-bottom-left-radius:0}}.list-group-flush .list-group-item{border-right:0;border-left:0;border-radius:0}.list-group-flush .list-group-item:last-child{margin-bottom:-1px}.list-group-flush:first-child .list-group-item:first-child{border-top:0}.list-group-flush:last-child .list-group-item:last-child{margin-bottom:0;border-bottom:0}.list-group-item-primary{color:#004085;background-color:#b8daff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#004085;background-color:#9fcdff}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#004085;border-color:#004085}.list-group-item-secondary{color:#383d41;background-color:#d6d8db}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#383d41;background-color:#c8cbcf}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#383d41;border-color:#383d41}.list-group-item-success{color:#155724;background-color:#c3e6cb}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#155724;background-color:#b1dfbb}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#155724;border-color:#155724}.list-group-item-info{color:#0c5460;background-color:#bee5eb}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#0c5460;background-color:#abdde5}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#0c5460;border-color:#0c5460}.list-group-item-warning{color:#856404;background-color:#ffeeba}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#856404;background-color:#ffe8a1}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#856404;border-color:#856404}.list-group-item-danger{color:#721c24;background-color:#f5c6cb}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#721c24;background-color:#f1b0b7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#721c24;border-color:#721c24}.list-group-item-light{color:#818182;background-color:#fdfdfe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#818182;background-color:#ececf6}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#818182;border-color:#818182}.list-group-item-dark{color:#1b1e21;background-color:#c6c8ca}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#1b1e21;background-color:#b9bbbe}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#1b1e21;border-color:#1b1e21}.close{float:right;font-size:1.5rem;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.5}.close:hover{color:#000;text-decoration:none}.close:not(:disabled):not(.disabled):focus,.close:not(:disabled):not(.disabled):hover{opacity:.75}button.close{padding:0;background-color:transparent;border:0;-webkit-appearance:none;-moz-appearance:none;appearance:none}a.close.disabled{pointer-events:none}.toast{max-width:350px;overflow:hidden;font-size:.875rem;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .25rem .75rem rgba(0,0,0,.1);-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px);opacity:0;border-radius:.25rem}.toast:not(:last-child){margin-bottom:.75rem}.toast.showing{opacity:1}.toast.show{display:block;opacity:1}.toast.hide{display:none}.toast-header{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.25rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05)}.toast-body{padding:.75rem}.modal-open{overflow:hidden}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal{position:fixed;top:0;left:0;z-index:1050;display:none;width:100%;height:100%;overflow:hidden;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:-webkit-transform .3s ease-out;transition:transform .3s ease-out;transition:transform .3s ease-out,-webkit-transform .3s ease-out;-webkit-transform:translate(0,-50px);transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{-webkit-transform:none;transform:none}.modal-dialog-scrollable{display:-ms-flexbox;display:flex;max-height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 1rem);overflow:hidden}.modal-dialog-scrollable .modal-footer,.modal-dialog-scrollable .modal-header{-ms-flex-negative:0;flex-shrink:0}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;min-height:calc(100% - 1rem)}.modal-dialog-centered::before{display:block;height:calc(100vh - 1rem);content:""}.modal-dialog-centered.modal-dialog-scrollable{-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;height:100%}.modal-dialog-centered.modal-dialog-scrollable .modal-content{max-height:none}.modal-dialog-centered.modal-dialog-scrollable::before{content:none}.modal-content{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:-ms-flexbox;display:flex;-ms-flex-align:start;align-items:flex-start;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:.3rem;border-top-right-radius:.3rem}.modal-header .close{padding:1rem 1rem;margin:-1rem -1rem -1rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem}.modal-footer{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:flex-end;padding:1rem;border-top:1px solid #dee2e6;border-bottom-right-radius:.3rem;border-bottom-left-radius:.3rem}.modal-footer>:not(:first-child){margin-left:.25rem}.modal-footer>:not(:last-child){margin-right:.25rem}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{max-height:calc(100% - 3.5rem)}.modal-dialog-scrollable .modal-content{max-height:calc(100vh - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-dialog-centered::before{height:calc(100vh - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.tooltip{position:absolute;z-index:1070;display:block;margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[x-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[x-placement^=top] .arrow,.bs-tooltip-top .arrow{bottom:0}.bs-tooltip-auto[x-placement^=top] .arrow::before,.bs-tooltip-top .arrow::before{top:0;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[x-placement^=right],.bs-tooltip-right{padding:0 .4rem}.bs-tooltip-auto[x-placement^=right] .arrow,.bs-tooltip-right .arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=right] .arrow::before,.bs-tooltip-right .arrow::before{right:0;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[x-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[x-placement^=bottom] .arrow,.bs-tooltip-bottom .arrow{top:0}.bs-tooltip-auto[x-placement^=bottom] .arrow::before,.bs-tooltip-bottom .arrow::before{bottom:0;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[x-placement^=left],.bs-tooltip-left{padding:0 .4rem}.bs-tooltip-auto[x-placement^=left] .arrow,.bs-tooltip-left .arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[x-placement^=left] .arrow::before,.bs-tooltip-left .arrow::before{left:0;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1060;display:block;max-width:276px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .arrow{position:absolute;display:block;width:1rem;height:.5rem;margin:0 .3rem}.popover .arrow::after,.popover .arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[x-placement^=top],.bs-popover-top{margin-bottom:.5rem}.bs-popover-auto[x-placement^=top]>.arrow,.bs-popover-top>.arrow{bottom:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=top]>.arrow::before,.bs-popover-top>.arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=top]>.arrow::after,.bs-popover-top>.arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[x-placement^=right],.bs-popover-right{margin-left:.5rem}.bs-popover-auto[x-placement^=right]>.arrow,.bs-popover-right>.arrow{left:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=right]>.arrow::before,.bs-popover-right>.arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=right]>.arrow::after,.bs-popover-right>.arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[x-placement^=bottom],.bs-popover-bottom{margin-top:.5rem}.bs-popover-auto[x-placement^=bottom]>.arrow,.bs-popover-bottom>.arrow{top:calc((.5rem + 1px) * -1)}.bs-popover-auto[x-placement^=bottom]>.arrow::before,.bs-popover-bottom>.arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=bottom]>.arrow::after,.bs-popover-bottom>.arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[x-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f7f7f7}.bs-popover-auto[x-placement^=left],.bs-popover-left{margin-right:.5rem}.bs-popover-auto[x-placement^=left]>.arrow,.bs-popover-left>.arrow{right:calc((.5rem + 1px) * -1);width:.5rem;height:1rem;margin:.3rem 0}.bs-popover-auto[x-placement^=left]>.arrow::before,.bs-popover-left>.arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[x-placement^=left]>.arrow::after,.bs-popover-left>.arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem .75rem;margin-bottom:0;font-size:1rem;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:.5rem .75rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{-ms-touch-action:pan-y;touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:-webkit-transform .6s ease-in-out;transition:transform .6s ease-in-out;transition:transform .6s ease-in-out,-webkit-transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-right,.carousel-item-next:not(.carousel-item-left){-webkit-transform:translateX(100%);transform:translateX(100%)}.active.carousel-item-left,.carousel-item-prev:not(.carousel-item-right){-webkit-transform:translateX(-100%);transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;-webkit-transform:none;transform:none}.carousel-fade .carousel-item-next.carousel-item-left,.carousel-fade .carousel-item-prev.carousel-item-right,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{z-index:0;opacity:0;transition:0s .6s opacity}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-left,.carousel-fade .active.carousel-item-right{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;width:15%;color:#fff;text-align:center;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:20px;height:20px;background:no-repeat 50%/100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:15;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;padding-left:0;margin-right:15%;margin-left:15%;list-style:none}.carousel-indicators li{box-sizing:content-box;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators li{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center}@-webkit-keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spinner-border{to{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:spinner-border .75s linear infinite;animation:spinner-border .75s linear infinite}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1}}@keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:text-bottom;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:spinner-grow .75s linear infinite;animation:spinner-grow .75s linear infinite}.spinner-grow-sm{width:1rem;height:1rem}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.bg-primary{background-color:#007bff!important}a.bg-primary:focus,a.bg-primary:hover,button.bg-primary:focus,button.bg-primary:hover{background-color:#0062cc!important}.bg-secondary{background-color:#6c757d!important}a.bg-secondary:focus,a.bg-secondary:hover,button.bg-secondary:focus,button.bg-secondary:hover{background-color:#545b62!important}.bg-success{background-color:#28a745!important}a.bg-success:focus,a.bg-success:hover,button.bg-success:focus,button.bg-success:hover{background-color:#1e7e34!important}.bg-info{background-color:#17a2b8!important}a.bg-info:focus,a.bg-info:hover,button.bg-info:focus,button.bg-info:hover{background-color:#117a8b!important}.bg-warning{background-color:#ffc107!important}a.bg-warning:focus,a.bg-warning:hover,button.bg-warning:focus,button.bg-warning:hover{background-color:#d39e00!important}.bg-danger{background-color:#dc3545!important}a.bg-danger:focus,a.bg-danger:hover,button.bg-danger:focus,button.bg-danger:hover{background-color:#bd2130!important}.bg-light{background-color:#f8f9fa!important}a.bg-light:focus,a.bg-light:hover,button.bg-light:focus,button.bg-light:hover{background-color:#dae0e5!important}.bg-dark{background-color:#343a40!important}a.bg-dark:focus,a.bg-dark:hover,button.bg-dark:focus,button.bg-dark:hover{background-color:#1d2124!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.border{border:1px solid #dee2e6!important}.border-top{border-top:1px solid #dee2e6!important}.border-right{border-right:1px solid #dee2e6!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-left{border-left:1px solid #dee2e6!important}.border-0{border:0!important}.border-top-0{border-top:0!important}.border-right-0{border-right:0!important}.border-bottom-0{border-bottom:0!important}.border-left-0{border-left:0!important}.border-primary{border-color:#007bff!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#28a745!important}.border-info{border-color:#17a2b8!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#343a40!important}.border-white{border-color:#fff!important}.rounded-sm{border-radius:.2rem!important}.rounded{border-radius:.25rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-right{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-left{border-top-left-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-lg{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-0{border-radius:0!important}.clearfix::after{display:block;clear:both;content:""}.d-none{display:none!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:-ms-flexbox!important;display:flex!important}.d-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}@media (min-width:576px){.d-sm-none{display:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:-ms-flexbox!important;display:flex!important}.d-sm-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:768px){.d-md-none{display:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:-ms-flexbox!important;display:flex!important}.d-md-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:992px){.d-lg-none{display:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:-ms-flexbox!important;display:flex!important}.d-lg-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media (min-width:1200px){.d-xl-none{display:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:-ms-flexbox!important;display:flex!important}.d-xl-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}@media print{.d-print-none{display:none!important}.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:-ms-flexbox!important;display:flex!important}.d-print-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}}.embed-responsive{position:relative;display:block;width:100%;padding:0;overflow:hidden}.embed-responsive::before{display:block;content:""}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive-21by9::before{padding-top:42.857143%}.embed-responsive-16by9::before{padding-top:56.25%}.embed-responsive-4by3::before{padding-top:75%}.embed-responsive-1by1::before{padding-top:100%}.flex-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-center{-ms-flex-align:center!important;align-items:center!important}.align-items-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}@media (min-width:576px){.flex-sm-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-sm-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-sm-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-sm-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-sm-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-sm-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-sm-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-sm-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-sm-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-sm-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-sm-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-sm-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-sm-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-sm-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-sm-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-sm-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-sm-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-sm-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-sm-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-sm-center{-ms-flex-align:center!important;align-items:center!important}.align-items-sm-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-sm-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-sm-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-sm-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-sm-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-sm-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-sm-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-sm-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-sm-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-sm-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-sm-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-sm-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-sm-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-sm-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:768px){.flex-md-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-md-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-md-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-md-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-md-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-md-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-md-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-md-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-md-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-md-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-md-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-md-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-md-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-md-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-md-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-md-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-md-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-md-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-md-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-md-center{-ms-flex-align:center!important;align-items:center!important}.align-items-md-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-md-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-md-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-md-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-md-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-md-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-md-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-md-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-md-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-md-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-md-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-md-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-md-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-md-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:992px){.flex-lg-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-lg-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-lg-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-lg-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-lg-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-lg-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-lg-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-lg-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-lg-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-lg-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-lg-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-lg-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-lg-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-lg-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-lg-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-lg-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-lg-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-lg-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-lg-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-lg-center{-ms-flex-align:center!important;align-items:center!important}.align-items-lg-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-lg-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-lg-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-lg-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-lg-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-lg-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-lg-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-lg-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-lg-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-lg-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-lg-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-lg-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-lg-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-lg-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}@media (min-width:1200px){.flex-xl-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-xl-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-xl-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xl-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.flex-xl-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-xl-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-xl-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-xl-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-xl-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.justify-content-xl-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xl-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xl-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xl-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.align-items-xl-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xl-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xl-center{-ms-flex-align:center!important;align-items:center!important}.align-items-xl-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xl-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}}.float-left{float:left!important}.float-right{float:right!important}.float-none{float:none!important}@media (min-width:576px){.float-sm-left{float:left!important}.float-sm-right{float:right!important}.float-sm-none{float:none!important}}@media (min-width:768px){.float-md-left{float:left!important}.float-md-right{float:right!important}.float-md-none{float:none!important}}@media (min-width:992px){.float-lg-left{float:left!important}.float-lg-right{float:right!important}.float-lg-none{float:none!important}}@media (min-width:1200px){.float-xl-left{float:left!important}.float-xl-right{float:right!important}.float-xl-none{float:none!important}}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}@supports ((position:-webkit-sticky) or (position:sticky)){.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.sr-only{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;overflow:visible;clip:auto;white-space:normal}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mw-100{max-width:100%!important}.mh-100{max-height:100%!important}.min-vw-100{min-width:100vw!important}.min-vh-100{min-height:100vh!important}.vw-100{width:100vw!important}.vh-100{height:100vh!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;pointer-events:auto;content:"";background-color:rgba(0,0,0,0)}.m-0{margin:0!important}.mt-0,.my-0{margin-top:0!important}.mr-0,.mx-0{margin-right:0!important}.mb-0,.my-0{margin-bottom:0!important}.ml-0,.mx-0{margin-left:0!important}.m-1{margin:.25rem!important}.mt-1,.my-1{margin-top:.25rem!important}.mr-1,.mx-1{margin-right:.25rem!important}.mb-1,.my-1{margin-bottom:.25rem!important}.ml-1,.mx-1{margin-left:.25rem!important}.m-2{margin:.5rem!important}.mt-2,.my-2{margin-top:.5rem!important}.mr-2,.mx-2{margin-right:.5rem!important}.mb-2,.my-2{margin-bottom:.5rem!important}.ml-2,.mx-2{margin-left:.5rem!important}.m-3{margin:1rem!important}.mt-3,.my-3{margin-top:1rem!important}.mr-3,.mx-3{margin-right:1rem!important}.mb-3,.my-3{margin-bottom:1rem!important}.ml-3,.mx-3{margin-left:1rem!important}.m-4{margin:1.5rem!important}.mt-4,.my-4{margin-top:1.5rem!important}.mr-4,.mx-4{margin-right:1.5rem!important}.mb-4,.my-4{margin-bottom:1.5rem!important}.ml-4,.mx-4{margin-left:1.5rem!important}.m-5{margin:3rem!important}.mt-5,.my-5{margin-top:3rem!important}.mr-5,.mx-5{margin-right:3rem!important}.mb-5,.my-5{margin-bottom:3rem!important}.ml-5,.mx-5{margin-left:3rem!important}.p-0{padding:0!important}.pt-0,.py-0{padding-top:0!important}.pr-0,.px-0{padding-right:0!important}.pb-0,.py-0{padding-bottom:0!important}.pl-0,.px-0{padding-left:0!important}.p-1{padding:.25rem!important}.pt-1,.py-1{padding-top:.25rem!important}.pr-1,.px-1{padding-right:.25rem!important}.pb-1,.py-1{padding-bottom:.25rem!important}.pl-1,.px-1{padding-left:.25rem!important}.p-2{padding:.5rem!important}.pt-2,.py-2{padding-top:.5rem!important}.pr-2,.px-2{padding-right:.5rem!important}.pb-2,.py-2{padding-bottom:.5rem!important}.pl-2,.px-2{padding-left:.5rem!important}.p-3{padding:1rem!important}.pt-3,.py-3{padding-top:1rem!important}.pr-3,.px-3{padding-right:1rem!important}.pb-3,.py-3{padding-bottom:1rem!important}.pl-3,.px-3{padding-left:1rem!important}.p-4{padding:1.5rem!important}.pt-4,.py-4{padding-top:1.5rem!important}.pr-4,.px-4{padding-right:1.5rem!important}.pb-4,.py-4{padding-bottom:1.5rem!important}.pl-4,.px-4{padding-left:1.5rem!important}.p-5{padding:3rem!important}.pt-5,.py-5{padding-top:3rem!important}.pr-5,.px-5{padding-right:3rem!important}.pb-5,.py-5{padding-bottom:3rem!important}.pl-5,.px-5{padding-left:3rem!important}.m-n1{margin:-.25rem!important}.mt-n1,.my-n1{margin-top:-.25rem!important}.mr-n1,.mx-n1{margin-right:-.25rem!important}.mb-n1,.my-n1{margin-bottom:-.25rem!important}.ml-n1,.mx-n1{margin-left:-.25rem!important}.m-n2{margin:-.5rem!important}.mt-n2,.my-n2{margin-top:-.5rem!important}.mr-n2,.mx-n2{margin-right:-.5rem!important}.mb-n2,.my-n2{margin-bottom:-.5rem!important}.ml-n2,.mx-n2{margin-left:-.5rem!important}.m-n3{margin:-1rem!important}.mt-n3,.my-n3{margin-top:-1rem!important}.mr-n3,.mx-n3{margin-right:-1rem!important}.mb-n3,.my-n3{margin-bottom:-1rem!important}.ml-n3,.mx-n3{margin-left:-1rem!important}.m-n4{margin:-1.5rem!important}.mt-n4,.my-n4{margin-top:-1.5rem!important}.mr-n4,.mx-n4{margin-right:-1.5rem!important}.mb-n4,.my-n4{margin-bottom:-1.5rem!important}.ml-n4,.mx-n4{margin-left:-1.5rem!important}.m-n5{margin:-3rem!important}.mt-n5,.my-n5{margin-top:-3rem!important}.mr-n5,.mx-n5{margin-right:-3rem!important}.mb-n5,.my-n5{margin-bottom:-3rem!important}.ml-n5,.mx-n5{margin-left:-3rem!important}.m-auto{margin:auto!important}.mt-auto,.my-auto{margin-top:auto!important}.mr-auto,.mx-auto{margin-right:auto!important}.mb-auto,.my-auto{margin-bottom:auto!important}.ml-auto,.mx-auto{margin-left:auto!important}@media (min-width:576px){.m-sm-0{margin:0!important}.mt-sm-0,.my-sm-0{margin-top:0!important}.mr-sm-0,.mx-sm-0{margin-right:0!important}.mb-sm-0,.my-sm-0{margin-bottom:0!important}.ml-sm-0,.mx-sm-0{margin-left:0!important}.m-sm-1{margin:.25rem!important}.mt-sm-1,.my-sm-1{margin-top:.25rem!important}.mr-sm-1,.mx-sm-1{margin-right:.25rem!important}.mb-sm-1,.my-sm-1{margin-bottom:.25rem!important}.ml-sm-1,.mx-sm-1{margin-left:.25rem!important}.m-sm-2{margin:.5rem!important}.mt-sm-2,.my-sm-2{margin-top:.5rem!important}.mr-sm-2,.mx-sm-2{margin-right:.5rem!important}.mb-sm-2,.my-sm-2{margin-bottom:.5rem!important}.ml-sm-2,.mx-sm-2{margin-left:.5rem!important}.m-sm-3{margin:1rem!important}.mt-sm-3,.my-sm-3{margin-top:1rem!important}.mr-sm-3,.mx-sm-3{margin-right:1rem!important}.mb-sm-3,.my-sm-3{margin-bottom:1rem!important}.ml-sm-3,.mx-sm-3{margin-left:1rem!important}.m-sm-4{margin:1.5rem!important}.mt-sm-4,.my-sm-4{margin-top:1.5rem!important}.mr-sm-4,.mx-sm-4{margin-right:1.5rem!important}.mb-sm-4,.my-sm-4{margin-bottom:1.5rem!important}.ml-sm-4,.mx-sm-4{margin-left:1.5rem!important}.m-sm-5{margin:3rem!important}.mt-sm-5,.my-sm-5{margin-top:3rem!important}.mr-sm-5,.mx-sm-5{margin-right:3rem!important}.mb-sm-5,.my-sm-5{margin-bottom:3rem!important}.ml-sm-5,.mx-sm-5{margin-left:3rem!important}.p-sm-0{padding:0!important}.pt-sm-0,.py-sm-0{padding-top:0!important}.pr-sm-0,.px-sm-0{padding-right:0!important}.pb-sm-0,.py-sm-0{padding-bottom:0!important}.pl-sm-0,.px-sm-0{padding-left:0!important}.p-sm-1{padding:.25rem!important}.pt-sm-1,.py-sm-1{padding-top:.25rem!important}.pr-sm-1,.px-sm-1{padding-right:.25rem!important}.pb-sm-1,.py-sm-1{padding-bottom:.25rem!important}.pl-sm-1,.px-sm-1{padding-left:.25rem!important}.p-sm-2{padding:.5rem!important}.pt-sm-2,.py-sm-2{padding-top:.5rem!important}.pr-sm-2,.px-sm-2{padding-right:.5rem!important}.pb-sm-2,.py-sm-2{padding-bottom:.5rem!important}.pl-sm-2,.px-sm-2{padding-left:.5rem!important}.p-sm-3{padding:1rem!important}.pt-sm-3,.py-sm-3{padding-top:1rem!important}.pr-sm-3,.px-sm-3{padding-right:1rem!important}.pb-sm-3,.py-sm-3{padding-bottom:1rem!important}.pl-sm-3,.px-sm-3{padding-left:1rem!important}.p-sm-4{padding:1.5rem!important}.pt-sm-4,.py-sm-4{padding-top:1.5rem!important}.pr-sm-4,.px-sm-4{padding-right:1.5rem!important}.pb-sm-4,.py-sm-4{padding-bottom:1.5rem!important}.pl-sm-4,.px-sm-4{padding-left:1.5rem!important}.p-sm-5{padding:3rem!important}.pt-sm-5,.py-sm-5{padding-top:3rem!important}.pr-sm-5,.px-sm-5{padding-right:3rem!important}.pb-sm-5,.py-sm-5{padding-bottom:3rem!important}.pl-sm-5,.px-sm-5{padding-left:3rem!important}.m-sm-n1{margin:-.25rem!important}.mt-sm-n1,.my-sm-n1{margin-top:-.25rem!important}.mr-sm-n1,.mx-sm-n1{margin-right:-.25rem!important}.mb-sm-n1,.my-sm-n1{margin-bottom:-.25rem!important}.ml-sm-n1,.mx-sm-n1{margin-left:-.25rem!important}.m-sm-n2{margin:-.5rem!important}.mt-sm-n2,.my-sm-n2{margin-top:-.5rem!important}.mr-sm-n2,.mx-sm-n2{margin-right:-.5rem!important}.mb-sm-n2,.my-sm-n2{margin-bottom:-.5rem!important}.ml-sm-n2,.mx-sm-n2{margin-left:-.5rem!important}.m-sm-n3{margin:-1rem!important}.mt-sm-n3,.my-sm-n3{margin-top:-1rem!important}.mr-sm-n3,.mx-sm-n3{margin-right:-1rem!important}.mb-sm-n3,.my-sm-n3{margin-bottom:-1rem!important}.ml-sm-n3,.mx-sm-n3{margin-left:-1rem!important}.m-sm-n4{margin:-1.5rem!important}.mt-sm-n4,.my-sm-n4{margin-top:-1.5rem!important}.mr-sm-n4,.mx-sm-n4{margin-right:-1.5rem!important}.mb-sm-n4,.my-sm-n4{margin-bottom:-1.5rem!important}.ml-sm-n4,.mx-sm-n4{margin-left:-1.5rem!important}.m-sm-n5{margin:-3rem!important}.mt-sm-n5,.my-sm-n5{margin-top:-3rem!important}.mr-sm-n5,.mx-sm-n5{margin-right:-3rem!important}.mb-sm-n5,.my-sm-n5{margin-bottom:-3rem!important}.ml-sm-n5,.mx-sm-n5{margin-left:-3rem!important}.m-sm-auto{margin:auto!important}.mt-sm-auto,.my-sm-auto{margin-top:auto!important}.mr-sm-auto,.mx-sm-auto{margin-right:auto!important}.mb-sm-auto,.my-sm-auto{margin-bottom:auto!important}.ml-sm-auto,.mx-sm-auto{margin-left:auto!important}}@media (min-width:768px){.m-md-0{margin:0!important}.mt-md-0,.my-md-0{margin-top:0!important}.mr-md-0,.mx-md-0{margin-right:0!important}.mb-md-0,.my-md-0{margin-bottom:0!important}.ml-md-0,.mx-md-0{margin-left:0!important}.m-md-1{margin:.25rem!important}.mt-md-1,.my-md-1{margin-top:.25rem!important}.mr-md-1,.mx-md-1{margin-right:.25rem!important}.mb-md-1,.my-md-1{margin-bottom:.25rem!important}.ml-md-1,.mx-md-1{margin-left:.25rem!important}.m-md-2{margin:.5rem!important}.mt-md-2,.my-md-2{margin-top:.5rem!important}.mr-md-2,.mx-md-2{margin-right:.5rem!important}.mb-md-2,.my-md-2{margin-bottom:.5rem!important}.ml-md-2,.mx-md-2{margin-left:.5rem!important}.m-md-3{margin:1rem!important}.mt-md-3,.my-md-3{margin-top:1rem!important}.mr-md-3,.mx-md-3{margin-right:1rem!important}.mb-md-3,.my-md-3{margin-bottom:1rem!important}.ml-md-3,.mx-md-3{margin-left:1rem!important}.m-md-4{margin:1.5rem!important}.mt-md-4,.my-md-4{margin-top:1.5rem!important}.mr-md-4,.mx-md-4{margin-right:1.5rem!important}.mb-md-4,.my-md-4{margin-bottom:1.5rem!important}.ml-md-4,.mx-md-4{margin-left:1.5rem!important}.m-md-5{margin:3rem!important}.mt-md-5,.my-md-5{margin-top:3rem!important}.mr-md-5,.mx-md-5{margin-right:3rem!important}.mb-md-5,.my-md-5{margin-bottom:3rem!important}.ml-md-5,.mx-md-5{margin-left:3rem!important}.p-md-0{padding:0!important}.pt-md-0,.py-md-0{padding-top:0!important}.pr-md-0,.px-md-0{padding-right:0!important}.pb-md-0,.py-md-0{padding-bottom:0!important}.pl-md-0,.px-md-0{padding-left:0!important}.p-md-1{padding:.25rem!important}.pt-md-1,.py-md-1{padding-top:.25rem!important}.pr-md-1,.px-md-1{padding-right:.25rem!important}.pb-md-1,.py-md-1{padding-bottom:.25rem!important}.pl-md-1,.px-md-1{padding-left:.25rem!important}.p-md-2{padding:.5rem!important}.pt-md-2,.py-md-2{padding-top:.5rem!important}.pr-md-2,.px-md-2{padding-right:.5rem!important}.pb-md-2,.py-md-2{padding-bottom:.5rem!important}.pl-md-2,.px-md-2{padding-left:.5rem!important}.p-md-3{padding:1rem!important}.pt-md-3,.py-md-3{padding-top:1rem!important}.pr-md-3,.px-md-3{padding-right:1rem!important}.pb-md-3,.py-md-3{padding-bottom:1rem!important}.pl-md-3,.px-md-3{padding-left:1rem!important}.p-md-4{padding:1.5rem!important}.pt-md-4,.py-md-4{padding-top:1.5rem!important}.pr-md-4,.px-md-4{padding-right:1.5rem!important}.pb-md-4,.py-md-4{padding-bottom:1.5rem!important}.pl-md-4,.px-md-4{padding-left:1.5rem!important}.p-md-5{padding:3rem!important}.pt-md-5,.py-md-5{padding-top:3rem!important}.pr-md-5,.px-md-5{padding-right:3rem!important}.pb-md-5,.py-md-5{padding-bottom:3rem!important}.pl-md-5,.px-md-5{padding-left:3rem!important}.m-md-n1{margin:-.25rem!important}.mt-md-n1,.my-md-n1{margin-top:-.25rem!important}.mr-md-n1,.mx-md-n1{margin-right:-.25rem!important}.mb-md-n1,.my-md-n1{margin-bottom:-.25rem!important}.ml-md-n1,.mx-md-n1{margin-left:-.25rem!important}.m-md-n2{margin:-.5rem!important}.mt-md-n2,.my-md-n2{margin-top:-.5rem!important}.mr-md-n2,.mx-md-n2{margin-right:-.5rem!important}.mb-md-n2,.my-md-n2{margin-bottom:-.5rem!important}.ml-md-n2,.mx-md-n2{margin-left:-.5rem!important}.m-md-n3{margin:-1rem!important}.mt-md-n3,.my-md-n3{margin-top:-1rem!important}.mr-md-n3,.mx-md-n3{margin-right:-1rem!important}.mb-md-n3,.my-md-n3{margin-bottom:-1rem!important}.ml-md-n3,.mx-md-n3{margin-left:-1rem!important}.m-md-n4{margin:-1.5rem!important}.mt-md-n4,.my-md-n4{margin-top:-1.5rem!important}.mr-md-n4,.mx-md-n4{margin-right:-1.5rem!important}.mb-md-n4,.my-md-n4{margin-bottom:-1.5rem!important}.ml-md-n4,.mx-md-n4{margin-left:-1.5rem!important}.m-md-n5{margin:-3rem!important}.mt-md-n5,.my-md-n5{margin-top:-3rem!important}.mr-md-n5,.mx-md-n5{margin-right:-3rem!important}.mb-md-n5,.my-md-n5{margin-bottom:-3rem!important}.ml-md-n5,.mx-md-n5{margin-left:-3rem!important}.m-md-auto{margin:auto!important}.mt-md-auto,.my-md-auto{margin-top:auto!important}.mr-md-auto,.mx-md-auto{margin-right:auto!important}.mb-md-auto,.my-md-auto{margin-bottom:auto!important}.ml-md-auto,.mx-md-auto{margin-left:auto!important}}@media (min-width:992px){.m-lg-0{margin:0!important}.mt-lg-0,.my-lg-0{margin-top:0!important}.mr-lg-0,.mx-lg-0{margin-right:0!important}.mb-lg-0,.my-lg-0{margin-bottom:0!important}.ml-lg-0,.mx-lg-0{margin-left:0!important}.m-lg-1{margin:.25rem!important}.mt-lg-1,.my-lg-1{margin-top:.25rem!important}.mr-lg-1,.mx-lg-1{margin-right:.25rem!important}.mb-lg-1,.my-lg-1{margin-bottom:.25rem!important}.ml-lg-1,.mx-lg-1{margin-left:.25rem!important}.m-lg-2{margin:.5rem!important}.mt-lg-2,.my-lg-2{margin-top:.5rem!important}.mr-lg-2,.mx-lg-2{margin-right:.5rem!important}.mb-lg-2,.my-lg-2{margin-bottom:.5rem!important}.ml-lg-2,.mx-lg-2{margin-left:.5rem!important}.m-lg-3{margin:1rem!important}.mt-lg-3,.my-lg-3{margin-top:1rem!important}.mr-lg-3,.mx-lg-3{margin-right:1rem!important}.mb-lg-3,.my-lg-3{margin-bottom:1rem!important}.ml-lg-3,.mx-lg-3{margin-left:1rem!important}.m-lg-4{margin:1.5rem!important}.mt-lg-4,.my-lg-4{margin-top:1.5rem!important}.mr-lg-4,.mx-lg-4{margin-right:1.5rem!important}.mb-lg-4,.my-lg-4{margin-bottom:1.5rem!important}.ml-lg-4,.mx-lg-4{margin-left:1.5rem!important}.m-lg-5{margin:3rem!important}.mt-lg-5,.my-lg-5{margin-top:3rem!important}.mr-lg-5,.mx-lg-5{margin-right:3rem!important}.mb-lg-5,.my-lg-5{margin-bottom:3rem!important}.ml-lg-5,.mx-lg-5{margin-left:3rem!important}.p-lg-0{padding:0!important}.pt-lg-0,.py-lg-0{padding-top:0!important}.pr-lg-0,.px-lg-0{padding-right:0!important}.pb-lg-0,.py-lg-0{padding-bottom:0!important}.pl-lg-0,.px-lg-0{padding-left:0!important}.p-lg-1{padding:.25rem!important}.pt-lg-1,.py-lg-1{padding-top:.25rem!important}.pr-lg-1,.px-lg-1{padding-right:.25rem!important}.pb-lg-1,.py-lg-1{padding-bottom:.25rem!important}.pl-lg-1,.px-lg-1{padding-left:.25rem!important}.p-lg-2{padding:.5rem!important}.pt-lg-2,.py-lg-2{padding-top:.5rem!important}.pr-lg-2,.px-lg-2{padding-right:.5rem!important}.pb-lg-2,.py-lg-2{padding-bottom:.5rem!important}.pl-lg-2,.px-lg-2{padding-left:.5rem!important}.p-lg-3{padding:1rem!important}.pt-lg-3,.py-lg-3{padding-top:1rem!important}.pr-lg-3,.px-lg-3{padding-right:1rem!important}.pb-lg-3,.py-lg-3{padding-bottom:1rem!important}.pl-lg-3,.px-lg-3{padding-left:1rem!important}.p-lg-4{padding:1.5rem!important}.pt-lg-4,.py-lg-4{padding-top:1.5rem!important}.pr-lg-4,.px-lg-4{padding-right:1.5rem!important}.pb-lg-4,.py-lg-4{padding-bottom:1.5rem!important}.pl-lg-4,.px-lg-4{padding-left:1.5rem!important}.p-lg-5{padding:3rem!important}.pt-lg-5,.py-lg-5{padding-top:3rem!important}.pr-lg-5,.px-lg-5{padding-right:3rem!important}.pb-lg-5,.py-lg-5{padding-bottom:3rem!important}.pl-lg-5,.px-lg-5{padding-left:3rem!important}.m-lg-n1{margin:-.25rem!important}.mt-lg-n1,.my-lg-n1{margin-top:-.25rem!important}.mr-lg-n1,.mx-lg-n1{margin-right:-.25rem!important}.mb-lg-n1,.my-lg-n1{margin-bottom:-.25rem!important}.ml-lg-n1,.mx-lg-n1{margin-left:-.25rem!important}.m-lg-n2{margin:-.5rem!important}.mt-lg-n2,.my-lg-n2{margin-top:-.5rem!important}.mr-lg-n2,.mx-lg-n2{margin-right:-.5rem!important}.mb-lg-n2,.my-lg-n2{margin-bottom:-.5rem!important}.ml-lg-n2,.mx-lg-n2{margin-left:-.5rem!important}.m-lg-n3{margin:-1rem!important}.mt-lg-n3,.my-lg-n3{margin-top:-1rem!important}.mr-lg-n3,.mx-lg-n3{margin-right:-1rem!important}.mb-lg-n3,.my-lg-n3{margin-bottom:-1rem!important}.ml-lg-n3,.mx-lg-n3{margin-left:-1rem!important}.m-lg-n4{margin:-1.5rem!important}.mt-lg-n4,.my-lg-n4{margin-top:-1.5rem!important}.mr-lg-n4,.mx-lg-n4{margin-right:-1.5rem!important}.mb-lg-n4,.my-lg-n4{margin-bottom:-1.5rem!important}.ml-lg-n4,.mx-lg-n4{margin-left:-1.5rem!important}.m-lg-n5{margin:-3rem!important}.mt-lg-n5,.my-lg-n5{margin-top:-3rem!important}.mr-lg-n5,.mx-lg-n5{margin-right:-3rem!important}.mb-lg-n5,.my-lg-n5{margin-bottom:-3rem!important}.ml-lg-n5,.mx-lg-n5{margin-left:-3rem!important}.m-lg-auto{margin:auto!important}.mt-lg-auto,.my-lg-auto{margin-top:auto!important}.mr-lg-auto,.mx-lg-auto{margin-right:auto!important}.mb-lg-auto,.my-lg-auto{margin-bottom:auto!important}.ml-lg-auto,.mx-lg-auto{margin-left:auto!important}}@media (min-width:1200px){.m-xl-0{margin:0!important}.mt-xl-0,.my-xl-0{margin-top:0!important}.mr-xl-0,.mx-xl-0{margin-right:0!important}.mb-xl-0,.my-xl-0{margin-bottom:0!important}.ml-xl-0,.mx-xl-0{margin-left:0!important}.m-xl-1{margin:.25rem!important}.mt-xl-1,.my-xl-1{margin-top:.25rem!important}.mr-xl-1,.mx-xl-1{margin-right:.25rem!important}.mb-xl-1,.my-xl-1{margin-bottom:.25rem!important}.ml-xl-1,.mx-xl-1{margin-left:.25rem!important}.m-xl-2{margin:.5rem!important}.mt-xl-2,.my-xl-2{margin-top:.5rem!important}.mr-xl-2,.mx-xl-2{margin-right:.5rem!important}.mb-xl-2,.my-xl-2{margin-bottom:.5rem!important}.ml-xl-2,.mx-xl-2{margin-left:.5rem!important}.m-xl-3{margin:1rem!important}.mt-xl-3,.my-xl-3{margin-top:1rem!important}.mr-xl-3,.mx-xl-3{margin-right:1rem!important}.mb-xl-3,.my-xl-3{margin-bottom:1rem!important}.ml-xl-3,.mx-xl-3{margin-left:1rem!important}.m-xl-4{margin:1.5rem!important}.mt-xl-4,.my-xl-4{margin-top:1.5rem!important}.mr-xl-4,.mx-xl-4{margin-right:1.5rem!important}.mb-xl-4,.my-xl-4{margin-bottom:1.5rem!important}.ml-xl-4,.mx-xl-4{margin-left:1.5rem!important}.m-xl-5{margin:3rem!important}.mt-xl-5,.my-xl-5{margin-top:3rem!important}.mr-xl-5,.mx-xl-5{margin-right:3rem!important}.mb-xl-5,.my-xl-5{margin-bottom:3rem!important}.ml-xl-5,.mx-xl-5{margin-left:3rem!important}.p-xl-0{padding:0!important}.pt-xl-0,.py-xl-0{padding-top:0!important}.pr-xl-0,.px-xl-0{padding-right:0!important}.pb-xl-0,.py-xl-0{padding-bottom:0!important}.pl-xl-0,.px-xl-0{padding-left:0!important}.p-xl-1{padding:.25rem!important}.pt-xl-1,.py-xl-1{padding-top:.25rem!important}.pr-xl-1,.px-xl-1{padding-right:.25rem!important}.pb-xl-1,.py-xl-1{padding-bottom:.25rem!important}.pl-xl-1,.px-xl-1{padding-left:.25rem!important}.p-xl-2{padding:.5rem!important}.pt-xl-2,.py-xl-2{padding-top:.5rem!important}.pr-xl-2,.px-xl-2{padding-right:.5rem!important}.pb-xl-2,.py-xl-2{padding-bottom:.5rem!important}.pl-xl-2,.px-xl-2{padding-left:.5rem!important}.p-xl-3{padding:1rem!important}.pt-xl-3,.py-xl-3{padding-top:1rem!important}.pr-xl-3,.px-xl-3{padding-right:1rem!important}.pb-xl-3,.py-xl-3{padding-bottom:1rem!important}.pl-xl-3,.px-xl-3{padding-left:1rem!important}.p-xl-4{padding:1.5rem!important}.pt-xl-4,.py-xl-4{padding-top:1.5rem!important}.pr-xl-4,.px-xl-4{padding-right:1.5rem!important}.pb-xl-4,.py-xl-4{padding-bottom:1.5rem!important}.pl-xl-4,.px-xl-4{padding-left:1.5rem!important}.p-xl-5{padding:3rem!important}.pt-xl-5,.py-xl-5{padding-top:3rem!important}.pr-xl-5,.px-xl-5{padding-right:3rem!important}.pb-xl-5,.py-xl-5{padding-bottom:3rem!important}.pl-xl-5,.px-xl-5{padding-left:3rem!important}.m-xl-n1{margin:-.25rem!important}.mt-xl-n1,.my-xl-n1{margin-top:-.25rem!important}.mr-xl-n1,.mx-xl-n1{margin-right:-.25rem!important}.mb-xl-n1,.my-xl-n1{margin-bottom:-.25rem!important}.ml-xl-n1,.mx-xl-n1{margin-left:-.25rem!important}.m-xl-n2{margin:-.5rem!important}.mt-xl-n2,.my-xl-n2{margin-top:-.5rem!important}.mr-xl-n2,.mx-xl-n2{margin-right:-.5rem!important}.mb-xl-n2,.my-xl-n2{margin-bottom:-.5rem!important}.ml-xl-n2,.mx-xl-n2{margin-left:-.5rem!important}.m-xl-n3{margin:-1rem!important}.mt-xl-n3,.my-xl-n3{margin-top:-1rem!important}.mr-xl-n3,.mx-xl-n3{margin-right:-1rem!important}.mb-xl-n3,.my-xl-n3{margin-bottom:-1rem!important}.ml-xl-n3,.mx-xl-n3{margin-left:-1rem!important}.m-xl-n4{margin:-1.5rem!important}.mt-xl-n4,.my-xl-n4{margin-top:-1.5rem!important}.mr-xl-n4,.mx-xl-n4{margin-right:-1.5rem!important}.mb-xl-n4,.my-xl-n4{margin-bottom:-1.5rem!important}.ml-xl-n4,.mx-xl-n4{margin-left:-1.5rem!important}.m-xl-n5{margin:-3rem!important}.mt-xl-n5,.my-xl-n5{margin-top:-3rem!important}.mr-xl-n5,.mx-xl-n5{margin-right:-3rem!important}.mb-xl-n5,.my-xl-n5{margin-bottom:-3rem!important}.ml-xl-n5,.mx-xl-n5{margin-left:-3rem!important}.m-xl-auto{margin:auto!important}.mt-xl-auto,.my-xl-auto{margin-top:auto!important}.mr-xl-auto,.mx-xl-auto{margin-right:auto!important}.mb-xl-auto,.my-xl-auto{margin-bottom:auto!important}.ml-xl-auto,.mx-xl-auto{margin-left:auto!important}}.text-monospace{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace!important}.text-justify{text-align:justify!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.text-left{text-align:left!important}.text-right{text-align:right!important}.text-center{text-align:center!important}@media (min-width:576px){.text-sm-left{text-align:left!important}.text-sm-right{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.text-md-left{text-align:left!important}.text-md-right{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.text-lg-left{text-align:left!important}.text-lg-right{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.text-xl-left{text-align:left!important}.text-xl-right{text-align:right!important}.text-xl-center{text-align:center!important}}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.font-weight-light{font-weight:300!important}.font-weight-lighter{font-weight:lighter!important}.font-weight-normal{font-weight:400!important}.font-weight-bold{font-weight:700!important}.font-weight-bolder{font-weight:bolder!important}.font-italic{font-style:italic!important}.text-white{color:#fff!important}.text-primary{color:#007bff!important}a.text-primary:focus,a.text-primary:hover{color:#0056b3!important}.text-secondary{color:#6c757d!important}a.text-secondary:focus,a.text-secondary:hover{color:#494f54!important}.text-success{color:#28a745!important}a.text-success:focus,a.text-success:hover{color:#19692c!important}.text-info{color:#17a2b8!important}a.text-info:focus,a.text-info:hover{color:#0f6674!important}.text-warning{color:#ffc107!important}a.text-warning:focus,a.text-warning:hover{color:#ba8b00!important}.text-danger{color:#dc3545!important}a.text-danger:focus,a.text-danger:hover{color:#a71d2a!important}.text-light{color:#f8f9fa!important}a.text-light:focus,a.text-light:hover{color:#cbd3da!important}.text-dark{color:#343a40!important}a.text-dark:focus,a.text-dark:hover{color:#121416!important}.text-body{color:#212529!important}.text-muted{color:#6c757d!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.text-decoration-none{text-decoration:none!important}.text-break{word-break:break-word!important;overflow-wrap:break-word!important}.text-reset{color:inherit!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media print{*,::after,::before{text-shadow:none!important;box-shadow:none!important}a:not(.btn){text-decoration:underline}abbr[title]::after{content:" (" attr(title) ")"}pre{white-space:pre-wrap!important}blockquote,pre{border:1px solid #adb5bd;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}@page{size:a3}body{min-width:992px!important}.container{min-width:992px!important}.navbar{display:none}.badge{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #dee2e6!important}.table-dark{color:inherit}.table-dark tbody+tbody,.table-dark td,.table-dark th,.table-dark thead th{border-color:#dee2e6}.table .thead-dark th{color:inherit;border-color:#dee2e6}} +/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/src/MultiShop/wwwroot/css/bootstrap/bootstrap.min.css.map b/src/MultiShop/wwwroot/css/bootstrap/bootstrap.min.css.map new file mode 100644 index 0000000..1e9cb78 --- /dev/null +++ b/src/MultiShop/wwwroot/css/bootstrap/bootstrap.min.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../scss/bootstrap.scss","../../scss/_root.scss","../../scss/_reboot.scss","dist/css/bootstrap.css","../../scss/vendor/_rfs.scss","bootstrap.css","../../scss/mixins/_hover.scss","../../scss/_type.scss","../../scss/mixins/_lists.scss","../../scss/_images.scss","../../scss/mixins/_image.scss","../../scss/mixins/_border-radius.scss","../../scss/_code.scss","../../scss/_grid.scss","../../scss/mixins/_grid.scss","../../scss/mixins/_breakpoints.scss","../../scss/mixins/_grid-framework.scss","../../scss/_tables.scss","../../scss/mixins/_table-row.scss","../../scss/_forms.scss","../../scss/mixins/_transition.scss","../../scss/mixins/_forms.scss","../../scss/mixins/_gradients.scss","../../scss/_buttons.scss","../../scss/mixins/_buttons.scss","../../scss/_transitions.scss","../../scss/_dropdown.scss","../../scss/mixins/_caret.scss","../../scss/mixins/_nav-divider.scss","../../scss/_button-group.scss","../../scss/_input-group.scss","../../scss/_custom-forms.scss","../../scss/_nav.scss","../../scss/_navbar.scss","../../scss/_card.scss","../../scss/_breadcrumb.scss","../../scss/_pagination.scss","../../scss/mixins/_pagination.scss","../../scss/_badge.scss","../../scss/mixins/_badge.scss","../../scss/_jumbotron.scss","../../scss/_alert.scss","../../scss/mixins/_alert.scss","../../scss/_progress.scss","../../scss/_media.scss","../../scss/_list-group.scss","../../scss/mixins/_list-group.scss","../../scss/_close.scss","../../scss/_toasts.scss","../../scss/_modal.scss","../../scss/_tooltip.scss","../../scss/mixins/_reset-text.scss","../../scss/_popover.scss","../../scss/_carousel.scss","../../scss/mixins/_clearfix.scss","../../scss/_spinners.scss","../../scss/utilities/_align.scss","../../scss/mixins/_background-variant.scss","../../scss/utilities/_background.scss","../../scss/utilities/_borders.scss","../../scss/utilities/_display.scss","../../scss/utilities/_embed.scss","../../scss/utilities/_flex.scss","../../scss/utilities/_float.scss","../../scss/utilities/_overflow.scss","../../scss/utilities/_position.scss","../../scss/utilities/_screenreaders.scss","../../scss/mixins/_screen-reader.scss","../../scss/utilities/_shadows.scss","../../scss/utilities/_sizing.scss","../../scss/utilities/_stretched-link.scss","../../scss/utilities/_spacing.scss","../../scss/utilities/_text.scss","../../scss/mixins/_text-truncate.scss","../../scss/mixins/_text-emphasis.scss","../../scss/mixins/_text-hide.scss","../../scss/utilities/_visibility.scss","../../scss/_print.scss"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Bootstrap v4.3.1 (https://getbootstrap.com/)\n * Copyright 2011-2019 The Bootstrap Authors\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n\n@import \"functions\";\n@import \"variables\";\n@import \"mixins\";\n@import \"root\";\n@import \"reboot\";\n@import \"type\";\n@import \"images\";\n@import \"code\";\n@import \"grid\";\n@import \"tables\";\n@import \"forms\";\n@import \"buttons\";\n@import \"transitions\";\n@import \"dropdown\";\n@import \"button-group\";\n@import \"input-group\";\n@import \"custom-forms\";\n@import \"nav\";\n@import \"navbar\";\n@import \"card\";\n@import \"breadcrumb\";\n@import \"pagination\";\n@import \"badge\";\n@import \"jumbotron\";\n@import \"alert\";\n@import \"progress\";\n@import \"media\";\n@import \"list-group\";\n@import \"close\";\n@import \"toasts\";\n@import \"modal\";\n@import \"tooltip\";\n@import \"popover\";\n@import \"carousel\";\n@import \"spinners\";\n@import \"utilities\";\n@import \"print\";\n",":root {\n // Custom variable values only support SassScript inside `#{}`.\n @each $color, $value in $colors {\n --#{$color}: #{$value};\n }\n\n @each $color, $value in $theme-colors {\n --#{$color}: #{$value};\n }\n\n @each $bp, $value in $grid-breakpoints {\n --breakpoint-#{$bp}: #{$value};\n }\n\n // Use `inspect` for lists so that quoted items keep the quotes.\n // See https://github.com/sass/sass/issues/2383#issuecomment-336349172\n --font-family-sans-serif: #{inspect($font-family-sans-serif)};\n --font-family-monospace: #{inspect($font-family-monospace)};\n}\n","// stylelint-disable at-rule-no-vendor-prefix, declaration-no-important, selector-no-qualifying-type, property-no-vendor-prefix\n\n// Reboot\n//\n// Normalization of HTML elements, manually forked from Normalize.css to remove\n// styles targeting irrelevant browsers while applying new styles.\n//\n// Normalize is licensed MIT. https://github.com/necolas/normalize.css\n\n\n// Document\n//\n// 1. Change from `box-sizing: content-box` so that `width` is not affected by `padding` or `border`.\n// 2. Change the default font family in all browsers.\n// 3. Correct the line height in all browsers.\n// 4. Prevent adjustments of font size after orientation changes in IE on Windows Phone and in iOS.\n// 5. Change the default tap highlight to be completely transparent in iOS.\n\n*,\n*::before,\n*::after {\n box-sizing: border-box; // 1\n}\n\nhtml {\n font-family: sans-serif; // 2\n line-height: 1.15; // 3\n -webkit-text-size-adjust: 100%; // 4\n -webkit-tap-highlight-color: rgba($black, 0); // 5\n}\n\n// Shim for \"new\" HTML5 structural elements to display correctly (IE10, older browsers)\n// TODO: remove in v5\n// stylelint-disable-next-line selector-list-comma-newline-after\narticle, aside, figcaption, figure, footer, header, hgroup, main, nav, section {\n display: block;\n}\n\n// Body\n//\n// 1. Remove the margin in all browsers.\n// 2. As a best practice, apply a default `background-color`.\n// 3. Set an explicit initial text-align value so that we can later use\n// the `inherit` value on things like `` elements.\n\nbody {\n margin: 0; // 1\n font-family: $font-family-base;\n @include font-size($font-size-base);\n font-weight: $font-weight-base;\n line-height: $line-height-base;\n color: $body-color;\n text-align: left; // 3\n background-color: $body-bg; // 2\n}\n\n// Suppress the focus outline on elements that cannot be accessed via keyboard.\n// This prevents an unwanted focus outline from appearing around elements that\n// might still respond to pointer events.\n//\n// Credit: https://github.com/suitcss/base\n[tabindex=\"-1\"]:focus {\n outline: 0 !important;\n}\n\n\n// Content grouping\n//\n// 1. Add the correct box sizing in Firefox.\n// 2. Show the overflow in Edge and IE.\n\nhr {\n box-sizing: content-box; // 1\n height: 0; // 1\n overflow: visible; // 2\n}\n\n\n//\n// Typography\n//\n\n// Remove top margins from headings\n//\n// By default, `

`-`

` all receive top and bottom margins. We nuke the top\n// margin for easier control within type scales as it avoids margin collapsing.\n// stylelint-disable-next-line selector-list-comma-newline-after\nh1, h2, h3, h4, h5, h6 {\n margin-top: 0;\n margin-bottom: $headings-margin-bottom;\n}\n\n// Reset margins on paragraphs\n//\n// Similarly, the top margin on `

`s get reset. However, we also reset the\n// bottom margin to use `rem` units instead of `em`.\np {\n margin-top: 0;\n margin-bottom: $paragraph-margin-bottom;\n}\n\n// Abbreviations\n//\n// 1. Duplicate behavior to the data-* attribute for our tooltip plugin\n// 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.\n// 3. Add explicit cursor to indicate changed behavior.\n// 4. Remove the bottom border in Firefox 39-.\n// 5. Prevent the text-decoration to be skipped.\n\nabbr[title],\nabbr[data-original-title] { // 1\n text-decoration: underline; // 2\n text-decoration: underline dotted; // 2\n cursor: help; // 3\n border-bottom: 0; // 4\n text-decoration-skip-ink: none; // 5\n}\n\naddress {\n margin-bottom: 1rem;\n font-style: normal;\n line-height: inherit;\n}\n\nol,\nul,\ndl {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n margin-bottom: 0;\n}\n\ndt {\n font-weight: $dt-font-weight;\n}\n\ndd {\n margin-bottom: .5rem;\n margin-left: 0; // Undo browser default\n}\n\nblockquote {\n margin: 0 0 1rem;\n}\n\nb,\nstrong {\n font-weight: $font-weight-bolder; // Add the correct font weight in Chrome, Edge, and Safari\n}\n\nsmall {\n @include font-size(80%); // Add the correct font size in all browsers\n}\n\n//\n// Prevent `sub` and `sup` elements from affecting the line height in\n// all browsers.\n//\n\nsub,\nsup {\n position: relative;\n @include font-size(75%);\n line-height: 0;\n vertical-align: baseline;\n}\n\nsub { bottom: -.25em; }\nsup { top: -.5em; }\n\n\n//\n// Links\n//\n\na {\n color: $link-color;\n text-decoration: $link-decoration;\n background-color: transparent; // Remove the gray background on active links in IE 10.\n\n @include hover {\n color: $link-hover-color;\n text-decoration: $link-hover-decoration;\n }\n}\n\n// And undo these styles for placeholder links/named anchors (without href)\n// which have not been made explicitly keyboard-focusable (without tabindex).\n// It would be more straightforward to just use a[href] in previous block, but that\n// causes specificity issues in many other styles that are too complex to fix.\n// See https://github.com/twbs/bootstrap/issues/19402\n\na:not([href]):not([tabindex]) {\n color: inherit;\n text-decoration: none;\n\n @include hover-focus {\n color: inherit;\n text-decoration: none;\n }\n\n &:focus {\n outline: 0;\n }\n}\n\n\n//\n// Code\n//\n\npre,\ncode,\nkbd,\nsamp {\n font-family: $font-family-monospace;\n @include font-size(1em); // Correct the odd `em` font sizing in all browsers.\n}\n\npre {\n // Remove browser default top margin\n margin-top: 0;\n // Reset browser default of `1em` to use `rem`s\n margin-bottom: 1rem;\n // Don't allow content to break outside\n overflow: auto;\n}\n\n\n//\n// Figures\n//\n\nfigure {\n // Apply a consistent margin strategy (matches our type styles).\n margin: 0 0 1rem;\n}\n\n\n//\n// Images and content\n//\n\nimg {\n vertical-align: middle;\n border-style: none; // Remove the border on images inside links in IE 10-.\n}\n\nsvg {\n // Workaround for the SVG overflow bug in IE10/11 is still required.\n // See https://github.com/twbs/bootstrap/issues/26878\n overflow: hidden;\n vertical-align: middle;\n}\n\n\n//\n// Tables\n//\n\ntable {\n border-collapse: collapse; // Prevent double borders\n}\n\ncaption {\n padding-top: $table-cell-padding;\n padding-bottom: $table-cell-padding;\n color: $table-caption-color;\n text-align: left;\n caption-side: bottom;\n}\n\nth {\n // Matches default `` alignment by inheriting from the ``, or the\n // closest parent with a set `text-align`.\n text-align: inherit;\n}\n\n\n//\n// Forms\n//\n\nlabel {\n // Allow labels to use `margin` for spacing.\n display: inline-block;\n margin-bottom: $label-margin-bottom;\n}\n\n// Remove the default `border-radius` that macOS Chrome adds.\n//\n// Details at https://github.com/twbs/bootstrap/issues/24093\nbutton {\n // stylelint-disable-next-line property-blacklist\n border-radius: 0;\n}\n\n// Work around a Firefox/IE bug where the transparent `button` background\n// results in a loss of the default `button` focus styles.\n//\n// Credit: https://github.com/suitcss/base/\nbutton:focus {\n outline: 1px dotted;\n outline: 5px auto -webkit-focus-ring-color;\n}\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n margin: 0; // Remove the margin in Firefox and Safari\n font-family: inherit;\n @include font-size(inherit);\n line-height: inherit;\n}\n\nbutton,\ninput {\n overflow: visible; // Show the overflow in Edge\n}\n\nbutton,\nselect {\n text-transform: none; // Remove the inheritance of text transform in Firefox\n}\n\n// Remove the inheritance of word-wrap in Safari.\n//\n// Details at https://github.com/twbs/bootstrap/issues/24990\nselect {\n word-wrap: normal;\n}\n\n\n// 1. Prevent a WebKit bug where (2) destroys native `audio` and `video`\n// controls in Android 4.\n// 2. Correct the inability to style clickable types in iOS and Safari.\nbutton,\n[type=\"button\"], // 1\n[type=\"reset\"],\n[type=\"submit\"] {\n -webkit-appearance: button; // 2\n}\n\n// Opinionated: add \"hand\" cursor to non-disabled button elements.\n@if $enable-pointer-cursor-for-buttons {\n button,\n [type=\"button\"],\n [type=\"reset\"],\n [type=\"submit\"] {\n &:not(:disabled) {\n cursor: pointer;\n }\n }\n}\n\n// Remove inner border and padding from Firefox, but don't restore the outline like Normalize.\nbutton::-moz-focus-inner,\n[type=\"button\"]::-moz-focus-inner,\n[type=\"reset\"]::-moz-focus-inner,\n[type=\"submit\"]::-moz-focus-inner {\n padding: 0;\n border-style: none;\n}\n\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n box-sizing: border-box; // 1. Add the correct box sizing in IE 10-\n padding: 0; // 2. Remove the padding in IE 10-\n}\n\n\ninput[type=\"date\"],\ninput[type=\"time\"],\ninput[type=\"datetime-local\"],\ninput[type=\"month\"] {\n // Remove the default appearance of temporal inputs to avoid a Mobile Safari\n // bug where setting a custom line-height prevents text from being vertically\n // centered within the input.\n // See https://bugs.webkit.org/show_bug.cgi?id=139848\n // and https://github.com/twbs/bootstrap/issues/11266\n -webkit-appearance: listbox;\n}\n\ntextarea {\n overflow: auto; // Remove the default vertical scrollbar in IE.\n // Textareas should really only resize vertically so they don't break their (horizontal) containers.\n resize: vertical;\n}\n\nfieldset {\n // Browsers set a default `min-width: min-content;` on fieldsets,\n // unlike e.g. `

`s, which have `min-width: 0;` by default.\n // So we reset that to ensure fieldsets behave more like a standard block element.\n // See https://github.com/twbs/bootstrap/issues/12359\n // and https://html.spec.whatwg.org/multipage/#the-fieldset-and-legend-elements\n min-width: 0;\n // Reset the default outline behavior of fieldsets so they don't affect page layout.\n padding: 0;\n margin: 0;\n border: 0;\n}\n\n// 1. Correct the text wrapping in Edge and IE.\n// 2. Correct the color inheritance from `fieldset` elements in IE.\nlegend {\n display: block;\n width: 100%;\n max-width: 100%; // 1\n padding: 0;\n margin-bottom: .5rem;\n @include font-size(1.5rem);\n line-height: inherit;\n color: inherit; // 2\n white-space: normal; // 1\n}\n\nprogress {\n vertical-align: baseline; // Add the correct vertical alignment in Chrome, Firefox, and Opera.\n}\n\n// Correct the cursor style of increment and decrement buttons in Chrome.\n[type=\"number\"]::-webkit-inner-spin-button,\n[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n[type=\"search\"] {\n // This overrides the extra rounded corners on search inputs in iOS so that our\n // `.form-control` class can properly style them. Note that this cannot simply\n // be added to `.form-control` as it's not specific enough. For details, see\n // https://github.com/twbs/bootstrap/issues/11586.\n outline-offset: -2px; // 2. Correct the outline style in Safari.\n -webkit-appearance: none;\n}\n\n//\n// Remove the inner padding in Chrome and Safari on macOS.\n//\n\n[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n//\n// 1. Correct the inability to style clickable types in iOS and Safari.\n// 2. Change font properties to `inherit` in Safari.\n//\n\n::-webkit-file-upload-button {\n font: inherit; // 2\n -webkit-appearance: button; // 1\n}\n\n//\n// Correct element displays\n//\n\noutput {\n display: inline-block;\n}\n\nsummary {\n display: list-item; // Add the correct display in all browsers\n cursor: pointer;\n}\n\ntemplate {\n display: none; // Add the correct display in IE\n}\n\n// Always hide an element with the `hidden` HTML attribute (from PureCSS).\n// Needed for proper display in IE 10-.\n[hidden] {\n display: none !important;\n}\n","/*!\n * Bootstrap v4.3.1 (https://getbootstrap.com/)\n * Copyright 2011-2019 The Bootstrap Authors\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n:root {\n --blue: #007bff;\n --indigo: #6610f2;\n --purple: #6f42c1;\n --pink: #e83e8c;\n --red: #dc3545;\n --orange: #fd7e14;\n --yellow: #ffc107;\n --green: #28a745;\n --teal: #20c997;\n --cyan: #17a2b8;\n --white: #fff;\n --gray: #6c757d;\n --gray-dark: #343a40;\n --primary: #007bff;\n --secondary: #6c757d;\n --success: #28a745;\n --info: #17a2b8;\n --warning: #ffc107;\n --danger: #dc3545;\n --light: #f8f9fa;\n --dark: #343a40;\n --breakpoint-xs: 0;\n --breakpoint-sm: 576px;\n --breakpoint-md: 768px;\n --breakpoint-lg: 992px;\n --breakpoint-xl: 1200px;\n --font-family-sans-serif: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n --font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n}\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\nhtml {\n font-family: sans-serif;\n line-height: 1.15;\n -webkit-text-size-adjust: 100%;\n -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\n\narticle, aside, figcaption, figure, footer, header, hgroup, main, nav, section {\n display: block;\n}\n\nbody {\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: #212529;\n text-align: left;\n background-color: #fff;\n}\n\n[tabindex=\"-1\"]:focus {\n outline: 0 !important;\n}\n\nhr {\n box-sizing: content-box;\n height: 0;\n overflow: visible;\n}\n\nh1, h2, h3, h4, h5, h6 {\n margin-top: 0;\n margin-bottom: 0.5rem;\n}\n\np {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nabbr[title],\nabbr[data-original-title] {\n text-decoration: underline;\n -webkit-text-decoration: underline dotted;\n text-decoration: underline dotted;\n cursor: help;\n border-bottom: 0;\n -webkit-text-decoration-skip-ink: none;\n text-decoration-skip-ink: none;\n}\n\naddress {\n margin-bottom: 1rem;\n font-style: normal;\n line-height: inherit;\n}\n\nol,\nul,\ndl {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n margin-bottom: 0;\n}\n\ndt {\n font-weight: 700;\n}\n\ndd {\n margin-bottom: .5rem;\n margin-left: 0;\n}\n\nblockquote {\n margin: 0 0 1rem;\n}\n\nb,\nstrong {\n font-weight: bolder;\n}\n\nsmall {\n font-size: 80%;\n}\n\nsub,\nsup {\n position: relative;\n font-size: 75%;\n line-height: 0;\n vertical-align: baseline;\n}\n\nsub {\n bottom: -.25em;\n}\n\nsup {\n top: -.5em;\n}\n\na {\n color: #007bff;\n text-decoration: none;\n background-color: transparent;\n}\n\na:hover {\n color: #0056b3;\n text-decoration: underline;\n}\n\na:not([href]):not([tabindex]) {\n color: inherit;\n text-decoration: none;\n}\n\na:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus {\n color: inherit;\n text-decoration: none;\n}\n\na:not([href]):not([tabindex]):focus {\n outline: 0;\n}\n\npre,\ncode,\nkbd,\nsamp {\n font-family: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n font-size: 1em;\n}\n\npre {\n margin-top: 0;\n margin-bottom: 1rem;\n overflow: auto;\n}\n\nfigure {\n margin: 0 0 1rem;\n}\n\nimg {\n vertical-align: middle;\n border-style: none;\n}\n\nsvg {\n overflow: hidden;\n vertical-align: middle;\n}\n\ntable {\n border-collapse: collapse;\n}\n\ncaption {\n padding-top: 0.75rem;\n padding-bottom: 0.75rem;\n color: #6c757d;\n text-align: left;\n caption-side: bottom;\n}\n\nth {\n text-align: inherit;\n}\n\nlabel {\n display: inline-block;\n margin-bottom: 0.5rem;\n}\n\nbutton {\n border-radius: 0;\n}\n\nbutton:focus {\n outline: 1px dotted;\n outline: 5px auto -webkit-focus-ring-color;\n}\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n margin: 0;\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\n\nbutton,\ninput {\n overflow: visible;\n}\n\nbutton,\nselect {\n text-transform: none;\n}\n\nselect {\n word-wrap: normal;\n}\n\nbutton,\n[type=\"button\"],\n[type=\"reset\"],\n[type=\"submit\"] {\n -webkit-appearance: button;\n}\n\nbutton:not(:disabled),\n[type=\"button\"]:not(:disabled),\n[type=\"reset\"]:not(:disabled),\n[type=\"submit\"]:not(:disabled) {\n cursor: pointer;\n}\n\nbutton::-moz-focus-inner,\n[type=\"button\"]::-moz-focus-inner,\n[type=\"reset\"]::-moz-focus-inner,\n[type=\"submit\"]::-moz-focus-inner {\n padding: 0;\n border-style: none;\n}\n\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n box-sizing: border-box;\n padding: 0;\n}\n\ninput[type=\"date\"],\ninput[type=\"time\"],\ninput[type=\"datetime-local\"],\ninput[type=\"month\"] {\n -webkit-appearance: listbox;\n}\n\ntextarea {\n overflow: auto;\n resize: vertical;\n}\n\nfieldset {\n min-width: 0;\n padding: 0;\n margin: 0;\n border: 0;\n}\n\nlegend {\n display: block;\n width: 100%;\n max-width: 100%;\n padding: 0;\n margin-bottom: .5rem;\n font-size: 1.5rem;\n line-height: inherit;\n color: inherit;\n white-space: normal;\n}\n\nprogress {\n vertical-align: baseline;\n}\n\n[type=\"number\"]::-webkit-inner-spin-button,\n[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n[type=\"search\"] {\n outline-offset: -2px;\n -webkit-appearance: none;\n}\n\n[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n::-webkit-file-upload-button {\n font: inherit;\n -webkit-appearance: button;\n}\n\noutput {\n display: inline-block;\n}\n\nsummary {\n display: list-item;\n cursor: pointer;\n}\n\ntemplate {\n display: none;\n}\n\n[hidden] {\n display: none !important;\n}\n\nh1, h2, h3, h4, h5, h6,\n.h1, .h2, .h3, .h4, .h5, .h6 {\n margin-bottom: 0.5rem;\n font-weight: 500;\n line-height: 1.2;\n}\n\nh1, .h1 {\n font-size: 2.5rem;\n}\n\nh2, .h2 {\n font-size: 2rem;\n}\n\nh3, .h3 {\n font-size: 1.75rem;\n}\n\nh4, .h4 {\n font-size: 1.5rem;\n}\n\nh5, .h5 {\n font-size: 1.25rem;\n}\n\nh6, .h6 {\n font-size: 1rem;\n}\n\n.lead {\n font-size: 1.25rem;\n font-weight: 300;\n}\n\n.display-1 {\n font-size: 6rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\n.display-2 {\n font-size: 5.5rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\n.display-3 {\n font-size: 4.5rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\n.display-4 {\n font-size: 3.5rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\nhr {\n margin-top: 1rem;\n margin-bottom: 1rem;\n border: 0;\n border-top: 1px solid rgba(0, 0, 0, 0.1);\n}\n\nsmall,\n.small {\n font-size: 80%;\n font-weight: 400;\n}\n\nmark,\n.mark {\n padding: 0.2em;\n background-color: #fcf8e3;\n}\n\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n\n.list-inline {\n padding-left: 0;\n list-style: none;\n}\n\n.list-inline-item {\n display: inline-block;\n}\n\n.list-inline-item:not(:last-child) {\n margin-right: 0.5rem;\n}\n\n.initialism {\n font-size: 90%;\n text-transform: uppercase;\n}\n\n.blockquote {\n margin-bottom: 1rem;\n font-size: 1.25rem;\n}\n\n.blockquote-footer {\n display: block;\n font-size: 80%;\n color: #6c757d;\n}\n\n.blockquote-footer::before {\n content: \"\\2014\\00A0\";\n}\n\n.img-fluid {\n max-width: 100%;\n height: auto;\n}\n\n.img-thumbnail {\n padding: 0.25rem;\n background-color: #fff;\n border: 1px solid #dee2e6;\n border-radius: 0.25rem;\n max-width: 100%;\n height: auto;\n}\n\n.figure {\n display: inline-block;\n}\n\n.figure-img {\n margin-bottom: 0.5rem;\n line-height: 1;\n}\n\n.figure-caption {\n font-size: 90%;\n color: #6c757d;\n}\n\ncode {\n font-size: 87.5%;\n color: #e83e8c;\n word-break: break-word;\n}\n\na > code {\n color: inherit;\n}\n\nkbd {\n padding: 0.2rem 0.4rem;\n font-size: 87.5%;\n color: #fff;\n background-color: #212529;\n border-radius: 0.2rem;\n}\n\nkbd kbd {\n padding: 0;\n font-size: 100%;\n font-weight: 700;\n}\n\npre {\n display: block;\n font-size: 87.5%;\n color: #212529;\n}\n\npre code {\n font-size: inherit;\n color: inherit;\n word-break: normal;\n}\n\n.pre-scrollable {\n max-height: 340px;\n overflow-y: scroll;\n}\n\n.container {\n width: 100%;\n padding-right: 15px;\n padding-left: 15px;\n margin-right: auto;\n margin-left: auto;\n}\n\n@media (min-width: 576px) {\n .container {\n max-width: 540px;\n }\n}\n\n@media (min-width: 768px) {\n .container {\n max-width: 720px;\n }\n}\n\n@media (min-width: 992px) {\n .container {\n max-width: 960px;\n }\n}\n\n@media (min-width: 1200px) {\n .container {\n max-width: 1140px;\n }\n}\n\n.container-fluid {\n width: 100%;\n padding-right: 15px;\n padding-left: 15px;\n margin-right: auto;\n margin-left: auto;\n}\n\n.row {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n margin-right: -15px;\n margin-left: -15px;\n}\n\n.no-gutters {\n margin-right: 0;\n margin-left: 0;\n}\n\n.no-gutters > .col,\n.no-gutters > [class*=\"col-\"] {\n padding-right: 0;\n padding-left: 0;\n}\n\n.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col,\n.col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm,\n.col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md,\n.col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg,\n.col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl,\n.col-xl-auto {\n position: relative;\n width: 100%;\n padding-right: 15px;\n padding-left: 15px;\n}\n\n.col {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n -ms-flex-positive: 1;\n flex-grow: 1;\n max-width: 100%;\n}\n\n.col-auto {\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n}\n\n.col-1 {\n -ms-flex: 0 0 8.333333%;\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n}\n\n.col-2 {\n -ms-flex: 0 0 16.666667%;\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n}\n\n.col-3 {\n -ms-flex: 0 0 25%;\n flex: 0 0 25%;\n max-width: 25%;\n}\n\n.col-4 {\n -ms-flex: 0 0 33.333333%;\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n}\n\n.col-5 {\n -ms-flex: 0 0 41.666667%;\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n}\n\n.col-6 {\n -ms-flex: 0 0 50%;\n flex: 0 0 50%;\n max-width: 50%;\n}\n\n.col-7 {\n -ms-flex: 0 0 58.333333%;\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n}\n\n.col-8 {\n -ms-flex: 0 0 66.666667%;\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n}\n\n.col-9 {\n -ms-flex: 0 0 75%;\n flex: 0 0 75%;\n max-width: 75%;\n}\n\n.col-10 {\n -ms-flex: 0 0 83.333333%;\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n}\n\n.col-11 {\n -ms-flex: 0 0 91.666667%;\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n}\n\n.col-12 {\n -ms-flex: 0 0 100%;\n flex: 0 0 100%;\n max-width: 100%;\n}\n\n.order-first {\n -ms-flex-order: -1;\n order: -1;\n}\n\n.order-last {\n -ms-flex-order: 13;\n order: 13;\n}\n\n.order-0 {\n -ms-flex-order: 0;\n order: 0;\n}\n\n.order-1 {\n -ms-flex-order: 1;\n order: 1;\n}\n\n.order-2 {\n -ms-flex-order: 2;\n order: 2;\n}\n\n.order-3 {\n -ms-flex-order: 3;\n order: 3;\n}\n\n.order-4 {\n -ms-flex-order: 4;\n order: 4;\n}\n\n.order-5 {\n -ms-flex-order: 5;\n order: 5;\n}\n\n.order-6 {\n -ms-flex-order: 6;\n order: 6;\n}\n\n.order-7 {\n -ms-flex-order: 7;\n order: 7;\n}\n\n.order-8 {\n -ms-flex-order: 8;\n order: 8;\n}\n\n.order-9 {\n -ms-flex-order: 9;\n order: 9;\n}\n\n.order-10 {\n -ms-flex-order: 10;\n order: 10;\n}\n\n.order-11 {\n -ms-flex-order: 11;\n order: 11;\n}\n\n.order-12 {\n -ms-flex-order: 12;\n order: 12;\n}\n\n.offset-1 {\n margin-left: 8.333333%;\n}\n\n.offset-2 {\n margin-left: 16.666667%;\n}\n\n.offset-3 {\n margin-left: 25%;\n}\n\n.offset-4 {\n margin-left: 33.333333%;\n}\n\n.offset-5 {\n margin-left: 41.666667%;\n}\n\n.offset-6 {\n margin-left: 50%;\n}\n\n.offset-7 {\n margin-left: 58.333333%;\n}\n\n.offset-8 {\n margin-left: 66.666667%;\n}\n\n.offset-9 {\n margin-left: 75%;\n}\n\n.offset-10 {\n margin-left: 83.333333%;\n}\n\n.offset-11 {\n margin-left: 91.666667%;\n}\n\n@media (min-width: 576px) {\n .col-sm {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n -ms-flex-positive: 1;\n flex-grow: 1;\n max-width: 100%;\n }\n .col-sm-auto {\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n }\n .col-sm-1 {\n -ms-flex: 0 0 8.333333%;\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-sm-2 {\n -ms-flex: 0 0 16.666667%;\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-sm-3 {\n -ms-flex: 0 0 25%;\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-sm-4 {\n -ms-flex: 0 0 33.333333%;\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-sm-5 {\n -ms-flex: 0 0 41.666667%;\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-sm-6 {\n -ms-flex: 0 0 50%;\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-sm-7 {\n -ms-flex: 0 0 58.333333%;\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-sm-8 {\n -ms-flex: 0 0 66.666667%;\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-sm-9 {\n -ms-flex: 0 0 75%;\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-sm-10 {\n -ms-flex: 0 0 83.333333%;\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-sm-11 {\n -ms-flex: 0 0 91.666667%;\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-sm-12 {\n -ms-flex: 0 0 100%;\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-sm-first {\n -ms-flex-order: -1;\n order: -1;\n }\n .order-sm-last {\n -ms-flex-order: 13;\n order: 13;\n }\n .order-sm-0 {\n -ms-flex-order: 0;\n order: 0;\n }\n .order-sm-1 {\n -ms-flex-order: 1;\n order: 1;\n }\n .order-sm-2 {\n -ms-flex-order: 2;\n order: 2;\n }\n .order-sm-3 {\n -ms-flex-order: 3;\n order: 3;\n }\n .order-sm-4 {\n -ms-flex-order: 4;\n order: 4;\n }\n .order-sm-5 {\n -ms-flex-order: 5;\n order: 5;\n }\n .order-sm-6 {\n -ms-flex-order: 6;\n order: 6;\n }\n .order-sm-7 {\n -ms-flex-order: 7;\n order: 7;\n }\n .order-sm-8 {\n -ms-flex-order: 8;\n order: 8;\n }\n .order-sm-9 {\n -ms-flex-order: 9;\n order: 9;\n }\n .order-sm-10 {\n -ms-flex-order: 10;\n order: 10;\n }\n .order-sm-11 {\n -ms-flex-order: 11;\n order: 11;\n }\n .order-sm-12 {\n -ms-flex-order: 12;\n order: 12;\n }\n .offset-sm-0 {\n margin-left: 0;\n }\n .offset-sm-1 {\n margin-left: 8.333333%;\n }\n .offset-sm-2 {\n margin-left: 16.666667%;\n }\n .offset-sm-3 {\n margin-left: 25%;\n }\n .offset-sm-4 {\n margin-left: 33.333333%;\n }\n .offset-sm-5 {\n margin-left: 41.666667%;\n }\n .offset-sm-6 {\n margin-left: 50%;\n }\n .offset-sm-7 {\n margin-left: 58.333333%;\n }\n .offset-sm-8 {\n margin-left: 66.666667%;\n }\n .offset-sm-9 {\n margin-left: 75%;\n }\n .offset-sm-10 {\n margin-left: 83.333333%;\n }\n .offset-sm-11 {\n margin-left: 91.666667%;\n }\n}\n\n@media (min-width: 768px) {\n .col-md {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n -ms-flex-positive: 1;\n flex-grow: 1;\n max-width: 100%;\n }\n .col-md-auto {\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n }\n .col-md-1 {\n -ms-flex: 0 0 8.333333%;\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-md-2 {\n -ms-flex: 0 0 16.666667%;\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-md-3 {\n -ms-flex: 0 0 25%;\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-md-4 {\n -ms-flex: 0 0 33.333333%;\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-md-5 {\n -ms-flex: 0 0 41.666667%;\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-md-6 {\n -ms-flex: 0 0 50%;\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-md-7 {\n -ms-flex: 0 0 58.333333%;\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-md-8 {\n -ms-flex: 0 0 66.666667%;\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-md-9 {\n -ms-flex: 0 0 75%;\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-md-10 {\n -ms-flex: 0 0 83.333333%;\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-md-11 {\n -ms-flex: 0 0 91.666667%;\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-md-12 {\n -ms-flex: 0 0 100%;\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-md-first {\n -ms-flex-order: -1;\n order: -1;\n }\n .order-md-last {\n -ms-flex-order: 13;\n order: 13;\n }\n .order-md-0 {\n -ms-flex-order: 0;\n order: 0;\n }\n .order-md-1 {\n -ms-flex-order: 1;\n order: 1;\n }\n .order-md-2 {\n -ms-flex-order: 2;\n order: 2;\n }\n .order-md-3 {\n -ms-flex-order: 3;\n order: 3;\n }\n .order-md-4 {\n -ms-flex-order: 4;\n order: 4;\n }\n .order-md-5 {\n -ms-flex-order: 5;\n order: 5;\n }\n .order-md-6 {\n -ms-flex-order: 6;\n order: 6;\n }\n .order-md-7 {\n -ms-flex-order: 7;\n order: 7;\n }\n .order-md-8 {\n -ms-flex-order: 8;\n order: 8;\n }\n .order-md-9 {\n -ms-flex-order: 9;\n order: 9;\n }\n .order-md-10 {\n -ms-flex-order: 10;\n order: 10;\n }\n .order-md-11 {\n -ms-flex-order: 11;\n order: 11;\n }\n .order-md-12 {\n -ms-flex-order: 12;\n order: 12;\n }\n .offset-md-0 {\n margin-left: 0;\n }\n .offset-md-1 {\n margin-left: 8.333333%;\n }\n .offset-md-2 {\n margin-left: 16.666667%;\n }\n .offset-md-3 {\n margin-left: 25%;\n }\n .offset-md-4 {\n margin-left: 33.333333%;\n }\n .offset-md-5 {\n margin-left: 41.666667%;\n }\n .offset-md-6 {\n margin-left: 50%;\n }\n .offset-md-7 {\n margin-left: 58.333333%;\n }\n .offset-md-8 {\n margin-left: 66.666667%;\n }\n .offset-md-9 {\n margin-left: 75%;\n }\n .offset-md-10 {\n margin-left: 83.333333%;\n }\n .offset-md-11 {\n margin-left: 91.666667%;\n }\n}\n\n@media (min-width: 992px) {\n .col-lg {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n -ms-flex-positive: 1;\n flex-grow: 1;\n max-width: 100%;\n }\n .col-lg-auto {\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n }\n .col-lg-1 {\n -ms-flex: 0 0 8.333333%;\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-lg-2 {\n -ms-flex: 0 0 16.666667%;\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-lg-3 {\n -ms-flex: 0 0 25%;\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-lg-4 {\n -ms-flex: 0 0 33.333333%;\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-lg-5 {\n -ms-flex: 0 0 41.666667%;\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-lg-6 {\n -ms-flex: 0 0 50%;\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-lg-7 {\n -ms-flex: 0 0 58.333333%;\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-lg-8 {\n -ms-flex: 0 0 66.666667%;\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-lg-9 {\n -ms-flex: 0 0 75%;\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-lg-10 {\n -ms-flex: 0 0 83.333333%;\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-lg-11 {\n -ms-flex: 0 0 91.666667%;\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-lg-12 {\n -ms-flex: 0 0 100%;\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-lg-first {\n -ms-flex-order: -1;\n order: -1;\n }\n .order-lg-last {\n -ms-flex-order: 13;\n order: 13;\n }\n .order-lg-0 {\n -ms-flex-order: 0;\n order: 0;\n }\n .order-lg-1 {\n -ms-flex-order: 1;\n order: 1;\n }\n .order-lg-2 {\n -ms-flex-order: 2;\n order: 2;\n }\n .order-lg-3 {\n -ms-flex-order: 3;\n order: 3;\n }\n .order-lg-4 {\n -ms-flex-order: 4;\n order: 4;\n }\n .order-lg-5 {\n -ms-flex-order: 5;\n order: 5;\n }\n .order-lg-6 {\n -ms-flex-order: 6;\n order: 6;\n }\n .order-lg-7 {\n -ms-flex-order: 7;\n order: 7;\n }\n .order-lg-8 {\n -ms-flex-order: 8;\n order: 8;\n }\n .order-lg-9 {\n -ms-flex-order: 9;\n order: 9;\n }\n .order-lg-10 {\n -ms-flex-order: 10;\n order: 10;\n }\n .order-lg-11 {\n -ms-flex-order: 11;\n order: 11;\n }\n .order-lg-12 {\n -ms-flex-order: 12;\n order: 12;\n }\n .offset-lg-0 {\n margin-left: 0;\n }\n .offset-lg-1 {\n margin-left: 8.333333%;\n }\n .offset-lg-2 {\n margin-left: 16.666667%;\n }\n .offset-lg-3 {\n margin-left: 25%;\n }\n .offset-lg-4 {\n margin-left: 33.333333%;\n }\n .offset-lg-5 {\n margin-left: 41.666667%;\n }\n .offset-lg-6 {\n margin-left: 50%;\n }\n .offset-lg-7 {\n margin-left: 58.333333%;\n }\n .offset-lg-8 {\n margin-left: 66.666667%;\n }\n .offset-lg-9 {\n margin-left: 75%;\n }\n .offset-lg-10 {\n margin-left: 83.333333%;\n }\n .offset-lg-11 {\n margin-left: 91.666667%;\n }\n}\n\n@media (min-width: 1200px) {\n .col-xl {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n -ms-flex-positive: 1;\n flex-grow: 1;\n max-width: 100%;\n }\n .col-xl-auto {\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n }\n .col-xl-1 {\n -ms-flex: 0 0 8.333333%;\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-xl-2 {\n -ms-flex: 0 0 16.666667%;\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-xl-3 {\n -ms-flex: 0 0 25%;\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-xl-4 {\n -ms-flex: 0 0 33.333333%;\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-xl-5 {\n -ms-flex: 0 0 41.666667%;\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-xl-6 {\n -ms-flex: 0 0 50%;\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-xl-7 {\n -ms-flex: 0 0 58.333333%;\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-xl-8 {\n -ms-flex: 0 0 66.666667%;\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-xl-9 {\n -ms-flex: 0 0 75%;\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-xl-10 {\n -ms-flex: 0 0 83.333333%;\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-xl-11 {\n -ms-flex: 0 0 91.666667%;\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-xl-12 {\n -ms-flex: 0 0 100%;\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-xl-first {\n -ms-flex-order: -1;\n order: -1;\n }\n .order-xl-last {\n -ms-flex-order: 13;\n order: 13;\n }\n .order-xl-0 {\n -ms-flex-order: 0;\n order: 0;\n }\n .order-xl-1 {\n -ms-flex-order: 1;\n order: 1;\n }\n .order-xl-2 {\n -ms-flex-order: 2;\n order: 2;\n }\n .order-xl-3 {\n -ms-flex-order: 3;\n order: 3;\n }\n .order-xl-4 {\n -ms-flex-order: 4;\n order: 4;\n }\n .order-xl-5 {\n -ms-flex-order: 5;\n order: 5;\n }\n .order-xl-6 {\n -ms-flex-order: 6;\n order: 6;\n }\n .order-xl-7 {\n -ms-flex-order: 7;\n order: 7;\n }\n .order-xl-8 {\n -ms-flex-order: 8;\n order: 8;\n }\n .order-xl-9 {\n -ms-flex-order: 9;\n order: 9;\n }\n .order-xl-10 {\n -ms-flex-order: 10;\n order: 10;\n }\n .order-xl-11 {\n -ms-flex-order: 11;\n order: 11;\n }\n .order-xl-12 {\n -ms-flex-order: 12;\n order: 12;\n }\n .offset-xl-0 {\n margin-left: 0;\n }\n .offset-xl-1 {\n margin-left: 8.333333%;\n }\n .offset-xl-2 {\n margin-left: 16.666667%;\n }\n .offset-xl-3 {\n margin-left: 25%;\n }\n .offset-xl-4 {\n margin-left: 33.333333%;\n }\n .offset-xl-5 {\n margin-left: 41.666667%;\n }\n .offset-xl-6 {\n margin-left: 50%;\n }\n .offset-xl-7 {\n margin-left: 58.333333%;\n }\n .offset-xl-8 {\n margin-left: 66.666667%;\n }\n .offset-xl-9 {\n margin-left: 75%;\n }\n .offset-xl-10 {\n margin-left: 83.333333%;\n }\n .offset-xl-11 {\n margin-left: 91.666667%;\n }\n}\n\n.table {\n width: 100%;\n margin-bottom: 1rem;\n color: #212529;\n}\n\n.table th,\n.table td {\n padding: 0.75rem;\n vertical-align: top;\n border-top: 1px solid #dee2e6;\n}\n\n.table thead th {\n vertical-align: bottom;\n border-bottom: 2px solid #dee2e6;\n}\n\n.table tbody + tbody {\n border-top: 2px solid #dee2e6;\n}\n\n.table-sm th,\n.table-sm td {\n padding: 0.3rem;\n}\n\n.table-bordered {\n border: 1px solid #dee2e6;\n}\n\n.table-bordered th,\n.table-bordered td {\n border: 1px solid #dee2e6;\n}\n\n.table-bordered thead th,\n.table-bordered thead td {\n border-bottom-width: 2px;\n}\n\n.table-borderless th,\n.table-borderless td,\n.table-borderless thead th,\n.table-borderless tbody + tbody {\n border: 0;\n}\n\n.table-striped tbody tr:nth-of-type(odd) {\n background-color: rgba(0, 0, 0, 0.05);\n}\n\n.table-hover tbody tr:hover {\n color: #212529;\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table-primary,\n.table-primary > th,\n.table-primary > td {\n background-color: #b8daff;\n}\n\n.table-primary th,\n.table-primary td,\n.table-primary thead th,\n.table-primary tbody + tbody {\n border-color: #7abaff;\n}\n\n.table-hover .table-primary:hover {\n background-color: #9fcdff;\n}\n\n.table-hover .table-primary:hover > td,\n.table-hover .table-primary:hover > th {\n background-color: #9fcdff;\n}\n\n.table-secondary,\n.table-secondary > th,\n.table-secondary > td {\n background-color: #d6d8db;\n}\n\n.table-secondary th,\n.table-secondary td,\n.table-secondary thead th,\n.table-secondary tbody + tbody {\n border-color: #b3b7bb;\n}\n\n.table-hover .table-secondary:hover {\n background-color: #c8cbcf;\n}\n\n.table-hover .table-secondary:hover > td,\n.table-hover .table-secondary:hover > th {\n background-color: #c8cbcf;\n}\n\n.table-success,\n.table-success > th,\n.table-success > td {\n background-color: #c3e6cb;\n}\n\n.table-success th,\n.table-success td,\n.table-success thead th,\n.table-success tbody + tbody {\n border-color: #8fd19e;\n}\n\n.table-hover .table-success:hover {\n background-color: #b1dfbb;\n}\n\n.table-hover .table-success:hover > td,\n.table-hover .table-success:hover > th {\n background-color: #b1dfbb;\n}\n\n.table-info,\n.table-info > th,\n.table-info > td {\n background-color: #bee5eb;\n}\n\n.table-info th,\n.table-info td,\n.table-info thead th,\n.table-info tbody + tbody {\n border-color: #86cfda;\n}\n\n.table-hover .table-info:hover {\n background-color: #abdde5;\n}\n\n.table-hover .table-info:hover > td,\n.table-hover .table-info:hover > th {\n background-color: #abdde5;\n}\n\n.table-warning,\n.table-warning > th,\n.table-warning > td {\n background-color: #ffeeba;\n}\n\n.table-warning th,\n.table-warning td,\n.table-warning thead th,\n.table-warning tbody + tbody {\n border-color: #ffdf7e;\n}\n\n.table-hover .table-warning:hover {\n background-color: #ffe8a1;\n}\n\n.table-hover .table-warning:hover > td,\n.table-hover .table-warning:hover > th {\n background-color: #ffe8a1;\n}\n\n.table-danger,\n.table-danger > th,\n.table-danger > td {\n background-color: #f5c6cb;\n}\n\n.table-danger th,\n.table-danger td,\n.table-danger thead th,\n.table-danger tbody + tbody {\n border-color: #ed969e;\n}\n\n.table-hover .table-danger:hover {\n background-color: #f1b0b7;\n}\n\n.table-hover .table-danger:hover > td,\n.table-hover .table-danger:hover > th {\n background-color: #f1b0b7;\n}\n\n.table-light,\n.table-light > th,\n.table-light > td {\n background-color: #fdfdfe;\n}\n\n.table-light th,\n.table-light td,\n.table-light thead th,\n.table-light tbody + tbody {\n border-color: #fbfcfc;\n}\n\n.table-hover .table-light:hover {\n background-color: #ececf6;\n}\n\n.table-hover .table-light:hover > td,\n.table-hover .table-light:hover > th {\n background-color: #ececf6;\n}\n\n.table-dark,\n.table-dark > th,\n.table-dark > td {\n background-color: #c6c8ca;\n}\n\n.table-dark th,\n.table-dark td,\n.table-dark thead th,\n.table-dark tbody + tbody {\n border-color: #95999c;\n}\n\n.table-hover .table-dark:hover {\n background-color: #b9bbbe;\n}\n\n.table-hover .table-dark:hover > td,\n.table-hover .table-dark:hover > th {\n background-color: #b9bbbe;\n}\n\n.table-active,\n.table-active > th,\n.table-active > td {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table-hover .table-active:hover {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table-hover .table-active:hover > td,\n.table-hover .table-active:hover > th {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table .thead-dark th {\n color: #fff;\n background-color: #343a40;\n border-color: #454d55;\n}\n\n.table .thead-light th {\n color: #495057;\n background-color: #e9ecef;\n border-color: #dee2e6;\n}\n\n.table-dark {\n color: #fff;\n background-color: #343a40;\n}\n\n.table-dark th,\n.table-dark td,\n.table-dark thead th {\n border-color: #454d55;\n}\n\n.table-dark.table-bordered {\n border: 0;\n}\n\n.table-dark.table-striped tbody tr:nth-of-type(odd) {\n background-color: rgba(255, 255, 255, 0.05);\n}\n\n.table-dark.table-hover tbody tr:hover {\n color: #fff;\n background-color: rgba(255, 255, 255, 0.075);\n}\n\n@media (max-width: 575.98px) {\n .table-responsive-sm {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n }\n .table-responsive-sm > .table-bordered {\n border: 0;\n }\n}\n\n@media (max-width: 767.98px) {\n .table-responsive-md {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n }\n .table-responsive-md > .table-bordered {\n border: 0;\n }\n}\n\n@media (max-width: 991.98px) {\n .table-responsive-lg {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n }\n .table-responsive-lg > .table-bordered {\n border: 0;\n }\n}\n\n@media (max-width: 1199.98px) {\n .table-responsive-xl {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n }\n .table-responsive-xl > .table-bordered {\n border: 0;\n }\n}\n\n.table-responsive {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n}\n\n.table-responsive > .table-bordered {\n border: 0;\n}\n\n.form-control {\n display: block;\n width: 100%;\n height: calc(1.5em + 0.75rem + 2px);\n padding: 0.375rem 0.75rem;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: #495057;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .form-control {\n transition: none;\n }\n}\n\n.form-control::-ms-expand {\n background-color: transparent;\n border: 0;\n}\n\n.form-control:focus {\n color: #495057;\n background-color: #fff;\n border-color: #80bdff;\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.form-control::-webkit-input-placeholder {\n color: #6c757d;\n opacity: 1;\n}\n\n.form-control::-moz-placeholder {\n color: #6c757d;\n opacity: 1;\n}\n\n.form-control:-ms-input-placeholder {\n color: #6c757d;\n opacity: 1;\n}\n\n.form-control::-ms-input-placeholder {\n color: #6c757d;\n opacity: 1;\n}\n\n.form-control::placeholder {\n color: #6c757d;\n opacity: 1;\n}\n\n.form-control:disabled, .form-control[readonly] {\n background-color: #e9ecef;\n opacity: 1;\n}\n\nselect.form-control:focus::-ms-value {\n color: #495057;\n background-color: #fff;\n}\n\n.form-control-file,\n.form-control-range {\n display: block;\n width: 100%;\n}\n\n.col-form-label {\n padding-top: calc(0.375rem + 1px);\n padding-bottom: calc(0.375rem + 1px);\n margin-bottom: 0;\n font-size: inherit;\n line-height: 1.5;\n}\n\n.col-form-label-lg {\n padding-top: calc(0.5rem + 1px);\n padding-bottom: calc(0.5rem + 1px);\n font-size: 1.25rem;\n line-height: 1.5;\n}\n\n.col-form-label-sm {\n padding-top: calc(0.25rem + 1px);\n padding-bottom: calc(0.25rem + 1px);\n font-size: 0.875rem;\n line-height: 1.5;\n}\n\n.form-control-plaintext {\n display: block;\n width: 100%;\n padding-top: 0.375rem;\n padding-bottom: 0.375rem;\n margin-bottom: 0;\n line-height: 1.5;\n color: #212529;\n background-color: transparent;\n border: solid transparent;\n border-width: 1px 0;\n}\n\n.form-control-plaintext.form-control-sm, .form-control-plaintext.form-control-lg {\n padding-right: 0;\n padding-left: 0;\n}\n\n.form-control-sm {\n height: calc(1.5em + 0.5rem + 2px);\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n border-radius: 0.2rem;\n}\n\n.form-control-lg {\n height: calc(1.5em + 1rem + 2px);\n padding: 0.5rem 1rem;\n font-size: 1.25rem;\n line-height: 1.5;\n border-radius: 0.3rem;\n}\n\nselect.form-control[size], select.form-control[multiple] {\n height: auto;\n}\n\ntextarea.form-control {\n height: auto;\n}\n\n.form-group {\n margin-bottom: 1rem;\n}\n\n.form-text {\n display: block;\n margin-top: 0.25rem;\n}\n\n.form-row {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n margin-right: -5px;\n margin-left: -5px;\n}\n\n.form-row > .col,\n.form-row > [class*=\"col-\"] {\n padding-right: 5px;\n padding-left: 5px;\n}\n\n.form-check {\n position: relative;\n display: block;\n padding-left: 1.25rem;\n}\n\n.form-check-input {\n position: absolute;\n margin-top: 0.3rem;\n margin-left: -1.25rem;\n}\n\n.form-check-input:disabled ~ .form-check-label {\n color: #6c757d;\n}\n\n.form-check-label {\n margin-bottom: 0;\n}\n\n.form-check-inline {\n display: -ms-inline-flexbox;\n display: inline-flex;\n -ms-flex-align: center;\n align-items: center;\n padding-left: 0;\n margin-right: 0.75rem;\n}\n\n.form-check-inline .form-check-input {\n position: static;\n margin-top: 0;\n margin-right: 0.3125rem;\n margin-left: 0;\n}\n\n.valid-feedback {\n display: none;\n width: 100%;\n margin-top: 0.25rem;\n font-size: 80%;\n color: #28a745;\n}\n\n.valid-tooltip {\n position: absolute;\n top: 100%;\n z-index: 5;\n display: none;\n max-width: 100%;\n padding: 0.25rem 0.5rem;\n margin-top: .1rem;\n font-size: 0.875rem;\n line-height: 1.5;\n color: #fff;\n background-color: rgba(40, 167, 69, 0.9);\n border-radius: 0.25rem;\n}\n\n.was-validated .form-control:valid, .form-control.is-valid {\n border-color: #28a745;\n padding-right: calc(1.5em + 0.75rem);\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e\");\n background-repeat: no-repeat;\n background-position: center right calc(0.375em + 0.1875rem);\n background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n\n.was-validated .form-control:valid:focus, .form-control.is-valid:focus {\n border-color: #28a745;\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.was-validated .form-control:valid ~ .valid-feedback,\n.was-validated .form-control:valid ~ .valid-tooltip, .form-control.is-valid ~ .valid-feedback,\n.form-control.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated textarea.form-control:valid, textarea.form-control.is-valid {\n padding-right: calc(1.5em + 0.75rem);\n background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem);\n}\n\n.was-validated .custom-select:valid, .custom-select.is-valid {\n border-color: #28a745;\n padding-right: calc((1em + 0.75rem) * 3 / 4 + 1.75rem);\n background: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e\") no-repeat right 0.75rem center/8px 10px, url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e\") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n\n.was-validated .custom-select:valid:focus, .custom-select.is-valid:focus {\n border-color: #28a745;\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.was-validated .custom-select:valid ~ .valid-feedback,\n.was-validated .custom-select:valid ~ .valid-tooltip, .custom-select.is-valid ~ .valid-feedback,\n.custom-select.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .form-control-file:valid ~ .valid-feedback,\n.was-validated .form-control-file:valid ~ .valid-tooltip, .form-control-file.is-valid ~ .valid-feedback,\n.form-control-file.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label {\n color: #28a745;\n}\n\n.was-validated .form-check-input:valid ~ .valid-feedback,\n.was-validated .form-check-input:valid ~ .valid-tooltip, .form-check-input.is-valid ~ .valid-feedback,\n.form-check-input.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .custom-control-input:valid ~ .custom-control-label, .custom-control-input.is-valid ~ .custom-control-label {\n color: #28a745;\n}\n\n.was-validated .custom-control-input:valid ~ .custom-control-label::before, .custom-control-input.is-valid ~ .custom-control-label::before {\n border-color: #28a745;\n}\n\n.was-validated .custom-control-input:valid ~ .valid-feedback,\n.was-validated .custom-control-input:valid ~ .valid-tooltip, .custom-control-input.is-valid ~ .valid-feedback,\n.custom-control-input.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .custom-control-input:valid:checked ~ .custom-control-label::before, .custom-control-input.is-valid:checked ~ .custom-control-label::before {\n border-color: #34ce57;\n background-color: #34ce57;\n}\n\n.was-validated .custom-control-input:valid:focus ~ .custom-control-label::before, .custom-control-input.is-valid:focus ~ .custom-control-label::before {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.was-validated .custom-control-input:valid:focus:not(:checked) ~ .custom-control-label::before, .custom-control-input.is-valid:focus:not(:checked) ~ .custom-control-label::before {\n border-color: #28a745;\n}\n\n.was-validated .custom-file-input:valid ~ .custom-file-label, .custom-file-input.is-valid ~ .custom-file-label {\n border-color: #28a745;\n}\n\n.was-validated .custom-file-input:valid ~ .valid-feedback,\n.was-validated .custom-file-input:valid ~ .valid-tooltip, .custom-file-input.is-valid ~ .valid-feedback,\n.custom-file-input.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .custom-file-input:valid:focus ~ .custom-file-label, .custom-file-input.is-valid:focus ~ .custom-file-label {\n border-color: #28a745;\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.invalid-feedback {\n display: none;\n width: 100%;\n margin-top: 0.25rem;\n font-size: 80%;\n color: #dc3545;\n}\n\n.invalid-tooltip {\n position: absolute;\n top: 100%;\n z-index: 5;\n display: none;\n max-width: 100%;\n padding: 0.25rem 0.5rem;\n margin-top: .1rem;\n font-size: 0.875rem;\n line-height: 1.5;\n color: #fff;\n background-color: rgba(220, 53, 69, 0.9);\n border-radius: 0.25rem;\n}\n\n.was-validated .form-control:invalid, .form-control.is-invalid {\n border-color: #dc3545;\n padding-right: calc(1.5em + 0.75rem);\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23dc3545' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23dc3545' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E\");\n background-repeat: no-repeat;\n background-position: center right calc(0.375em + 0.1875rem);\n background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n\n.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus {\n border-color: #dc3545;\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.was-validated .form-control:invalid ~ .invalid-feedback,\n.was-validated .form-control:invalid ~ .invalid-tooltip, .form-control.is-invalid ~ .invalid-feedback,\n.form-control.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated textarea.form-control:invalid, textarea.form-control.is-invalid {\n padding-right: calc(1.5em + 0.75rem);\n background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem);\n}\n\n.was-validated .custom-select:invalid, .custom-select.is-invalid {\n border-color: #dc3545;\n padding-right: calc((1em + 0.75rem) * 3 / 4 + 1.75rem);\n background: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e\") no-repeat right 0.75rem center/8px 10px, url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23dc3545' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23dc3545' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E\") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n\n.was-validated .custom-select:invalid:focus, .custom-select.is-invalid:focus {\n border-color: #dc3545;\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.was-validated .custom-select:invalid ~ .invalid-feedback,\n.was-validated .custom-select:invalid ~ .invalid-tooltip, .custom-select.is-invalid ~ .invalid-feedback,\n.custom-select.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .form-control-file:invalid ~ .invalid-feedback,\n.was-validated .form-control-file:invalid ~ .invalid-tooltip, .form-control-file.is-invalid ~ .invalid-feedback,\n.form-control-file.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label {\n color: #dc3545;\n}\n\n.was-validated .form-check-input:invalid ~ .invalid-feedback,\n.was-validated .form-check-input:invalid ~ .invalid-tooltip, .form-check-input.is-invalid ~ .invalid-feedback,\n.form-check-input.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .custom-control-input:invalid ~ .custom-control-label, .custom-control-input.is-invalid ~ .custom-control-label {\n color: #dc3545;\n}\n\n.was-validated .custom-control-input:invalid ~ .custom-control-label::before, .custom-control-input.is-invalid ~ .custom-control-label::before {\n border-color: #dc3545;\n}\n\n.was-validated .custom-control-input:invalid ~ .invalid-feedback,\n.was-validated .custom-control-input:invalid ~ .invalid-tooltip, .custom-control-input.is-invalid ~ .invalid-feedback,\n.custom-control-input.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .custom-control-input:invalid:checked ~ .custom-control-label::before, .custom-control-input.is-invalid:checked ~ .custom-control-label::before {\n border-color: #e4606d;\n background-color: #e4606d;\n}\n\n.was-validated .custom-control-input:invalid:focus ~ .custom-control-label::before, .custom-control-input.is-invalid:focus ~ .custom-control-label::before {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.was-validated .custom-control-input:invalid:focus:not(:checked) ~ .custom-control-label::before, .custom-control-input.is-invalid:focus:not(:checked) ~ .custom-control-label::before {\n border-color: #dc3545;\n}\n\n.was-validated .custom-file-input:invalid ~ .custom-file-label, .custom-file-input.is-invalid ~ .custom-file-label {\n border-color: #dc3545;\n}\n\n.was-validated .custom-file-input:invalid ~ .invalid-feedback,\n.was-validated .custom-file-input:invalid ~ .invalid-tooltip, .custom-file-input.is-invalid ~ .invalid-feedback,\n.custom-file-input.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .custom-file-input:invalid:focus ~ .custom-file-label, .custom-file-input.is-invalid:focus ~ .custom-file-label {\n border-color: #dc3545;\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.form-inline {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-flow: row wrap;\n flex-flow: row wrap;\n -ms-flex-align: center;\n align-items: center;\n}\n\n.form-inline .form-check {\n width: 100%;\n}\n\n@media (min-width: 576px) {\n .form-inline label {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: center;\n justify-content: center;\n margin-bottom: 0;\n }\n .form-inline .form-group {\n display: -ms-flexbox;\n display: flex;\n -ms-flex: 0 0 auto;\n flex: 0 0 auto;\n -ms-flex-flow: row wrap;\n flex-flow: row wrap;\n -ms-flex-align: center;\n align-items: center;\n margin-bottom: 0;\n }\n .form-inline .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .form-inline .form-control-plaintext {\n display: inline-block;\n }\n .form-inline .input-group,\n .form-inline .custom-select {\n width: auto;\n }\n .form-inline .form-check {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: center;\n justify-content: center;\n width: auto;\n padding-left: 0;\n }\n .form-inline .form-check-input {\n position: relative;\n -ms-flex-negative: 0;\n flex-shrink: 0;\n margin-top: 0;\n margin-right: 0.25rem;\n margin-left: 0;\n }\n .form-inline .custom-control {\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: center;\n justify-content: center;\n }\n .form-inline .custom-control-label {\n margin-bottom: 0;\n }\n}\n\n.btn {\n display: inline-block;\n font-weight: 400;\n color: #212529;\n text-align: center;\n vertical-align: middle;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n background-color: transparent;\n border: 1px solid transparent;\n padding: 0.375rem 0.75rem;\n font-size: 1rem;\n line-height: 1.5;\n border-radius: 0.25rem;\n transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .btn {\n transition: none;\n }\n}\n\n.btn:hover {\n color: #212529;\n text-decoration: none;\n}\n\n.btn:focus, .btn.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.btn.disabled, .btn:disabled {\n opacity: 0.65;\n}\n\na.btn.disabled,\nfieldset:disabled a.btn {\n pointer-events: none;\n}\n\n.btn-primary {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-primary:hover {\n color: #fff;\n background-color: #0069d9;\n border-color: #0062cc;\n}\n\n.btn-primary:focus, .btn-primary.focus {\n box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.5);\n}\n\n.btn-primary.disabled, .btn-primary:disabled {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-primary:not(:disabled):not(.disabled):active, .btn-primary:not(:disabled):not(.disabled).active,\n.show > .btn-primary.dropdown-toggle {\n color: #fff;\n background-color: #0062cc;\n border-color: #005cbf;\n}\n\n.btn-primary:not(:disabled):not(.disabled):active:focus, .btn-primary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-primary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.5);\n}\n\n.btn-secondary {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-secondary:hover {\n color: #fff;\n background-color: #5a6268;\n border-color: #545b62;\n}\n\n.btn-secondary:focus, .btn-secondary.focus {\n box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5);\n}\n\n.btn-secondary.disabled, .btn-secondary:disabled {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-secondary:not(:disabled):not(.disabled):active, .btn-secondary:not(:disabled):not(.disabled).active,\n.show > .btn-secondary.dropdown-toggle {\n color: #fff;\n background-color: #545b62;\n border-color: #4e555b;\n}\n\n.btn-secondary:not(:disabled):not(.disabled):active:focus, .btn-secondary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-secondary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5);\n}\n\n.btn-success {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-success:hover {\n color: #fff;\n background-color: #218838;\n border-color: #1e7e34;\n}\n\n.btn-success:focus, .btn-success.focus {\n box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5);\n}\n\n.btn-success.disabled, .btn-success:disabled {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-success:not(:disabled):not(.disabled):active, .btn-success:not(:disabled):not(.disabled).active,\n.show > .btn-success.dropdown-toggle {\n color: #fff;\n background-color: #1e7e34;\n border-color: #1c7430;\n}\n\n.btn-success:not(:disabled):not(.disabled):active:focus, .btn-success:not(:disabled):not(.disabled).active:focus,\n.show > .btn-success.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5);\n}\n\n.btn-info {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-info:hover {\n color: #fff;\n background-color: #138496;\n border-color: #117a8b;\n}\n\n.btn-info:focus, .btn-info.focus {\n box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5);\n}\n\n.btn-info.disabled, .btn-info:disabled {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-info:not(:disabled):not(.disabled):active, .btn-info:not(:disabled):not(.disabled).active,\n.show > .btn-info.dropdown-toggle {\n color: #fff;\n background-color: #117a8b;\n border-color: #10707f;\n}\n\n.btn-info:not(:disabled):not(.disabled):active:focus, .btn-info:not(:disabled):not(.disabled).active:focus,\n.show > .btn-info.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5);\n}\n\n.btn-warning {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-warning:hover {\n color: #212529;\n background-color: #e0a800;\n border-color: #d39e00;\n}\n\n.btn-warning:focus, .btn-warning.focus {\n box-shadow: 0 0 0 0.2rem rgba(222, 170, 12, 0.5);\n}\n\n.btn-warning.disabled, .btn-warning:disabled {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-warning:not(:disabled):not(.disabled):active, .btn-warning:not(:disabled):not(.disabled).active,\n.show > .btn-warning.dropdown-toggle {\n color: #212529;\n background-color: #d39e00;\n border-color: #c69500;\n}\n\n.btn-warning:not(:disabled):not(.disabled):active:focus, .btn-warning:not(:disabled):not(.disabled).active:focus,\n.show > .btn-warning.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(222, 170, 12, 0.5);\n}\n\n.btn-danger {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-danger:hover {\n color: #fff;\n background-color: #c82333;\n border-color: #bd2130;\n}\n\n.btn-danger:focus, .btn-danger.focus {\n box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5);\n}\n\n.btn-danger.disabled, .btn-danger:disabled {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-danger:not(:disabled):not(.disabled):active, .btn-danger:not(:disabled):not(.disabled).active,\n.show > .btn-danger.dropdown-toggle {\n color: #fff;\n background-color: #bd2130;\n border-color: #b21f2d;\n}\n\n.btn-danger:not(:disabled):not(.disabled):active:focus, .btn-danger:not(:disabled):not(.disabled).active:focus,\n.show > .btn-danger.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5);\n}\n\n.btn-light {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-light:hover {\n color: #212529;\n background-color: #e2e6ea;\n border-color: #dae0e5;\n}\n\n.btn-light:focus, .btn-light.focus {\n box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5);\n}\n\n.btn-light.disabled, .btn-light:disabled {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-light:not(:disabled):not(.disabled):active, .btn-light:not(:disabled):not(.disabled).active,\n.show > .btn-light.dropdown-toggle {\n color: #212529;\n background-color: #dae0e5;\n border-color: #d3d9df;\n}\n\n.btn-light:not(:disabled):not(.disabled):active:focus, .btn-light:not(:disabled):not(.disabled).active:focus,\n.show > .btn-light.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5);\n}\n\n.btn-dark {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-dark:hover {\n color: #fff;\n background-color: #23272b;\n border-color: #1d2124;\n}\n\n.btn-dark:focus, .btn-dark.focus {\n box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5);\n}\n\n.btn-dark.disabled, .btn-dark:disabled {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-dark:not(:disabled):not(.disabled):active, .btn-dark:not(:disabled):not(.disabled).active,\n.show > .btn-dark.dropdown-toggle {\n color: #fff;\n background-color: #1d2124;\n border-color: #171a1d;\n}\n\n.btn-dark:not(:disabled):not(.disabled):active:focus, .btn-dark:not(:disabled):not(.disabled).active:focus,\n.show > .btn-dark.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5);\n}\n\n.btn-outline-primary {\n color: #007bff;\n border-color: #007bff;\n}\n\n.btn-outline-primary:hover {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-outline-primary:focus, .btn-outline-primary.focus {\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);\n}\n\n.btn-outline-primary.disabled, .btn-outline-primary:disabled {\n color: #007bff;\n background-color: transparent;\n}\n\n.btn-outline-primary:not(:disabled):not(.disabled):active, .btn-outline-primary:not(:disabled):not(.disabled).active,\n.show > .btn-outline-primary.dropdown-toggle {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-outline-primary:not(:disabled):not(.disabled):active:focus, .btn-outline-primary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-primary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);\n}\n\n.btn-outline-secondary {\n color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-outline-secondary:hover {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-outline-secondary:focus, .btn-outline-secondary.focus {\n box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);\n}\n\n.btn-outline-secondary.disabled, .btn-outline-secondary:disabled {\n color: #6c757d;\n background-color: transparent;\n}\n\n.btn-outline-secondary:not(:disabled):not(.disabled):active, .btn-outline-secondary:not(:disabled):not(.disabled).active,\n.show > .btn-outline-secondary.dropdown-toggle {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-outline-secondary:not(:disabled):not(.disabled):active:focus, .btn-outline-secondary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-secondary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);\n}\n\n.btn-outline-success {\n color: #28a745;\n border-color: #28a745;\n}\n\n.btn-outline-success:hover {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-outline-success:focus, .btn-outline-success.focus {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);\n}\n\n.btn-outline-success.disabled, .btn-outline-success:disabled {\n color: #28a745;\n background-color: transparent;\n}\n\n.btn-outline-success:not(:disabled):not(.disabled):active, .btn-outline-success:not(:disabled):not(.disabled).active,\n.show > .btn-outline-success.dropdown-toggle {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-outline-success:not(:disabled):not(.disabled):active:focus, .btn-outline-success:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-success.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);\n}\n\n.btn-outline-info {\n color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-outline-info:hover {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-outline-info:focus, .btn-outline-info.focus {\n box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);\n}\n\n.btn-outline-info.disabled, .btn-outline-info:disabled {\n color: #17a2b8;\n background-color: transparent;\n}\n\n.btn-outline-info:not(:disabled):not(.disabled):active, .btn-outline-info:not(:disabled):not(.disabled).active,\n.show > .btn-outline-info.dropdown-toggle {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-outline-info:not(:disabled):not(.disabled):active:focus, .btn-outline-info:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-info.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);\n}\n\n.btn-outline-warning {\n color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-outline-warning:hover {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-outline-warning:focus, .btn-outline-warning.focus {\n box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);\n}\n\n.btn-outline-warning.disabled, .btn-outline-warning:disabled {\n color: #ffc107;\n background-color: transparent;\n}\n\n.btn-outline-warning:not(:disabled):not(.disabled):active, .btn-outline-warning:not(:disabled):not(.disabled).active,\n.show > .btn-outline-warning.dropdown-toggle {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-outline-warning:not(:disabled):not(.disabled):active:focus, .btn-outline-warning:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-warning.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);\n}\n\n.btn-outline-danger {\n color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-outline-danger:hover {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-outline-danger:focus, .btn-outline-danger.focus {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);\n}\n\n.btn-outline-danger.disabled, .btn-outline-danger:disabled {\n color: #dc3545;\n background-color: transparent;\n}\n\n.btn-outline-danger:not(:disabled):not(.disabled):active, .btn-outline-danger:not(:disabled):not(.disabled).active,\n.show > .btn-outline-danger.dropdown-toggle {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-outline-danger:not(:disabled):not(.disabled):active:focus, .btn-outline-danger:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-danger.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);\n}\n\n.btn-outline-light {\n color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-outline-light:hover {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-outline-light:focus, .btn-outline-light.focus {\n box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);\n}\n\n.btn-outline-light.disabled, .btn-outline-light:disabled {\n color: #f8f9fa;\n background-color: transparent;\n}\n\n.btn-outline-light:not(:disabled):not(.disabled):active, .btn-outline-light:not(:disabled):not(.disabled).active,\n.show > .btn-outline-light.dropdown-toggle {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-outline-light:not(:disabled):not(.disabled):active:focus, .btn-outline-light:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-light.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);\n}\n\n.btn-outline-dark {\n color: #343a40;\n border-color: #343a40;\n}\n\n.btn-outline-dark:hover {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-outline-dark:focus, .btn-outline-dark.focus {\n box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);\n}\n\n.btn-outline-dark.disabled, .btn-outline-dark:disabled {\n color: #343a40;\n background-color: transparent;\n}\n\n.btn-outline-dark:not(:disabled):not(.disabled):active, .btn-outline-dark:not(:disabled):not(.disabled).active,\n.show > .btn-outline-dark.dropdown-toggle {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-outline-dark:not(:disabled):not(.disabled):active:focus, .btn-outline-dark:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-dark.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);\n}\n\n.btn-link {\n font-weight: 400;\n color: #007bff;\n text-decoration: none;\n}\n\n.btn-link:hover {\n color: #0056b3;\n text-decoration: underline;\n}\n\n.btn-link:focus, .btn-link.focus {\n text-decoration: underline;\n box-shadow: none;\n}\n\n.btn-link:disabled, .btn-link.disabled {\n color: #6c757d;\n pointer-events: none;\n}\n\n.btn-lg, .btn-group-lg > .btn {\n padding: 0.5rem 1rem;\n font-size: 1.25rem;\n line-height: 1.5;\n border-radius: 0.3rem;\n}\n\n.btn-sm, .btn-group-sm > .btn {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n border-radius: 0.2rem;\n}\n\n.btn-block {\n display: block;\n width: 100%;\n}\n\n.btn-block + .btn-block {\n margin-top: 0.5rem;\n}\n\ninput[type=\"submit\"].btn-block,\ninput[type=\"reset\"].btn-block,\ninput[type=\"button\"].btn-block {\n width: 100%;\n}\n\n.fade {\n transition: opacity 0.15s linear;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .fade {\n transition: none;\n }\n}\n\n.fade:not(.show) {\n opacity: 0;\n}\n\n.collapse:not(.show) {\n display: none;\n}\n\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n transition: height 0.35s ease;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .collapsing {\n transition: none;\n }\n}\n\n.dropup,\n.dropright,\n.dropdown,\n.dropleft {\n position: relative;\n}\n\n.dropdown-toggle {\n white-space: nowrap;\n}\n\n.dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0.3em solid;\n border-right: 0.3em solid transparent;\n border-bottom: 0;\n border-left: 0.3em solid transparent;\n}\n\n.dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: 1000;\n display: none;\n float: left;\n min-width: 10rem;\n padding: 0.5rem 0;\n margin: 0.125rem 0 0;\n font-size: 1rem;\n color: #212529;\n text-align: left;\n list-style: none;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-radius: 0.25rem;\n}\n\n.dropdown-menu-left {\n right: auto;\n left: 0;\n}\n\n.dropdown-menu-right {\n right: 0;\n left: auto;\n}\n\n@media (min-width: 576px) {\n .dropdown-menu-sm-left {\n right: auto;\n left: 0;\n }\n .dropdown-menu-sm-right {\n right: 0;\n left: auto;\n }\n}\n\n@media (min-width: 768px) {\n .dropdown-menu-md-left {\n right: auto;\n left: 0;\n }\n .dropdown-menu-md-right {\n right: 0;\n left: auto;\n }\n}\n\n@media (min-width: 992px) {\n .dropdown-menu-lg-left {\n right: auto;\n left: 0;\n }\n .dropdown-menu-lg-right {\n right: 0;\n left: auto;\n }\n}\n\n@media (min-width: 1200px) {\n .dropdown-menu-xl-left {\n right: auto;\n left: 0;\n }\n .dropdown-menu-xl-right {\n right: 0;\n left: auto;\n }\n}\n\n.dropup .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-top: 0;\n margin-bottom: 0.125rem;\n}\n\n.dropup .dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0;\n border-right: 0.3em solid transparent;\n border-bottom: 0.3em solid;\n border-left: 0.3em solid transparent;\n}\n\n.dropup .dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropright .dropdown-menu {\n top: 0;\n right: auto;\n left: 100%;\n margin-top: 0;\n margin-left: 0.125rem;\n}\n\n.dropright .dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0.3em solid transparent;\n border-right: 0;\n border-bottom: 0.3em solid transparent;\n border-left: 0.3em solid;\n}\n\n.dropright .dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropright .dropdown-toggle::after {\n vertical-align: 0;\n}\n\n.dropleft .dropdown-menu {\n top: 0;\n right: 100%;\n left: auto;\n margin-top: 0;\n margin-right: 0.125rem;\n}\n\n.dropleft .dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n}\n\n.dropleft .dropdown-toggle::after {\n display: none;\n}\n\n.dropleft .dropdown-toggle::before {\n display: inline-block;\n margin-right: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0.3em solid transparent;\n border-right: 0.3em solid;\n border-bottom: 0.3em solid transparent;\n}\n\n.dropleft .dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropleft .dropdown-toggle::before {\n vertical-align: 0;\n}\n\n.dropdown-menu[x-placement^=\"top\"], .dropdown-menu[x-placement^=\"right\"], .dropdown-menu[x-placement^=\"bottom\"], .dropdown-menu[x-placement^=\"left\"] {\n right: auto;\n bottom: auto;\n}\n\n.dropdown-divider {\n height: 0;\n margin: 0.5rem 0;\n overflow: hidden;\n border-top: 1px solid #e9ecef;\n}\n\n.dropdown-item {\n display: block;\n width: 100%;\n padding: 0.25rem 1.5rem;\n clear: both;\n font-weight: 400;\n color: #212529;\n text-align: inherit;\n white-space: nowrap;\n background-color: transparent;\n border: 0;\n}\n\n.dropdown-item:hover, .dropdown-item:focus {\n color: #16181b;\n text-decoration: none;\n background-color: #f8f9fa;\n}\n\n.dropdown-item.active, .dropdown-item:active {\n color: #fff;\n text-decoration: none;\n background-color: #007bff;\n}\n\n.dropdown-item.disabled, .dropdown-item:disabled {\n color: #6c757d;\n pointer-events: none;\n background-color: transparent;\n}\n\n.dropdown-menu.show {\n display: block;\n}\n\n.dropdown-header {\n display: block;\n padding: 0.5rem 1.5rem;\n margin-bottom: 0;\n font-size: 0.875rem;\n color: #6c757d;\n white-space: nowrap;\n}\n\n.dropdown-item-text {\n display: block;\n padding: 0.25rem 1.5rem;\n color: #212529;\n}\n\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: -ms-inline-flexbox;\n display: inline-flex;\n vertical-align: middle;\n}\n\n.btn-group > .btn,\n.btn-group-vertical > .btn {\n position: relative;\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n}\n\n.btn-group > .btn:hover,\n.btn-group-vertical > .btn:hover {\n z-index: 1;\n}\n\n.btn-group > .btn:focus, .btn-group > .btn:active, .btn-group > .btn.active,\n.btn-group-vertical > .btn:focus,\n.btn-group-vertical > .btn:active,\n.btn-group-vertical > .btn.active {\n z-index: 1;\n}\n\n.btn-toolbar {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n -ms-flex-pack: start;\n justify-content: flex-start;\n}\n\n.btn-toolbar .input-group {\n width: auto;\n}\n\n.btn-group > .btn:not(:first-child),\n.btn-group > .btn-group:not(:first-child) {\n margin-left: -1px;\n}\n\n.btn-group > .btn:not(:last-child):not(.dropdown-toggle),\n.btn-group > .btn-group:not(:last-child) > .btn {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.btn-group > .btn:not(:first-child),\n.btn-group > .btn-group:not(:first-child) > .btn {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.dropdown-toggle-split {\n padding-right: 0.5625rem;\n padding-left: 0.5625rem;\n}\n\n.dropdown-toggle-split::after,\n.dropup .dropdown-toggle-split::after,\n.dropright .dropdown-toggle-split::after {\n margin-left: 0;\n}\n\n.dropleft .dropdown-toggle-split::before {\n margin-right: 0;\n}\n\n.btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split {\n padding-right: 0.375rem;\n padding-left: 0.375rem;\n}\n\n.btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split {\n padding-right: 0.75rem;\n padding-left: 0.75rem;\n}\n\n.btn-group-vertical {\n -ms-flex-direction: column;\n flex-direction: column;\n -ms-flex-align: start;\n align-items: flex-start;\n -ms-flex-pack: center;\n justify-content: center;\n}\n\n.btn-group-vertical > .btn,\n.btn-group-vertical > .btn-group {\n width: 100%;\n}\n\n.btn-group-vertical > .btn:not(:first-child),\n.btn-group-vertical > .btn-group:not(:first-child) {\n margin-top: -1px;\n}\n\n.btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle),\n.btn-group-vertical > .btn-group:not(:last-child) > .btn {\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.btn-group-vertical > .btn:not(:first-child),\n.btn-group-vertical > .btn-group:not(:first-child) > .btn {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n.btn-group-toggle > .btn,\n.btn-group-toggle > .btn-group > .btn {\n margin-bottom: 0;\n}\n\n.btn-group-toggle > .btn input[type=\"radio\"],\n.btn-group-toggle > .btn input[type=\"checkbox\"],\n.btn-group-toggle > .btn-group > .btn input[type=\"radio\"],\n.btn-group-toggle > .btn-group > .btn input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0, 0, 0, 0);\n pointer-events: none;\n}\n\n.input-group {\n position: relative;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n -ms-flex-align: stretch;\n align-items: stretch;\n width: 100%;\n}\n\n.input-group > .form-control,\n.input-group > .form-control-plaintext,\n.input-group > .custom-select,\n.input-group > .custom-file {\n position: relative;\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n width: 1%;\n margin-bottom: 0;\n}\n\n.input-group > .form-control + .form-control,\n.input-group > .form-control + .custom-select,\n.input-group > .form-control + .custom-file,\n.input-group > .form-control-plaintext + .form-control,\n.input-group > .form-control-plaintext + .custom-select,\n.input-group > .form-control-plaintext + .custom-file,\n.input-group > .custom-select + .form-control,\n.input-group > .custom-select + .custom-select,\n.input-group > .custom-select + .custom-file,\n.input-group > .custom-file + .form-control,\n.input-group > .custom-file + .custom-select,\n.input-group > .custom-file + .custom-file {\n margin-left: -1px;\n}\n\n.input-group > .form-control:focus,\n.input-group > .custom-select:focus,\n.input-group > .custom-file .custom-file-input:focus ~ .custom-file-label {\n z-index: 3;\n}\n\n.input-group > .custom-file .custom-file-input:focus {\n z-index: 4;\n}\n\n.input-group > .form-control:not(:last-child),\n.input-group > .custom-select:not(:last-child) {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.input-group > .form-control:not(:first-child),\n.input-group > .custom-select:not(:first-child) {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.input-group > .custom-file {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n}\n\n.input-group > .custom-file:not(:last-child) .custom-file-label,\n.input-group > .custom-file:not(:last-child) .custom-file-label::after {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.input-group > .custom-file:not(:first-child) .custom-file-label {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.input-group-prepend,\n.input-group-append {\n display: -ms-flexbox;\n display: flex;\n}\n\n.input-group-prepend .btn,\n.input-group-append .btn {\n position: relative;\n z-index: 2;\n}\n\n.input-group-prepend .btn:focus,\n.input-group-append .btn:focus {\n z-index: 3;\n}\n\n.input-group-prepend .btn + .btn,\n.input-group-prepend .btn + .input-group-text,\n.input-group-prepend .input-group-text + .input-group-text,\n.input-group-prepend .input-group-text + .btn,\n.input-group-append .btn + .btn,\n.input-group-append .btn + .input-group-text,\n.input-group-append .input-group-text + .input-group-text,\n.input-group-append .input-group-text + .btn {\n margin-left: -1px;\n}\n\n.input-group-prepend {\n margin-right: -1px;\n}\n\n.input-group-append {\n margin-left: -1px;\n}\n\n.input-group-text {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n padding: 0.375rem 0.75rem;\n margin-bottom: 0;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: #495057;\n text-align: center;\n white-space: nowrap;\n background-color: #e9ecef;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n}\n\n.input-group-text input[type=\"radio\"],\n.input-group-text input[type=\"checkbox\"] {\n margin-top: 0;\n}\n\n.input-group-lg > .form-control:not(textarea),\n.input-group-lg > .custom-select {\n height: calc(1.5em + 1rem + 2px);\n}\n\n.input-group-lg > .form-control,\n.input-group-lg > .custom-select,\n.input-group-lg > .input-group-prepend > .input-group-text,\n.input-group-lg > .input-group-append > .input-group-text,\n.input-group-lg > .input-group-prepend > .btn,\n.input-group-lg > .input-group-append > .btn {\n padding: 0.5rem 1rem;\n font-size: 1.25rem;\n line-height: 1.5;\n border-radius: 0.3rem;\n}\n\n.input-group-sm > .form-control:not(textarea),\n.input-group-sm > .custom-select {\n height: calc(1.5em + 0.5rem + 2px);\n}\n\n.input-group-sm > .form-control,\n.input-group-sm > .custom-select,\n.input-group-sm > .input-group-prepend > .input-group-text,\n.input-group-sm > .input-group-append > .input-group-text,\n.input-group-sm > .input-group-prepend > .btn,\n.input-group-sm > .input-group-append > .btn {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n border-radius: 0.2rem;\n}\n\n.input-group-lg > .custom-select,\n.input-group-sm > .custom-select {\n padding-right: 1.75rem;\n}\n\n.input-group > .input-group-prepend > .btn,\n.input-group > .input-group-prepend > .input-group-text,\n.input-group > .input-group-append:not(:last-child) > .btn,\n.input-group > .input-group-append:not(:last-child) > .input-group-text,\n.input-group > .input-group-append:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group > .input-group-append:last-child > .input-group-text:not(:last-child) {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.input-group > .input-group-append > .btn,\n.input-group > .input-group-append > .input-group-text,\n.input-group > .input-group-prepend:not(:first-child) > .btn,\n.input-group > .input-group-prepend:not(:first-child) > .input-group-text,\n.input-group > .input-group-prepend:first-child > .btn:not(:first-child),\n.input-group > .input-group-prepend:first-child > .input-group-text:not(:first-child) {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.custom-control {\n position: relative;\n display: block;\n min-height: 1.5rem;\n padding-left: 1.5rem;\n}\n\n.custom-control-inline {\n display: -ms-inline-flexbox;\n display: inline-flex;\n margin-right: 1rem;\n}\n\n.custom-control-input {\n position: absolute;\n z-index: -1;\n opacity: 0;\n}\n\n.custom-control-input:checked ~ .custom-control-label::before {\n color: #fff;\n border-color: #007bff;\n background-color: #007bff;\n}\n\n.custom-control-input:focus ~ .custom-control-label::before {\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-control-input:focus:not(:checked) ~ .custom-control-label::before {\n border-color: #80bdff;\n}\n\n.custom-control-input:not(:disabled):active ~ .custom-control-label::before {\n color: #fff;\n background-color: #b3d7ff;\n border-color: #b3d7ff;\n}\n\n.custom-control-input:disabled ~ .custom-control-label {\n color: #6c757d;\n}\n\n.custom-control-input:disabled ~ .custom-control-label::before {\n background-color: #e9ecef;\n}\n\n.custom-control-label {\n position: relative;\n margin-bottom: 0;\n vertical-align: top;\n}\n\n.custom-control-label::before {\n position: absolute;\n top: 0.25rem;\n left: -1.5rem;\n display: block;\n width: 1rem;\n height: 1rem;\n pointer-events: none;\n content: \"\";\n background-color: #fff;\n border: #adb5bd solid 1px;\n}\n\n.custom-control-label::after {\n position: absolute;\n top: 0.25rem;\n left: -1.5rem;\n display: block;\n width: 1rem;\n height: 1rem;\n content: \"\";\n background: no-repeat 50% / 50% 50%;\n}\n\n.custom-checkbox .custom-control-label::before {\n border-radius: 0.25rem;\n}\n\n.custom-checkbox .custom-control-input:checked ~ .custom-control-label::after {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3e%3c/svg%3e\");\n}\n\n.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::before {\n border-color: #007bff;\n background-color: #007bff;\n}\n\n.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::after {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e\");\n}\n\n.custom-checkbox .custom-control-input:disabled:checked ~ .custom-control-label::before {\n background-color: rgba(0, 123, 255, 0.5);\n}\n\n.custom-checkbox .custom-control-input:disabled:indeterminate ~ .custom-control-label::before {\n background-color: rgba(0, 123, 255, 0.5);\n}\n\n.custom-radio .custom-control-label::before {\n border-radius: 50%;\n}\n\n.custom-radio .custom-control-input:checked ~ .custom-control-label::after {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e\");\n}\n\n.custom-radio .custom-control-input:disabled:checked ~ .custom-control-label::before {\n background-color: rgba(0, 123, 255, 0.5);\n}\n\n.custom-switch {\n padding-left: 2.25rem;\n}\n\n.custom-switch .custom-control-label::before {\n left: -2.25rem;\n width: 1.75rem;\n pointer-events: all;\n border-radius: 0.5rem;\n}\n\n.custom-switch .custom-control-label::after {\n top: calc(0.25rem + 2px);\n left: calc(-2.25rem + 2px);\n width: calc(1rem - 4px);\n height: calc(1rem - 4px);\n background-color: #adb5bd;\n border-radius: 0.5rem;\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-transform 0.15s ease-in-out;\n transition: transform 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n transition: transform 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-transform 0.15s ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .custom-switch .custom-control-label::after {\n transition: none;\n }\n}\n\n.custom-switch .custom-control-input:checked ~ .custom-control-label::after {\n background-color: #fff;\n -webkit-transform: translateX(0.75rem);\n transform: translateX(0.75rem);\n}\n\n.custom-switch .custom-control-input:disabled:checked ~ .custom-control-label::before {\n background-color: rgba(0, 123, 255, 0.5);\n}\n\n.custom-select {\n display: inline-block;\n width: 100%;\n height: calc(1.5em + 0.75rem + 2px);\n padding: 0.375rem 1.75rem 0.375rem 0.75rem;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: #495057;\n vertical-align: middle;\n background: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e\") no-repeat right 0.75rem center/8px 10px;\n background-color: #fff;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n}\n\n.custom-select:focus {\n border-color: #80bdff;\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-select:focus::-ms-value {\n color: #495057;\n background-color: #fff;\n}\n\n.custom-select[multiple], .custom-select[size]:not([size=\"1\"]) {\n height: auto;\n padding-right: 0.75rem;\n background-image: none;\n}\n\n.custom-select:disabled {\n color: #6c757d;\n background-color: #e9ecef;\n}\n\n.custom-select::-ms-expand {\n display: none;\n}\n\n.custom-select-sm {\n height: calc(1.5em + 0.5rem + 2px);\n padding-top: 0.25rem;\n padding-bottom: 0.25rem;\n padding-left: 0.5rem;\n font-size: 0.875rem;\n}\n\n.custom-select-lg {\n height: calc(1.5em + 1rem + 2px);\n padding-top: 0.5rem;\n padding-bottom: 0.5rem;\n padding-left: 1rem;\n font-size: 1.25rem;\n}\n\n.custom-file {\n position: relative;\n display: inline-block;\n width: 100%;\n height: calc(1.5em + 0.75rem + 2px);\n margin-bottom: 0;\n}\n\n.custom-file-input {\n position: relative;\n z-index: 2;\n width: 100%;\n height: calc(1.5em + 0.75rem + 2px);\n margin: 0;\n opacity: 0;\n}\n\n.custom-file-input:focus ~ .custom-file-label {\n border-color: #80bdff;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-file-input:disabled ~ .custom-file-label {\n background-color: #e9ecef;\n}\n\n.custom-file-input:lang(en) ~ .custom-file-label::after {\n content: \"Browse\";\n}\n\n.custom-file-input ~ .custom-file-label[data-browse]::after {\n content: attr(data-browse);\n}\n\n.custom-file-label {\n position: absolute;\n top: 0;\n right: 0;\n left: 0;\n z-index: 1;\n height: calc(1.5em + 0.75rem + 2px);\n padding: 0.375rem 0.75rem;\n font-weight: 400;\n line-height: 1.5;\n color: #495057;\n background-color: #fff;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n}\n\n.custom-file-label::after {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n z-index: 3;\n display: block;\n height: calc(1.5em + 0.75rem);\n padding: 0.375rem 0.75rem;\n line-height: 1.5;\n color: #495057;\n content: \"Browse\";\n background-color: #e9ecef;\n border-left: inherit;\n border-radius: 0 0.25rem 0.25rem 0;\n}\n\n.custom-range {\n width: 100%;\n height: calc(1rem + 0.4rem);\n padding: 0;\n background-color: transparent;\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n}\n\n.custom-range:focus {\n outline: none;\n}\n\n.custom-range:focus::-webkit-slider-thumb {\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-range:focus::-moz-range-thumb {\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-range:focus::-ms-thumb {\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-range::-moz-focus-outer {\n border: 0;\n}\n\n.custom-range::-webkit-slider-thumb {\n width: 1rem;\n height: 1rem;\n margin-top: -0.25rem;\n background-color: #007bff;\n border: 0;\n border-radius: 1rem;\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n -webkit-appearance: none;\n appearance: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .custom-range::-webkit-slider-thumb {\n transition: none;\n }\n}\n\n.custom-range::-webkit-slider-thumb:active {\n background-color: #b3d7ff;\n}\n\n.custom-range::-webkit-slider-runnable-track {\n width: 100%;\n height: 0.5rem;\n color: transparent;\n cursor: pointer;\n background-color: #dee2e6;\n border-color: transparent;\n border-radius: 1rem;\n}\n\n.custom-range::-moz-range-thumb {\n width: 1rem;\n height: 1rem;\n background-color: #007bff;\n border: 0;\n border-radius: 1rem;\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n -moz-appearance: none;\n appearance: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .custom-range::-moz-range-thumb {\n transition: none;\n }\n}\n\n.custom-range::-moz-range-thumb:active {\n background-color: #b3d7ff;\n}\n\n.custom-range::-moz-range-track {\n width: 100%;\n height: 0.5rem;\n color: transparent;\n cursor: pointer;\n background-color: #dee2e6;\n border-color: transparent;\n border-radius: 1rem;\n}\n\n.custom-range::-ms-thumb {\n width: 1rem;\n height: 1rem;\n margin-top: 0;\n margin-right: 0.2rem;\n margin-left: 0.2rem;\n background-color: #007bff;\n border: 0;\n border-radius: 1rem;\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n appearance: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .custom-range::-ms-thumb {\n transition: none;\n }\n}\n\n.custom-range::-ms-thumb:active {\n background-color: #b3d7ff;\n}\n\n.custom-range::-ms-track {\n width: 100%;\n height: 0.5rem;\n color: transparent;\n cursor: pointer;\n background-color: transparent;\n border-color: transparent;\n border-width: 0.5rem;\n}\n\n.custom-range::-ms-fill-lower {\n background-color: #dee2e6;\n border-radius: 1rem;\n}\n\n.custom-range::-ms-fill-upper {\n margin-right: 15px;\n background-color: #dee2e6;\n border-radius: 1rem;\n}\n\n.custom-range:disabled::-webkit-slider-thumb {\n background-color: #adb5bd;\n}\n\n.custom-range:disabled::-webkit-slider-runnable-track {\n cursor: default;\n}\n\n.custom-range:disabled::-moz-range-thumb {\n background-color: #adb5bd;\n}\n\n.custom-range:disabled::-moz-range-track {\n cursor: default;\n}\n\n.custom-range:disabled::-ms-thumb {\n background-color: #adb5bd;\n}\n\n.custom-control-label::before,\n.custom-file-label,\n.custom-select {\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .custom-control-label::before,\n .custom-file-label,\n .custom-select {\n transition: none;\n }\n}\n\n.nav {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n padding-left: 0;\n margin-bottom: 0;\n list-style: none;\n}\n\n.nav-link {\n display: block;\n padding: 0.5rem 1rem;\n}\n\n.nav-link:hover, .nav-link:focus {\n text-decoration: none;\n}\n\n.nav-link.disabled {\n color: #6c757d;\n pointer-events: none;\n cursor: default;\n}\n\n.nav-tabs {\n border-bottom: 1px solid #dee2e6;\n}\n\n.nav-tabs .nav-item {\n margin-bottom: -1px;\n}\n\n.nav-tabs .nav-link {\n border: 1px solid transparent;\n border-top-left-radius: 0.25rem;\n border-top-right-radius: 0.25rem;\n}\n\n.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus {\n border-color: #e9ecef #e9ecef #dee2e6;\n}\n\n.nav-tabs .nav-link.disabled {\n color: #6c757d;\n background-color: transparent;\n border-color: transparent;\n}\n\n.nav-tabs .nav-link.active,\n.nav-tabs .nav-item.show .nav-link {\n color: #495057;\n background-color: #fff;\n border-color: #dee2e6 #dee2e6 #fff;\n}\n\n.nav-tabs .dropdown-menu {\n margin-top: -1px;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n.nav-pills .nav-link {\n border-radius: 0.25rem;\n}\n\n.nav-pills .nav-link.active,\n.nav-pills .show > .nav-link {\n color: #fff;\n background-color: #007bff;\n}\n\n.nav-fill .nav-item {\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n text-align: center;\n}\n\n.nav-justified .nav-item {\n -ms-flex-preferred-size: 0;\n flex-basis: 0;\n -ms-flex-positive: 1;\n flex-grow: 1;\n text-align: center;\n}\n\n.tab-content > .tab-pane {\n display: none;\n}\n\n.tab-content > .active {\n display: block;\n}\n\n.navbar {\n position: relative;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: justify;\n justify-content: space-between;\n padding: 0.5rem 1rem;\n}\n\n.navbar > .container,\n.navbar > .container-fluid {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: justify;\n justify-content: space-between;\n}\n\n.navbar-brand {\n display: inline-block;\n padding-top: 0.3125rem;\n padding-bottom: 0.3125rem;\n margin-right: 1rem;\n font-size: 1.25rem;\n line-height: inherit;\n white-space: nowrap;\n}\n\n.navbar-brand:hover, .navbar-brand:focus {\n text-decoration: none;\n}\n\n.navbar-nav {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n padding-left: 0;\n margin-bottom: 0;\n list-style: none;\n}\n\n.navbar-nav .nav-link {\n padding-right: 0;\n padding-left: 0;\n}\n\n.navbar-nav .dropdown-menu {\n position: static;\n float: none;\n}\n\n.navbar-text {\n display: inline-block;\n padding-top: 0.5rem;\n padding-bottom: 0.5rem;\n}\n\n.navbar-collapse {\n -ms-flex-preferred-size: 100%;\n flex-basis: 100%;\n -ms-flex-positive: 1;\n flex-grow: 1;\n -ms-flex-align: center;\n align-items: center;\n}\n\n.navbar-toggler {\n padding: 0.25rem 0.75rem;\n font-size: 1.25rem;\n line-height: 1;\n background-color: transparent;\n border: 1px solid transparent;\n border-radius: 0.25rem;\n}\n\n.navbar-toggler:hover, .navbar-toggler:focus {\n text-decoration: none;\n}\n\n.navbar-toggler-icon {\n display: inline-block;\n width: 1.5em;\n height: 1.5em;\n vertical-align: middle;\n content: \"\";\n background: no-repeat center center;\n background-size: 100% 100%;\n}\n\n@media (max-width: 575.98px) {\n .navbar-expand-sm > .container,\n .navbar-expand-sm > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 576px) {\n .navbar-expand-sm {\n -ms-flex-flow: row nowrap;\n flex-flow: row nowrap;\n -ms-flex-pack: start;\n justify-content: flex-start;\n }\n .navbar-expand-sm .navbar-nav {\n -ms-flex-direction: row;\n flex-direction: row;\n }\n .navbar-expand-sm .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-sm .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-sm > .container,\n .navbar-expand-sm > .container-fluid {\n -ms-flex-wrap: nowrap;\n flex-wrap: nowrap;\n }\n .navbar-expand-sm .navbar-collapse {\n display: -ms-flexbox !important;\n display: flex !important;\n -ms-flex-preferred-size: auto;\n flex-basis: auto;\n }\n .navbar-expand-sm .navbar-toggler {\n display: none;\n }\n}\n\n@media (max-width: 767.98px) {\n .navbar-expand-md > .container,\n .navbar-expand-md > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 768px) {\n .navbar-expand-md {\n -ms-flex-flow: row nowrap;\n flex-flow: row nowrap;\n -ms-flex-pack: start;\n justify-content: flex-start;\n }\n .navbar-expand-md .navbar-nav {\n -ms-flex-direction: row;\n flex-direction: row;\n }\n .navbar-expand-md .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-md .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-md > .container,\n .navbar-expand-md > .container-fluid {\n -ms-flex-wrap: nowrap;\n flex-wrap: nowrap;\n }\n .navbar-expand-md .navbar-collapse {\n display: -ms-flexbox !important;\n display: flex !important;\n -ms-flex-preferred-size: auto;\n flex-basis: auto;\n }\n .navbar-expand-md .navbar-toggler {\n display: none;\n }\n}\n\n@media (max-width: 991.98px) {\n .navbar-expand-lg > .container,\n .navbar-expand-lg > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 992px) {\n .navbar-expand-lg {\n -ms-flex-flow: row nowrap;\n flex-flow: row nowrap;\n -ms-flex-pack: start;\n justify-content: flex-start;\n }\n .navbar-expand-lg .navbar-nav {\n -ms-flex-direction: row;\n flex-direction: row;\n }\n .navbar-expand-lg .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-lg .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-lg > .container,\n .navbar-expand-lg > .container-fluid {\n -ms-flex-wrap: nowrap;\n flex-wrap: nowrap;\n }\n .navbar-expand-lg .navbar-collapse {\n display: -ms-flexbox !important;\n display: flex !important;\n -ms-flex-preferred-size: auto;\n flex-basis: auto;\n }\n .navbar-expand-lg .navbar-toggler {\n display: none;\n }\n}\n\n@media (max-width: 1199.98px) {\n .navbar-expand-xl > .container,\n .navbar-expand-xl > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 1200px) {\n .navbar-expand-xl {\n -ms-flex-flow: row nowrap;\n flex-flow: row nowrap;\n -ms-flex-pack: start;\n justify-content: flex-start;\n }\n .navbar-expand-xl .navbar-nav {\n -ms-flex-direction: row;\n flex-direction: row;\n }\n .navbar-expand-xl .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-xl .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-xl > .container,\n .navbar-expand-xl > .container-fluid {\n -ms-flex-wrap: nowrap;\n flex-wrap: nowrap;\n }\n .navbar-expand-xl .navbar-collapse {\n display: -ms-flexbox !important;\n display: flex !important;\n -ms-flex-preferred-size: auto;\n flex-basis: auto;\n }\n .navbar-expand-xl .navbar-toggler {\n display: none;\n }\n}\n\n.navbar-expand {\n -ms-flex-flow: row nowrap;\n flex-flow: row nowrap;\n -ms-flex-pack: start;\n justify-content: flex-start;\n}\n\n.navbar-expand > .container,\n.navbar-expand > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n}\n\n.navbar-expand .navbar-nav {\n -ms-flex-direction: row;\n flex-direction: row;\n}\n\n.navbar-expand .navbar-nav .dropdown-menu {\n position: absolute;\n}\n\n.navbar-expand .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n}\n\n.navbar-expand > .container,\n.navbar-expand > .container-fluid {\n -ms-flex-wrap: nowrap;\n flex-wrap: nowrap;\n}\n\n.navbar-expand .navbar-collapse {\n display: -ms-flexbox !important;\n display: flex !important;\n -ms-flex-preferred-size: auto;\n flex-basis: auto;\n}\n\n.navbar-expand .navbar-toggler {\n display: none;\n}\n\n.navbar-light .navbar-brand {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-brand:hover, .navbar-light .navbar-brand:focus {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-nav .nav-link {\n color: rgba(0, 0, 0, 0.5);\n}\n\n.navbar-light .navbar-nav .nav-link:hover, .navbar-light .navbar-nav .nav-link:focus {\n color: rgba(0, 0, 0, 0.7);\n}\n\n.navbar-light .navbar-nav .nav-link.disabled {\n color: rgba(0, 0, 0, 0.3);\n}\n\n.navbar-light .navbar-nav .show > .nav-link,\n.navbar-light .navbar-nav .active > .nav-link,\n.navbar-light .navbar-nav .nav-link.show,\n.navbar-light .navbar-nav .nav-link.active {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-toggler {\n color: rgba(0, 0, 0, 0.5);\n border-color: rgba(0, 0, 0, 0.1);\n}\n\n.navbar-light .navbar-toggler-icon {\n background-image: url(\"data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e\");\n}\n\n.navbar-light .navbar-text {\n color: rgba(0, 0, 0, 0.5);\n}\n\n.navbar-light .navbar-text a {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-text a:hover, .navbar-light .navbar-text a:focus {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-dark .navbar-brand {\n color: #fff;\n}\n\n.navbar-dark .navbar-brand:hover, .navbar-dark .navbar-brand:focus {\n color: #fff;\n}\n\n.navbar-dark .navbar-nav .nav-link {\n color: rgba(255, 255, 255, 0.5);\n}\n\n.navbar-dark .navbar-nav .nav-link:hover, .navbar-dark .navbar-nav .nav-link:focus {\n color: rgba(255, 255, 255, 0.75);\n}\n\n.navbar-dark .navbar-nav .nav-link.disabled {\n color: rgba(255, 255, 255, 0.25);\n}\n\n.navbar-dark .navbar-nav .show > .nav-link,\n.navbar-dark .navbar-nav .active > .nav-link,\n.navbar-dark .navbar-nav .nav-link.show,\n.navbar-dark .navbar-nav .nav-link.active {\n color: #fff;\n}\n\n.navbar-dark .navbar-toggler {\n color: rgba(255, 255, 255, 0.5);\n border-color: rgba(255, 255, 255, 0.1);\n}\n\n.navbar-dark .navbar-toggler-icon {\n background-image: url(\"data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e\");\n}\n\n.navbar-dark .navbar-text {\n color: rgba(255, 255, 255, 0.5);\n}\n\n.navbar-dark .navbar-text a {\n color: #fff;\n}\n\n.navbar-dark .navbar-text a:hover, .navbar-dark .navbar-text a:focus {\n color: #fff;\n}\n\n.card {\n position: relative;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n min-width: 0;\n word-wrap: break-word;\n background-color: #fff;\n background-clip: border-box;\n border: 1px solid rgba(0, 0, 0, 0.125);\n border-radius: 0.25rem;\n}\n\n.card > hr {\n margin-right: 0;\n margin-left: 0;\n}\n\n.card > .list-group:first-child .list-group-item:first-child {\n border-top-left-radius: 0.25rem;\n border-top-right-radius: 0.25rem;\n}\n\n.card > .list-group:last-child .list-group-item:last-child {\n border-bottom-right-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n}\n\n.card-body {\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n padding: 1.25rem;\n}\n\n.card-title {\n margin-bottom: 0.75rem;\n}\n\n.card-subtitle {\n margin-top: -0.375rem;\n margin-bottom: 0;\n}\n\n.card-text:last-child {\n margin-bottom: 0;\n}\n\n.card-link:hover {\n text-decoration: none;\n}\n\n.card-link + .card-link {\n margin-left: 1.25rem;\n}\n\n.card-header {\n padding: 0.75rem 1.25rem;\n margin-bottom: 0;\n background-color: rgba(0, 0, 0, 0.03);\n border-bottom: 1px solid rgba(0, 0, 0, 0.125);\n}\n\n.card-header:first-child {\n border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0;\n}\n\n.card-header + .list-group .list-group-item:first-child {\n border-top: 0;\n}\n\n.card-footer {\n padding: 0.75rem 1.25rem;\n background-color: rgba(0, 0, 0, 0.03);\n border-top: 1px solid rgba(0, 0, 0, 0.125);\n}\n\n.card-footer:last-child {\n border-radius: 0 0 calc(0.25rem - 1px) calc(0.25rem - 1px);\n}\n\n.card-header-tabs {\n margin-right: -0.625rem;\n margin-bottom: -0.75rem;\n margin-left: -0.625rem;\n border-bottom: 0;\n}\n\n.card-header-pills {\n margin-right: -0.625rem;\n margin-left: -0.625rem;\n}\n\n.card-img-overlay {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n padding: 1.25rem;\n}\n\n.card-img {\n width: 100%;\n border-radius: calc(0.25rem - 1px);\n}\n\n.card-img-top {\n width: 100%;\n border-top-left-radius: calc(0.25rem - 1px);\n border-top-right-radius: calc(0.25rem - 1px);\n}\n\n.card-img-bottom {\n width: 100%;\n border-bottom-right-radius: calc(0.25rem - 1px);\n border-bottom-left-radius: calc(0.25rem - 1px);\n}\n\n.card-deck {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n\n.card-deck .card {\n margin-bottom: 15px;\n}\n\n@media (min-width: 576px) {\n .card-deck {\n -ms-flex-flow: row wrap;\n flex-flow: row wrap;\n margin-right: -15px;\n margin-left: -15px;\n }\n .card-deck .card {\n display: -ms-flexbox;\n display: flex;\n -ms-flex: 1 0 0%;\n flex: 1 0 0%;\n -ms-flex-direction: column;\n flex-direction: column;\n margin-right: 15px;\n margin-bottom: 0;\n margin-left: 15px;\n }\n}\n\n.card-group {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n}\n\n.card-group > .card {\n margin-bottom: 15px;\n}\n\n@media (min-width: 576px) {\n .card-group {\n -ms-flex-flow: row wrap;\n flex-flow: row wrap;\n }\n .card-group > .card {\n -ms-flex: 1 0 0%;\n flex: 1 0 0%;\n margin-bottom: 0;\n }\n .card-group > .card + .card {\n margin-left: 0;\n border-left: 0;\n }\n .card-group > .card:not(:last-child) {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n }\n .card-group > .card:not(:last-child) .card-img-top,\n .card-group > .card:not(:last-child) .card-header {\n border-top-right-radius: 0;\n }\n .card-group > .card:not(:last-child) .card-img-bottom,\n .card-group > .card:not(:last-child) .card-footer {\n border-bottom-right-radius: 0;\n }\n .card-group > .card:not(:first-child) {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n }\n .card-group > .card:not(:first-child) .card-img-top,\n .card-group > .card:not(:first-child) .card-header {\n border-top-left-radius: 0;\n }\n .card-group > .card:not(:first-child) .card-img-bottom,\n .card-group > .card:not(:first-child) .card-footer {\n border-bottom-left-radius: 0;\n }\n}\n\n.card-columns .card {\n margin-bottom: 0.75rem;\n}\n\n@media (min-width: 576px) {\n .card-columns {\n -webkit-column-count: 3;\n -moz-column-count: 3;\n column-count: 3;\n -webkit-column-gap: 1.25rem;\n -moz-column-gap: 1.25rem;\n column-gap: 1.25rem;\n orphans: 1;\n widows: 1;\n }\n .card-columns .card {\n display: inline-block;\n width: 100%;\n }\n}\n\n.accordion > .card {\n overflow: hidden;\n}\n\n.accordion > .card:not(:first-of-type) .card-header:first-child {\n border-radius: 0;\n}\n\n.accordion > .card:not(:first-of-type):not(:last-of-type) {\n border-bottom: 0;\n border-radius: 0;\n}\n\n.accordion > .card:first-of-type {\n border-bottom: 0;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.accordion > .card:last-of-type {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n.accordion > .card .card-header {\n margin-bottom: -1px;\n}\n\n.breadcrumb {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-wrap: wrap;\n flex-wrap: wrap;\n padding: 0.75rem 1rem;\n margin-bottom: 1rem;\n list-style: none;\n background-color: #e9ecef;\n border-radius: 0.25rem;\n}\n\n.breadcrumb-item + .breadcrumb-item {\n padding-left: 0.5rem;\n}\n\n.breadcrumb-item + .breadcrumb-item::before {\n display: inline-block;\n padding-right: 0.5rem;\n color: #6c757d;\n content: \"/\";\n}\n\n.breadcrumb-item + .breadcrumb-item:hover::before {\n text-decoration: underline;\n}\n\n.breadcrumb-item + .breadcrumb-item:hover::before {\n text-decoration: none;\n}\n\n.breadcrumb-item.active {\n color: #6c757d;\n}\n\n.pagination {\n display: -ms-flexbox;\n display: flex;\n padding-left: 0;\n list-style: none;\n border-radius: 0.25rem;\n}\n\n.page-link {\n position: relative;\n display: block;\n padding: 0.5rem 0.75rem;\n margin-left: -1px;\n line-height: 1.25;\n color: #007bff;\n background-color: #fff;\n border: 1px solid #dee2e6;\n}\n\n.page-link:hover {\n z-index: 2;\n color: #0056b3;\n text-decoration: none;\n background-color: #e9ecef;\n border-color: #dee2e6;\n}\n\n.page-link:focus {\n z-index: 2;\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.page-item:first-child .page-link {\n margin-left: 0;\n border-top-left-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n}\n\n.page-item:last-child .page-link {\n border-top-right-radius: 0.25rem;\n border-bottom-right-radius: 0.25rem;\n}\n\n.page-item.active .page-link {\n z-index: 1;\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.page-item.disabled .page-link {\n color: #6c757d;\n pointer-events: none;\n cursor: auto;\n background-color: #fff;\n border-color: #dee2e6;\n}\n\n.pagination-lg .page-link {\n padding: 0.75rem 1.5rem;\n font-size: 1.25rem;\n line-height: 1.5;\n}\n\n.pagination-lg .page-item:first-child .page-link {\n border-top-left-radius: 0.3rem;\n border-bottom-left-radius: 0.3rem;\n}\n\n.pagination-lg .page-item:last-child .page-link {\n border-top-right-radius: 0.3rem;\n border-bottom-right-radius: 0.3rem;\n}\n\n.pagination-sm .page-link {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n}\n\n.pagination-sm .page-item:first-child .page-link {\n border-top-left-radius: 0.2rem;\n border-bottom-left-radius: 0.2rem;\n}\n\n.pagination-sm .page-item:last-child .page-link {\n border-top-right-radius: 0.2rem;\n border-bottom-right-radius: 0.2rem;\n}\n\n.badge {\n display: inline-block;\n padding: 0.25em 0.4em;\n font-size: 75%;\n font-weight: 700;\n line-height: 1;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: 0.25rem;\n transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .badge {\n transition: none;\n }\n}\n\na.badge:hover, a.badge:focus {\n text-decoration: none;\n}\n\n.badge:empty {\n display: none;\n}\n\n.btn .badge {\n position: relative;\n top: -1px;\n}\n\n.badge-pill {\n padding-right: 0.6em;\n padding-left: 0.6em;\n border-radius: 10rem;\n}\n\n.badge-primary {\n color: #fff;\n background-color: #007bff;\n}\n\na.badge-primary:hover, a.badge-primary:focus {\n color: #fff;\n background-color: #0062cc;\n}\n\na.badge-primary:focus, a.badge-primary.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);\n}\n\n.badge-secondary {\n color: #fff;\n background-color: #6c757d;\n}\n\na.badge-secondary:hover, a.badge-secondary:focus {\n color: #fff;\n background-color: #545b62;\n}\n\na.badge-secondary:focus, a.badge-secondary.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);\n}\n\n.badge-success {\n color: #fff;\n background-color: #28a745;\n}\n\na.badge-success:hover, a.badge-success:focus {\n color: #fff;\n background-color: #1e7e34;\n}\n\na.badge-success:focus, a.badge-success.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);\n}\n\n.badge-info {\n color: #fff;\n background-color: #17a2b8;\n}\n\na.badge-info:hover, a.badge-info:focus {\n color: #fff;\n background-color: #117a8b;\n}\n\na.badge-info:focus, a.badge-info.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);\n}\n\n.badge-warning {\n color: #212529;\n background-color: #ffc107;\n}\n\na.badge-warning:hover, a.badge-warning:focus {\n color: #212529;\n background-color: #d39e00;\n}\n\na.badge-warning:focus, a.badge-warning.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);\n}\n\n.badge-danger {\n color: #fff;\n background-color: #dc3545;\n}\n\na.badge-danger:hover, a.badge-danger:focus {\n color: #fff;\n background-color: #bd2130;\n}\n\na.badge-danger:focus, a.badge-danger.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);\n}\n\n.badge-light {\n color: #212529;\n background-color: #f8f9fa;\n}\n\na.badge-light:hover, a.badge-light:focus {\n color: #212529;\n background-color: #dae0e5;\n}\n\na.badge-light:focus, a.badge-light.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);\n}\n\n.badge-dark {\n color: #fff;\n background-color: #343a40;\n}\n\na.badge-dark:hover, a.badge-dark:focus {\n color: #fff;\n background-color: #1d2124;\n}\n\na.badge-dark:focus, a.badge-dark.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);\n}\n\n.jumbotron {\n padding: 2rem 1rem;\n margin-bottom: 2rem;\n background-color: #e9ecef;\n border-radius: 0.3rem;\n}\n\n@media (min-width: 576px) {\n .jumbotron {\n padding: 4rem 2rem;\n }\n}\n\n.jumbotron-fluid {\n padding-right: 0;\n padding-left: 0;\n border-radius: 0;\n}\n\n.alert {\n position: relative;\n padding: 0.75rem 1.25rem;\n margin-bottom: 1rem;\n border: 1px solid transparent;\n border-radius: 0.25rem;\n}\n\n.alert-heading {\n color: inherit;\n}\n\n.alert-link {\n font-weight: 700;\n}\n\n.alert-dismissible {\n padding-right: 4rem;\n}\n\n.alert-dismissible .close {\n position: absolute;\n top: 0;\n right: 0;\n padding: 0.75rem 1.25rem;\n color: inherit;\n}\n\n.alert-primary {\n color: #004085;\n background-color: #cce5ff;\n border-color: #b8daff;\n}\n\n.alert-primary hr {\n border-top-color: #9fcdff;\n}\n\n.alert-primary .alert-link {\n color: #002752;\n}\n\n.alert-secondary {\n color: #383d41;\n background-color: #e2e3e5;\n border-color: #d6d8db;\n}\n\n.alert-secondary hr {\n border-top-color: #c8cbcf;\n}\n\n.alert-secondary .alert-link {\n color: #202326;\n}\n\n.alert-success {\n color: #155724;\n background-color: #d4edda;\n border-color: #c3e6cb;\n}\n\n.alert-success hr {\n border-top-color: #b1dfbb;\n}\n\n.alert-success .alert-link {\n color: #0b2e13;\n}\n\n.alert-info {\n color: #0c5460;\n background-color: #d1ecf1;\n border-color: #bee5eb;\n}\n\n.alert-info hr {\n border-top-color: #abdde5;\n}\n\n.alert-info .alert-link {\n color: #062c33;\n}\n\n.alert-warning {\n color: #856404;\n background-color: #fff3cd;\n border-color: #ffeeba;\n}\n\n.alert-warning hr {\n border-top-color: #ffe8a1;\n}\n\n.alert-warning .alert-link {\n color: #533f03;\n}\n\n.alert-danger {\n color: #721c24;\n background-color: #f8d7da;\n border-color: #f5c6cb;\n}\n\n.alert-danger hr {\n border-top-color: #f1b0b7;\n}\n\n.alert-danger .alert-link {\n color: #491217;\n}\n\n.alert-light {\n color: #818182;\n background-color: #fefefe;\n border-color: #fdfdfe;\n}\n\n.alert-light hr {\n border-top-color: #ececf6;\n}\n\n.alert-light .alert-link {\n color: #686868;\n}\n\n.alert-dark {\n color: #1b1e21;\n background-color: #d6d8d9;\n border-color: #c6c8ca;\n}\n\n.alert-dark hr {\n border-top-color: #b9bbbe;\n}\n\n.alert-dark .alert-link {\n color: #040505;\n}\n\n@-webkit-keyframes progress-bar-stripes {\n from {\n background-position: 1rem 0;\n }\n to {\n background-position: 0 0;\n }\n}\n\n@keyframes progress-bar-stripes {\n from {\n background-position: 1rem 0;\n }\n to {\n background-position: 0 0;\n }\n}\n\n.progress {\n display: -ms-flexbox;\n display: flex;\n height: 1rem;\n overflow: hidden;\n font-size: 0.75rem;\n background-color: #e9ecef;\n border-radius: 0.25rem;\n}\n\n.progress-bar {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n -ms-flex-pack: center;\n justify-content: center;\n color: #fff;\n text-align: center;\n white-space: nowrap;\n background-color: #007bff;\n transition: width 0.6s ease;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .progress-bar {\n transition: none;\n }\n}\n\n.progress-bar-striped {\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-size: 1rem 1rem;\n}\n\n.progress-bar-animated {\n -webkit-animation: progress-bar-stripes 1s linear infinite;\n animation: progress-bar-stripes 1s linear infinite;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .progress-bar-animated {\n -webkit-animation: none;\n animation: none;\n }\n}\n\n.media {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: start;\n align-items: flex-start;\n}\n\n.media-body {\n -ms-flex: 1;\n flex: 1;\n}\n\n.list-group {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n padding-left: 0;\n margin-bottom: 0;\n}\n\n.list-group-item-action {\n width: 100%;\n color: #495057;\n text-align: inherit;\n}\n\n.list-group-item-action:hover, .list-group-item-action:focus {\n z-index: 1;\n color: #495057;\n text-decoration: none;\n background-color: #f8f9fa;\n}\n\n.list-group-item-action:active {\n color: #212529;\n background-color: #e9ecef;\n}\n\n.list-group-item {\n position: relative;\n display: block;\n padding: 0.75rem 1.25rem;\n margin-bottom: -1px;\n background-color: #fff;\n border: 1px solid rgba(0, 0, 0, 0.125);\n}\n\n.list-group-item:first-child {\n border-top-left-radius: 0.25rem;\n border-top-right-radius: 0.25rem;\n}\n\n.list-group-item:last-child {\n margin-bottom: 0;\n border-bottom-right-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n}\n\n.list-group-item.disabled, .list-group-item:disabled {\n color: #6c757d;\n pointer-events: none;\n background-color: #fff;\n}\n\n.list-group-item.active {\n z-index: 2;\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.list-group-horizontal {\n -ms-flex-direction: row;\n flex-direction: row;\n}\n\n.list-group-horizontal .list-group-item {\n margin-right: -1px;\n margin-bottom: 0;\n}\n\n.list-group-horizontal .list-group-item:first-child {\n border-top-left-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n border-top-right-radius: 0;\n}\n\n.list-group-horizontal .list-group-item:last-child {\n margin-right: 0;\n border-top-right-radius: 0.25rem;\n border-bottom-right-radius: 0.25rem;\n border-bottom-left-radius: 0;\n}\n\n@media (min-width: 576px) {\n .list-group-horizontal-sm {\n -ms-flex-direction: row;\n flex-direction: row;\n }\n .list-group-horizontal-sm .list-group-item {\n margin-right: -1px;\n margin-bottom: 0;\n }\n .list-group-horizontal-sm .list-group-item:first-child {\n border-top-left-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n border-top-right-radius: 0;\n }\n .list-group-horizontal-sm .list-group-item:last-child {\n margin-right: 0;\n border-top-right-radius: 0.25rem;\n border-bottom-right-radius: 0.25rem;\n border-bottom-left-radius: 0;\n }\n}\n\n@media (min-width: 768px) {\n .list-group-horizontal-md {\n -ms-flex-direction: row;\n flex-direction: row;\n }\n .list-group-horizontal-md .list-group-item {\n margin-right: -1px;\n margin-bottom: 0;\n }\n .list-group-horizontal-md .list-group-item:first-child {\n border-top-left-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n border-top-right-radius: 0;\n }\n .list-group-horizontal-md .list-group-item:last-child {\n margin-right: 0;\n border-top-right-radius: 0.25rem;\n border-bottom-right-radius: 0.25rem;\n border-bottom-left-radius: 0;\n }\n}\n\n@media (min-width: 992px) {\n .list-group-horizontal-lg {\n -ms-flex-direction: row;\n flex-direction: row;\n }\n .list-group-horizontal-lg .list-group-item {\n margin-right: -1px;\n margin-bottom: 0;\n }\n .list-group-horizontal-lg .list-group-item:first-child {\n border-top-left-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n border-top-right-radius: 0;\n }\n .list-group-horizontal-lg .list-group-item:last-child {\n margin-right: 0;\n border-top-right-radius: 0.25rem;\n border-bottom-right-radius: 0.25rem;\n border-bottom-left-radius: 0;\n }\n}\n\n@media (min-width: 1200px) {\n .list-group-horizontal-xl {\n -ms-flex-direction: row;\n flex-direction: row;\n }\n .list-group-horizontal-xl .list-group-item {\n margin-right: -1px;\n margin-bottom: 0;\n }\n .list-group-horizontal-xl .list-group-item:first-child {\n border-top-left-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n border-top-right-radius: 0;\n }\n .list-group-horizontal-xl .list-group-item:last-child {\n margin-right: 0;\n border-top-right-radius: 0.25rem;\n border-bottom-right-radius: 0.25rem;\n border-bottom-left-radius: 0;\n }\n}\n\n.list-group-flush .list-group-item {\n border-right: 0;\n border-left: 0;\n border-radius: 0;\n}\n\n.list-group-flush .list-group-item:last-child {\n margin-bottom: -1px;\n}\n\n.list-group-flush:first-child .list-group-item:first-child {\n border-top: 0;\n}\n\n.list-group-flush:last-child .list-group-item:last-child {\n margin-bottom: 0;\n border-bottom: 0;\n}\n\n.list-group-item-primary {\n color: #004085;\n background-color: #b8daff;\n}\n\n.list-group-item-primary.list-group-item-action:hover, .list-group-item-primary.list-group-item-action:focus {\n color: #004085;\n background-color: #9fcdff;\n}\n\n.list-group-item-primary.list-group-item-action.active {\n color: #fff;\n background-color: #004085;\n border-color: #004085;\n}\n\n.list-group-item-secondary {\n color: #383d41;\n background-color: #d6d8db;\n}\n\n.list-group-item-secondary.list-group-item-action:hover, .list-group-item-secondary.list-group-item-action:focus {\n color: #383d41;\n background-color: #c8cbcf;\n}\n\n.list-group-item-secondary.list-group-item-action.active {\n color: #fff;\n background-color: #383d41;\n border-color: #383d41;\n}\n\n.list-group-item-success {\n color: #155724;\n background-color: #c3e6cb;\n}\n\n.list-group-item-success.list-group-item-action:hover, .list-group-item-success.list-group-item-action:focus {\n color: #155724;\n background-color: #b1dfbb;\n}\n\n.list-group-item-success.list-group-item-action.active {\n color: #fff;\n background-color: #155724;\n border-color: #155724;\n}\n\n.list-group-item-info {\n color: #0c5460;\n background-color: #bee5eb;\n}\n\n.list-group-item-info.list-group-item-action:hover, .list-group-item-info.list-group-item-action:focus {\n color: #0c5460;\n background-color: #abdde5;\n}\n\n.list-group-item-info.list-group-item-action.active {\n color: #fff;\n background-color: #0c5460;\n border-color: #0c5460;\n}\n\n.list-group-item-warning {\n color: #856404;\n background-color: #ffeeba;\n}\n\n.list-group-item-warning.list-group-item-action:hover, .list-group-item-warning.list-group-item-action:focus {\n color: #856404;\n background-color: #ffe8a1;\n}\n\n.list-group-item-warning.list-group-item-action.active {\n color: #fff;\n background-color: #856404;\n border-color: #856404;\n}\n\n.list-group-item-danger {\n color: #721c24;\n background-color: #f5c6cb;\n}\n\n.list-group-item-danger.list-group-item-action:hover, .list-group-item-danger.list-group-item-action:focus {\n color: #721c24;\n background-color: #f1b0b7;\n}\n\n.list-group-item-danger.list-group-item-action.active {\n color: #fff;\n background-color: #721c24;\n border-color: #721c24;\n}\n\n.list-group-item-light {\n color: #818182;\n background-color: #fdfdfe;\n}\n\n.list-group-item-light.list-group-item-action:hover, .list-group-item-light.list-group-item-action:focus {\n color: #818182;\n background-color: #ececf6;\n}\n\n.list-group-item-light.list-group-item-action.active {\n color: #fff;\n background-color: #818182;\n border-color: #818182;\n}\n\n.list-group-item-dark {\n color: #1b1e21;\n background-color: #c6c8ca;\n}\n\n.list-group-item-dark.list-group-item-action:hover, .list-group-item-dark.list-group-item-action:focus {\n color: #1b1e21;\n background-color: #b9bbbe;\n}\n\n.list-group-item-dark.list-group-item-action.active {\n color: #fff;\n background-color: #1b1e21;\n border-color: #1b1e21;\n}\n\n.close {\n float: right;\n font-size: 1.5rem;\n font-weight: 700;\n line-height: 1;\n color: #000;\n text-shadow: 0 1px 0 #fff;\n opacity: .5;\n}\n\n.close:hover {\n color: #000;\n text-decoration: none;\n}\n\n.close:not(:disabled):not(.disabled):hover, .close:not(:disabled):not(.disabled):focus {\n opacity: .75;\n}\n\nbutton.close {\n padding: 0;\n background-color: transparent;\n border: 0;\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n}\n\na.close.disabled {\n pointer-events: none;\n}\n\n.toast {\n max-width: 350px;\n overflow: hidden;\n font-size: 0.875rem;\n background-color: rgba(255, 255, 255, 0.85);\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.1);\n box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.1);\n -webkit-backdrop-filter: blur(10px);\n backdrop-filter: blur(10px);\n opacity: 0;\n border-radius: 0.25rem;\n}\n\n.toast:not(:last-child) {\n margin-bottom: 0.75rem;\n}\n\n.toast.showing {\n opacity: 1;\n}\n\n.toast.show {\n display: block;\n opacity: 1;\n}\n\n.toast.hide {\n display: none;\n}\n\n.toast-header {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n padding: 0.25rem 0.75rem;\n color: #6c757d;\n background-color: rgba(255, 255, 255, 0.85);\n background-clip: padding-box;\n border-bottom: 1px solid rgba(0, 0, 0, 0.05);\n}\n\n.toast-body {\n padding: 0.75rem;\n}\n\n.modal-open {\n overflow: hidden;\n}\n\n.modal-open .modal {\n overflow-x: hidden;\n overflow-y: auto;\n}\n\n.modal {\n position: fixed;\n top: 0;\n left: 0;\n z-index: 1050;\n display: none;\n width: 100%;\n height: 100%;\n overflow: hidden;\n outline: 0;\n}\n\n.modal-dialog {\n position: relative;\n width: auto;\n margin: 0.5rem;\n pointer-events: none;\n}\n\n.modal.fade .modal-dialog {\n transition: -webkit-transform 0.3s ease-out;\n transition: transform 0.3s ease-out;\n transition: transform 0.3s ease-out, -webkit-transform 0.3s ease-out;\n -webkit-transform: translate(0, -50px);\n transform: translate(0, -50px);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .modal.fade .modal-dialog {\n transition: none;\n }\n}\n\n.modal.show .modal-dialog {\n -webkit-transform: none;\n transform: none;\n}\n\n.modal-dialog-scrollable {\n display: -ms-flexbox;\n display: flex;\n max-height: calc(100% - 1rem);\n}\n\n.modal-dialog-scrollable .modal-content {\n max-height: calc(100vh - 1rem);\n overflow: hidden;\n}\n\n.modal-dialog-scrollable .modal-header,\n.modal-dialog-scrollable .modal-footer {\n -ms-flex-negative: 0;\n flex-shrink: 0;\n}\n\n.modal-dialog-scrollable .modal-body {\n overflow-y: auto;\n}\n\n.modal-dialog-centered {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n min-height: calc(100% - 1rem);\n}\n\n.modal-dialog-centered::before {\n display: block;\n height: calc(100vh - 1rem);\n content: \"\";\n}\n\n.modal-dialog-centered.modal-dialog-scrollable {\n -ms-flex-direction: column;\n flex-direction: column;\n -ms-flex-pack: center;\n justify-content: center;\n height: 100%;\n}\n\n.modal-dialog-centered.modal-dialog-scrollable .modal-content {\n max-height: none;\n}\n\n.modal-dialog-centered.modal-dialog-scrollable::before {\n content: none;\n}\n\n.modal-content {\n position: relative;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-direction: column;\n flex-direction: column;\n width: 100%;\n pointer-events: auto;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 0.3rem;\n outline: 0;\n}\n\n.modal-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n z-index: 1040;\n width: 100vw;\n height: 100vh;\n background-color: #000;\n}\n\n.modal-backdrop.fade {\n opacity: 0;\n}\n\n.modal-backdrop.show {\n opacity: 0.5;\n}\n\n.modal-header {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: start;\n align-items: flex-start;\n -ms-flex-pack: justify;\n justify-content: space-between;\n padding: 1rem 1rem;\n border-bottom: 1px solid #dee2e6;\n border-top-left-radius: 0.3rem;\n border-top-right-radius: 0.3rem;\n}\n\n.modal-header .close {\n padding: 1rem 1rem;\n margin: -1rem -1rem -1rem auto;\n}\n\n.modal-title {\n margin-bottom: 0;\n line-height: 1.5;\n}\n\n.modal-body {\n position: relative;\n -ms-flex: 1 1 auto;\n flex: 1 1 auto;\n padding: 1rem;\n}\n\n.modal-footer {\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: end;\n justify-content: flex-end;\n padding: 1rem;\n border-top: 1px solid #dee2e6;\n border-bottom-right-radius: 0.3rem;\n border-bottom-left-radius: 0.3rem;\n}\n\n.modal-footer > :not(:first-child) {\n margin-left: .25rem;\n}\n\n.modal-footer > :not(:last-child) {\n margin-right: .25rem;\n}\n\n.modal-scrollbar-measure {\n position: absolute;\n top: -9999px;\n width: 50px;\n height: 50px;\n overflow: scroll;\n}\n\n@media (min-width: 576px) {\n .modal-dialog {\n max-width: 500px;\n margin: 1.75rem auto;\n }\n .modal-dialog-scrollable {\n max-height: calc(100% - 3.5rem);\n }\n .modal-dialog-scrollable .modal-content {\n max-height: calc(100vh - 3.5rem);\n }\n .modal-dialog-centered {\n min-height: calc(100% - 3.5rem);\n }\n .modal-dialog-centered::before {\n height: calc(100vh - 3.5rem);\n }\n .modal-sm {\n max-width: 300px;\n }\n}\n\n@media (min-width: 992px) {\n .modal-lg,\n .modal-xl {\n max-width: 800px;\n }\n}\n\n@media (min-width: 1200px) {\n .modal-xl {\n max-width: 1140px;\n }\n}\n\n.tooltip {\n position: absolute;\n z-index: 1070;\n display: block;\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n font-style: normal;\n font-weight: 400;\n line-height: 1.5;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n letter-spacing: normal;\n word-break: normal;\n word-spacing: normal;\n white-space: normal;\n line-break: auto;\n font-size: 0.875rem;\n word-wrap: break-word;\n opacity: 0;\n}\n\n.tooltip.show {\n opacity: 0.9;\n}\n\n.tooltip .arrow {\n position: absolute;\n display: block;\n width: 0.8rem;\n height: 0.4rem;\n}\n\n.tooltip .arrow::before {\n position: absolute;\n content: \"\";\n border-color: transparent;\n border-style: solid;\n}\n\n.bs-tooltip-top, .bs-tooltip-auto[x-placement^=\"top\"] {\n padding: 0.4rem 0;\n}\n\n.bs-tooltip-top .arrow, .bs-tooltip-auto[x-placement^=\"top\"] .arrow {\n bottom: 0;\n}\n\n.bs-tooltip-top .arrow::before, .bs-tooltip-auto[x-placement^=\"top\"] .arrow::before {\n top: 0;\n border-width: 0.4rem 0.4rem 0;\n border-top-color: #000;\n}\n\n.bs-tooltip-right, .bs-tooltip-auto[x-placement^=\"right\"] {\n padding: 0 0.4rem;\n}\n\n.bs-tooltip-right .arrow, .bs-tooltip-auto[x-placement^=\"right\"] .arrow {\n left: 0;\n width: 0.4rem;\n height: 0.8rem;\n}\n\n.bs-tooltip-right .arrow::before, .bs-tooltip-auto[x-placement^=\"right\"] .arrow::before {\n right: 0;\n border-width: 0.4rem 0.4rem 0.4rem 0;\n border-right-color: #000;\n}\n\n.bs-tooltip-bottom, .bs-tooltip-auto[x-placement^=\"bottom\"] {\n padding: 0.4rem 0;\n}\n\n.bs-tooltip-bottom .arrow, .bs-tooltip-auto[x-placement^=\"bottom\"] .arrow {\n top: 0;\n}\n\n.bs-tooltip-bottom .arrow::before, .bs-tooltip-auto[x-placement^=\"bottom\"] .arrow::before {\n bottom: 0;\n border-width: 0 0.4rem 0.4rem;\n border-bottom-color: #000;\n}\n\n.bs-tooltip-left, .bs-tooltip-auto[x-placement^=\"left\"] {\n padding: 0 0.4rem;\n}\n\n.bs-tooltip-left .arrow, .bs-tooltip-auto[x-placement^=\"left\"] .arrow {\n right: 0;\n width: 0.4rem;\n height: 0.8rem;\n}\n\n.bs-tooltip-left .arrow::before, .bs-tooltip-auto[x-placement^=\"left\"] .arrow::before {\n left: 0;\n border-width: 0.4rem 0 0.4rem 0.4rem;\n border-left-color: #000;\n}\n\n.tooltip-inner {\n max-width: 200px;\n padding: 0.25rem 0.5rem;\n color: #fff;\n text-align: center;\n background-color: #000;\n border-radius: 0.25rem;\n}\n\n.popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 1060;\n display: block;\n max-width: 276px;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n font-style: normal;\n font-weight: 400;\n line-height: 1.5;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n letter-spacing: normal;\n word-break: normal;\n word-spacing: normal;\n white-space: normal;\n line-break: auto;\n font-size: 0.875rem;\n word-wrap: break-word;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 0.3rem;\n}\n\n.popover .arrow {\n position: absolute;\n display: block;\n width: 1rem;\n height: 0.5rem;\n margin: 0 0.3rem;\n}\n\n.popover .arrow::before, .popover .arrow::after {\n position: absolute;\n display: block;\n content: \"\";\n border-color: transparent;\n border-style: solid;\n}\n\n.bs-popover-top, .bs-popover-auto[x-placement^=\"top\"] {\n margin-bottom: 0.5rem;\n}\n\n.bs-popover-top > .arrow, .bs-popover-auto[x-placement^=\"top\"] > .arrow {\n bottom: calc((0.5rem + 1px) * -1);\n}\n\n.bs-popover-top > .arrow::before, .bs-popover-auto[x-placement^=\"top\"] > .arrow::before {\n bottom: 0;\n border-width: 0.5rem 0.5rem 0;\n border-top-color: rgba(0, 0, 0, 0.25);\n}\n\n.bs-popover-top > .arrow::after, .bs-popover-auto[x-placement^=\"top\"] > .arrow::after {\n bottom: 1px;\n border-width: 0.5rem 0.5rem 0;\n border-top-color: #fff;\n}\n\n.bs-popover-right, .bs-popover-auto[x-placement^=\"right\"] {\n margin-left: 0.5rem;\n}\n\n.bs-popover-right > .arrow, .bs-popover-auto[x-placement^=\"right\"] > .arrow {\n left: calc((0.5rem + 1px) * -1);\n width: 0.5rem;\n height: 1rem;\n margin: 0.3rem 0;\n}\n\n.bs-popover-right > .arrow::before, .bs-popover-auto[x-placement^=\"right\"] > .arrow::before {\n left: 0;\n border-width: 0.5rem 0.5rem 0.5rem 0;\n border-right-color: rgba(0, 0, 0, 0.25);\n}\n\n.bs-popover-right > .arrow::after, .bs-popover-auto[x-placement^=\"right\"] > .arrow::after {\n left: 1px;\n border-width: 0.5rem 0.5rem 0.5rem 0;\n border-right-color: #fff;\n}\n\n.bs-popover-bottom, .bs-popover-auto[x-placement^=\"bottom\"] {\n margin-top: 0.5rem;\n}\n\n.bs-popover-bottom > .arrow, .bs-popover-auto[x-placement^=\"bottom\"] > .arrow {\n top: calc((0.5rem + 1px) * -1);\n}\n\n.bs-popover-bottom > .arrow::before, .bs-popover-auto[x-placement^=\"bottom\"] > .arrow::before {\n top: 0;\n border-width: 0 0.5rem 0.5rem 0.5rem;\n border-bottom-color: rgba(0, 0, 0, 0.25);\n}\n\n.bs-popover-bottom > .arrow::after, .bs-popover-auto[x-placement^=\"bottom\"] > .arrow::after {\n top: 1px;\n border-width: 0 0.5rem 0.5rem 0.5rem;\n border-bottom-color: #fff;\n}\n\n.bs-popover-bottom .popover-header::before, .bs-popover-auto[x-placement^=\"bottom\"] .popover-header::before {\n position: absolute;\n top: 0;\n left: 50%;\n display: block;\n width: 1rem;\n margin-left: -0.5rem;\n content: \"\";\n border-bottom: 1px solid #f7f7f7;\n}\n\n.bs-popover-left, .bs-popover-auto[x-placement^=\"left\"] {\n margin-right: 0.5rem;\n}\n\n.bs-popover-left > .arrow, .bs-popover-auto[x-placement^=\"left\"] > .arrow {\n right: calc((0.5rem + 1px) * -1);\n width: 0.5rem;\n height: 1rem;\n margin: 0.3rem 0;\n}\n\n.bs-popover-left > .arrow::before, .bs-popover-auto[x-placement^=\"left\"] > .arrow::before {\n right: 0;\n border-width: 0.5rem 0 0.5rem 0.5rem;\n border-left-color: rgba(0, 0, 0, 0.25);\n}\n\n.bs-popover-left > .arrow::after, .bs-popover-auto[x-placement^=\"left\"] > .arrow::after {\n right: 1px;\n border-width: 0.5rem 0 0.5rem 0.5rem;\n border-left-color: #fff;\n}\n\n.popover-header {\n padding: 0.5rem 0.75rem;\n margin-bottom: 0;\n font-size: 1rem;\n background-color: #f7f7f7;\n border-bottom: 1px solid #ebebeb;\n border-top-left-radius: calc(0.3rem - 1px);\n border-top-right-radius: calc(0.3rem - 1px);\n}\n\n.popover-header:empty {\n display: none;\n}\n\n.popover-body {\n padding: 0.5rem 0.75rem;\n color: #212529;\n}\n\n.carousel {\n position: relative;\n}\n\n.carousel.pointer-event {\n -ms-touch-action: pan-y;\n touch-action: pan-y;\n}\n\n.carousel-inner {\n position: relative;\n width: 100%;\n overflow: hidden;\n}\n\n.carousel-inner::after {\n display: block;\n clear: both;\n content: \"\";\n}\n\n.carousel-item {\n position: relative;\n display: none;\n float: left;\n width: 100%;\n margin-right: -100%;\n -webkit-backface-visibility: hidden;\n backface-visibility: hidden;\n transition: -webkit-transform 0.6s ease-in-out;\n transition: transform 0.6s ease-in-out;\n transition: transform 0.6s ease-in-out, -webkit-transform 0.6s ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .carousel-item {\n transition: none;\n }\n}\n\n.carousel-item.active,\n.carousel-item-next,\n.carousel-item-prev {\n display: block;\n}\n\n.carousel-item-next:not(.carousel-item-left),\n.active.carousel-item-right {\n -webkit-transform: translateX(100%);\n transform: translateX(100%);\n}\n\n.carousel-item-prev:not(.carousel-item-right),\n.active.carousel-item-left {\n -webkit-transform: translateX(-100%);\n transform: translateX(-100%);\n}\n\n.carousel-fade .carousel-item {\n opacity: 0;\n transition-property: opacity;\n -webkit-transform: none;\n transform: none;\n}\n\n.carousel-fade .carousel-item.active,\n.carousel-fade .carousel-item-next.carousel-item-left,\n.carousel-fade .carousel-item-prev.carousel-item-right {\n z-index: 1;\n opacity: 1;\n}\n\n.carousel-fade .active.carousel-item-left,\n.carousel-fade .active.carousel-item-right {\n z-index: 0;\n opacity: 0;\n transition: 0s 0.6s opacity;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .carousel-fade .active.carousel-item-left,\n .carousel-fade .active.carousel-item-right {\n transition: none;\n }\n}\n\n.carousel-control-prev,\n.carousel-control-next {\n position: absolute;\n top: 0;\n bottom: 0;\n z-index: 1;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-align: center;\n align-items: center;\n -ms-flex-pack: center;\n justify-content: center;\n width: 15%;\n color: #fff;\n text-align: center;\n opacity: 0.5;\n transition: opacity 0.15s ease;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .carousel-control-prev,\n .carousel-control-next {\n transition: none;\n }\n}\n\n.carousel-control-prev:hover, .carousel-control-prev:focus,\n.carousel-control-next:hover,\n.carousel-control-next:focus {\n color: #fff;\n text-decoration: none;\n outline: 0;\n opacity: 0.9;\n}\n\n.carousel-control-prev {\n left: 0;\n}\n\n.carousel-control-next {\n right: 0;\n}\n\n.carousel-control-prev-icon,\n.carousel-control-next-icon {\n display: inline-block;\n width: 20px;\n height: 20px;\n background: no-repeat 50% / 100% 100%;\n}\n\n.carousel-control-prev-icon {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3e%3c/svg%3e\");\n}\n\n.carousel-control-next-icon {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3e%3c/svg%3e\");\n}\n\n.carousel-indicators {\n position: absolute;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 15;\n display: -ms-flexbox;\n display: flex;\n -ms-flex-pack: center;\n justify-content: center;\n padding-left: 0;\n margin-right: 15%;\n margin-left: 15%;\n list-style: none;\n}\n\n.carousel-indicators li {\n box-sizing: content-box;\n -ms-flex: 0 1 auto;\n flex: 0 1 auto;\n width: 30px;\n height: 3px;\n margin-right: 3px;\n margin-left: 3px;\n text-indent: -999px;\n cursor: pointer;\n background-color: #fff;\n background-clip: padding-box;\n border-top: 10px solid transparent;\n border-bottom: 10px solid transparent;\n opacity: .5;\n transition: opacity 0.6s ease;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .carousel-indicators li {\n transition: none;\n }\n}\n\n.carousel-indicators .active {\n opacity: 1;\n}\n\n.carousel-caption {\n position: absolute;\n right: 15%;\n bottom: 20px;\n left: 15%;\n z-index: 10;\n padding-top: 20px;\n padding-bottom: 20px;\n color: #fff;\n text-align: center;\n}\n\n@-webkit-keyframes spinner-border {\n to {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n\n@keyframes spinner-border {\n to {\n -webkit-transform: rotate(360deg);\n transform: rotate(360deg);\n }\n}\n\n.spinner-border {\n display: inline-block;\n width: 2rem;\n height: 2rem;\n vertical-align: text-bottom;\n border: 0.25em solid currentColor;\n border-right-color: transparent;\n border-radius: 50%;\n -webkit-animation: spinner-border .75s linear infinite;\n animation: spinner-border .75s linear infinite;\n}\n\n.spinner-border-sm {\n width: 1rem;\n height: 1rem;\n border-width: 0.2em;\n}\n\n@-webkit-keyframes spinner-grow {\n 0% {\n -webkit-transform: scale(0);\n transform: scale(0);\n }\n 50% {\n opacity: 1;\n }\n}\n\n@keyframes spinner-grow {\n 0% {\n -webkit-transform: scale(0);\n transform: scale(0);\n }\n 50% {\n opacity: 1;\n }\n}\n\n.spinner-grow {\n display: inline-block;\n width: 2rem;\n height: 2rem;\n vertical-align: text-bottom;\n background-color: currentColor;\n border-radius: 50%;\n opacity: 0;\n -webkit-animation: spinner-grow .75s linear infinite;\n animation: spinner-grow .75s linear infinite;\n}\n\n.spinner-grow-sm {\n width: 1rem;\n height: 1rem;\n}\n\n.align-baseline {\n vertical-align: baseline !important;\n}\n\n.align-top {\n vertical-align: top !important;\n}\n\n.align-middle {\n vertical-align: middle !important;\n}\n\n.align-bottom {\n vertical-align: bottom !important;\n}\n\n.align-text-bottom {\n vertical-align: text-bottom !important;\n}\n\n.align-text-top {\n vertical-align: text-top !important;\n}\n\n.bg-primary {\n background-color: #007bff !important;\n}\n\na.bg-primary:hover, a.bg-primary:focus,\nbutton.bg-primary:hover,\nbutton.bg-primary:focus {\n background-color: #0062cc !important;\n}\n\n.bg-secondary {\n background-color: #6c757d !important;\n}\n\na.bg-secondary:hover, a.bg-secondary:focus,\nbutton.bg-secondary:hover,\nbutton.bg-secondary:focus {\n background-color: #545b62 !important;\n}\n\n.bg-success {\n background-color: #28a745 !important;\n}\n\na.bg-success:hover, a.bg-success:focus,\nbutton.bg-success:hover,\nbutton.bg-success:focus {\n background-color: #1e7e34 !important;\n}\n\n.bg-info {\n background-color: #17a2b8 !important;\n}\n\na.bg-info:hover, a.bg-info:focus,\nbutton.bg-info:hover,\nbutton.bg-info:focus {\n background-color: #117a8b !important;\n}\n\n.bg-warning {\n background-color: #ffc107 !important;\n}\n\na.bg-warning:hover, a.bg-warning:focus,\nbutton.bg-warning:hover,\nbutton.bg-warning:focus {\n background-color: #d39e00 !important;\n}\n\n.bg-danger {\n background-color: #dc3545 !important;\n}\n\na.bg-danger:hover, a.bg-danger:focus,\nbutton.bg-danger:hover,\nbutton.bg-danger:focus {\n background-color: #bd2130 !important;\n}\n\n.bg-light {\n background-color: #f8f9fa !important;\n}\n\na.bg-light:hover, a.bg-light:focus,\nbutton.bg-light:hover,\nbutton.bg-light:focus {\n background-color: #dae0e5 !important;\n}\n\n.bg-dark {\n background-color: #343a40 !important;\n}\n\na.bg-dark:hover, a.bg-dark:focus,\nbutton.bg-dark:hover,\nbutton.bg-dark:focus {\n background-color: #1d2124 !important;\n}\n\n.bg-white {\n background-color: #fff !important;\n}\n\n.bg-transparent {\n background-color: transparent !important;\n}\n\n.border {\n border: 1px solid #dee2e6 !important;\n}\n\n.border-top {\n border-top: 1px solid #dee2e6 !important;\n}\n\n.border-right {\n border-right: 1px solid #dee2e6 !important;\n}\n\n.border-bottom {\n border-bottom: 1px solid #dee2e6 !important;\n}\n\n.border-left {\n border-left: 1px solid #dee2e6 !important;\n}\n\n.border-0 {\n border: 0 !important;\n}\n\n.border-top-0 {\n border-top: 0 !important;\n}\n\n.border-right-0 {\n border-right: 0 !important;\n}\n\n.border-bottom-0 {\n border-bottom: 0 !important;\n}\n\n.border-left-0 {\n border-left: 0 !important;\n}\n\n.border-primary {\n border-color: #007bff !important;\n}\n\n.border-secondary {\n border-color: #6c757d !important;\n}\n\n.border-success {\n border-color: #28a745 !important;\n}\n\n.border-info {\n border-color: #17a2b8 !important;\n}\n\n.border-warning {\n border-color: #ffc107 !important;\n}\n\n.border-danger {\n border-color: #dc3545 !important;\n}\n\n.border-light {\n border-color: #f8f9fa !important;\n}\n\n.border-dark {\n border-color: #343a40 !important;\n}\n\n.border-white {\n border-color: #fff !important;\n}\n\n.rounded-sm {\n border-radius: 0.2rem !important;\n}\n\n.rounded {\n border-radius: 0.25rem !important;\n}\n\n.rounded-top {\n border-top-left-radius: 0.25rem !important;\n border-top-right-radius: 0.25rem !important;\n}\n\n.rounded-right {\n border-top-right-radius: 0.25rem !important;\n border-bottom-right-radius: 0.25rem !important;\n}\n\n.rounded-bottom {\n border-bottom-right-radius: 0.25rem !important;\n border-bottom-left-radius: 0.25rem !important;\n}\n\n.rounded-left {\n border-top-left-radius: 0.25rem !important;\n border-bottom-left-radius: 0.25rem !important;\n}\n\n.rounded-lg {\n border-radius: 0.3rem !important;\n}\n\n.rounded-circle {\n border-radius: 50% !important;\n}\n\n.rounded-pill {\n border-radius: 50rem !important;\n}\n\n.rounded-0 {\n border-radius: 0 !important;\n}\n\n.clearfix::after {\n display: block;\n clear: both;\n content: \"\";\n}\n\n.d-none {\n display: none !important;\n}\n\n.d-inline {\n display: inline !important;\n}\n\n.d-inline-block {\n display: inline-block !important;\n}\n\n.d-block {\n display: block !important;\n}\n\n.d-table {\n display: table !important;\n}\n\n.d-table-row {\n display: table-row !important;\n}\n\n.d-table-cell {\n display: table-cell !important;\n}\n\n.d-flex {\n display: -ms-flexbox !important;\n display: flex !important;\n}\n\n.d-inline-flex {\n display: -ms-inline-flexbox !important;\n display: inline-flex !important;\n}\n\n@media (min-width: 576px) {\n .d-sm-none {\n display: none !important;\n }\n .d-sm-inline {\n display: inline !important;\n }\n .d-sm-inline-block {\n display: inline-block !important;\n }\n .d-sm-block {\n display: block !important;\n }\n .d-sm-table {\n display: table !important;\n }\n .d-sm-table-row {\n display: table-row !important;\n }\n .d-sm-table-cell {\n display: table-cell !important;\n }\n .d-sm-flex {\n display: -ms-flexbox !important;\n display: flex !important;\n }\n .d-sm-inline-flex {\n display: -ms-inline-flexbox !important;\n display: inline-flex !important;\n }\n}\n\n@media (min-width: 768px) {\n .d-md-none {\n display: none !important;\n }\n .d-md-inline {\n display: inline !important;\n }\n .d-md-inline-block {\n display: inline-block !important;\n }\n .d-md-block {\n display: block !important;\n }\n .d-md-table {\n display: table !important;\n }\n .d-md-table-row {\n display: table-row !important;\n }\n .d-md-table-cell {\n display: table-cell !important;\n }\n .d-md-flex {\n display: -ms-flexbox !important;\n display: flex !important;\n }\n .d-md-inline-flex {\n display: -ms-inline-flexbox !important;\n display: inline-flex !important;\n }\n}\n\n@media (min-width: 992px) {\n .d-lg-none {\n display: none !important;\n }\n .d-lg-inline {\n display: inline !important;\n }\n .d-lg-inline-block {\n display: inline-block !important;\n }\n .d-lg-block {\n display: block !important;\n }\n .d-lg-table {\n display: table !important;\n }\n .d-lg-table-row {\n display: table-row !important;\n }\n .d-lg-table-cell {\n display: table-cell !important;\n }\n .d-lg-flex {\n display: -ms-flexbox !important;\n display: flex !important;\n }\n .d-lg-inline-flex {\n display: -ms-inline-flexbox !important;\n display: inline-flex !important;\n }\n}\n\n@media (min-width: 1200px) {\n .d-xl-none {\n display: none !important;\n }\n .d-xl-inline {\n display: inline !important;\n }\n .d-xl-inline-block {\n display: inline-block !important;\n }\n .d-xl-block {\n display: block !important;\n }\n .d-xl-table {\n display: table !important;\n }\n .d-xl-table-row {\n display: table-row !important;\n }\n .d-xl-table-cell {\n display: table-cell !important;\n }\n .d-xl-flex {\n display: -ms-flexbox !important;\n display: flex !important;\n }\n .d-xl-inline-flex {\n display: -ms-inline-flexbox !important;\n display: inline-flex !important;\n }\n}\n\n@media print {\n .d-print-none {\n display: none !important;\n }\n .d-print-inline {\n display: inline !important;\n }\n .d-print-inline-block {\n display: inline-block !important;\n }\n .d-print-block {\n display: block !important;\n }\n .d-print-table {\n display: table !important;\n }\n .d-print-table-row {\n display: table-row !important;\n }\n .d-print-table-cell {\n display: table-cell !important;\n }\n .d-print-flex {\n display: -ms-flexbox !important;\n display: flex !important;\n }\n .d-print-inline-flex {\n display: -ms-inline-flexbox !important;\n display: inline-flex !important;\n }\n}\n\n.embed-responsive {\n position: relative;\n display: block;\n width: 100%;\n padding: 0;\n overflow: hidden;\n}\n\n.embed-responsive::before {\n display: block;\n content: \"\";\n}\n\n.embed-responsive .embed-responsive-item,\n.embed-responsive iframe,\n.embed-responsive embed,\n.embed-responsive object,\n.embed-responsive video {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 100%;\n height: 100%;\n border: 0;\n}\n\n.embed-responsive-21by9::before {\n padding-top: 42.857143%;\n}\n\n.embed-responsive-16by9::before {\n padding-top: 56.25%;\n}\n\n.embed-responsive-4by3::before {\n padding-top: 75%;\n}\n\n.embed-responsive-1by1::before {\n padding-top: 100%;\n}\n\n.flex-row {\n -ms-flex-direction: row !important;\n flex-direction: row !important;\n}\n\n.flex-column {\n -ms-flex-direction: column !important;\n flex-direction: column !important;\n}\n\n.flex-row-reverse {\n -ms-flex-direction: row-reverse !important;\n flex-direction: row-reverse !important;\n}\n\n.flex-column-reverse {\n -ms-flex-direction: column-reverse !important;\n flex-direction: column-reverse !important;\n}\n\n.flex-wrap {\n -ms-flex-wrap: wrap !important;\n flex-wrap: wrap !important;\n}\n\n.flex-nowrap {\n -ms-flex-wrap: nowrap !important;\n flex-wrap: nowrap !important;\n}\n\n.flex-wrap-reverse {\n -ms-flex-wrap: wrap-reverse !important;\n flex-wrap: wrap-reverse !important;\n}\n\n.flex-fill {\n -ms-flex: 1 1 auto !important;\n flex: 1 1 auto !important;\n}\n\n.flex-grow-0 {\n -ms-flex-positive: 0 !important;\n flex-grow: 0 !important;\n}\n\n.flex-grow-1 {\n -ms-flex-positive: 1 !important;\n flex-grow: 1 !important;\n}\n\n.flex-shrink-0 {\n -ms-flex-negative: 0 !important;\n flex-shrink: 0 !important;\n}\n\n.flex-shrink-1 {\n -ms-flex-negative: 1 !important;\n flex-shrink: 1 !important;\n}\n\n.justify-content-start {\n -ms-flex-pack: start !important;\n justify-content: flex-start !important;\n}\n\n.justify-content-end {\n -ms-flex-pack: end !important;\n justify-content: flex-end !important;\n}\n\n.justify-content-center {\n -ms-flex-pack: center !important;\n justify-content: center !important;\n}\n\n.justify-content-between {\n -ms-flex-pack: justify !important;\n justify-content: space-between !important;\n}\n\n.justify-content-around {\n -ms-flex-pack: distribute !important;\n justify-content: space-around !important;\n}\n\n.align-items-start {\n -ms-flex-align: start !important;\n align-items: flex-start !important;\n}\n\n.align-items-end {\n -ms-flex-align: end !important;\n align-items: flex-end !important;\n}\n\n.align-items-center {\n -ms-flex-align: center !important;\n align-items: center !important;\n}\n\n.align-items-baseline {\n -ms-flex-align: baseline !important;\n align-items: baseline !important;\n}\n\n.align-items-stretch {\n -ms-flex-align: stretch !important;\n align-items: stretch !important;\n}\n\n.align-content-start {\n -ms-flex-line-pack: start !important;\n align-content: flex-start !important;\n}\n\n.align-content-end {\n -ms-flex-line-pack: end !important;\n align-content: flex-end !important;\n}\n\n.align-content-center {\n -ms-flex-line-pack: center !important;\n align-content: center !important;\n}\n\n.align-content-between {\n -ms-flex-line-pack: justify !important;\n align-content: space-between !important;\n}\n\n.align-content-around {\n -ms-flex-line-pack: distribute !important;\n align-content: space-around !important;\n}\n\n.align-content-stretch {\n -ms-flex-line-pack: stretch !important;\n align-content: stretch !important;\n}\n\n.align-self-auto {\n -ms-flex-item-align: auto !important;\n align-self: auto !important;\n}\n\n.align-self-start {\n -ms-flex-item-align: start !important;\n align-self: flex-start !important;\n}\n\n.align-self-end {\n -ms-flex-item-align: end !important;\n align-self: flex-end !important;\n}\n\n.align-self-center {\n -ms-flex-item-align: center !important;\n align-self: center !important;\n}\n\n.align-self-baseline {\n -ms-flex-item-align: baseline !important;\n align-self: baseline !important;\n}\n\n.align-self-stretch {\n -ms-flex-item-align: stretch !important;\n align-self: stretch !important;\n}\n\n@media (min-width: 576px) {\n .flex-sm-row {\n -ms-flex-direction: row !important;\n flex-direction: row !important;\n }\n .flex-sm-column {\n -ms-flex-direction: column !important;\n flex-direction: column !important;\n }\n .flex-sm-row-reverse {\n -ms-flex-direction: row-reverse !important;\n flex-direction: row-reverse !important;\n }\n .flex-sm-column-reverse {\n -ms-flex-direction: column-reverse !important;\n flex-direction: column-reverse !important;\n }\n .flex-sm-wrap {\n -ms-flex-wrap: wrap !important;\n flex-wrap: wrap !important;\n }\n .flex-sm-nowrap {\n -ms-flex-wrap: nowrap !important;\n flex-wrap: nowrap !important;\n }\n .flex-sm-wrap-reverse {\n -ms-flex-wrap: wrap-reverse !important;\n flex-wrap: wrap-reverse !important;\n }\n .flex-sm-fill {\n -ms-flex: 1 1 auto !important;\n flex: 1 1 auto !important;\n }\n .flex-sm-grow-0 {\n -ms-flex-positive: 0 !important;\n flex-grow: 0 !important;\n }\n .flex-sm-grow-1 {\n -ms-flex-positive: 1 !important;\n flex-grow: 1 !important;\n }\n .flex-sm-shrink-0 {\n -ms-flex-negative: 0 !important;\n flex-shrink: 0 !important;\n }\n .flex-sm-shrink-1 {\n -ms-flex-negative: 1 !important;\n flex-shrink: 1 !important;\n }\n .justify-content-sm-start {\n -ms-flex-pack: start !important;\n justify-content: flex-start !important;\n }\n .justify-content-sm-end {\n -ms-flex-pack: end !important;\n justify-content: flex-end !important;\n }\n .justify-content-sm-center {\n -ms-flex-pack: center !important;\n justify-content: center !important;\n }\n .justify-content-sm-between {\n -ms-flex-pack: justify !important;\n justify-content: space-between !important;\n }\n .justify-content-sm-around {\n -ms-flex-pack: distribute !important;\n justify-content: space-around !important;\n }\n .align-items-sm-start {\n -ms-flex-align: start !important;\n align-items: flex-start !important;\n }\n .align-items-sm-end {\n -ms-flex-align: end !important;\n align-items: flex-end !important;\n }\n .align-items-sm-center {\n -ms-flex-align: center !important;\n align-items: center !important;\n }\n .align-items-sm-baseline {\n -ms-flex-align: baseline !important;\n align-items: baseline !important;\n }\n .align-items-sm-stretch {\n -ms-flex-align: stretch !important;\n align-items: stretch !important;\n }\n .align-content-sm-start {\n -ms-flex-line-pack: start !important;\n align-content: flex-start !important;\n }\n .align-content-sm-end {\n -ms-flex-line-pack: end !important;\n align-content: flex-end !important;\n }\n .align-content-sm-center {\n -ms-flex-line-pack: center !important;\n align-content: center !important;\n }\n .align-content-sm-between {\n -ms-flex-line-pack: justify !important;\n align-content: space-between !important;\n }\n .align-content-sm-around {\n -ms-flex-line-pack: distribute !important;\n align-content: space-around !important;\n }\n .align-content-sm-stretch {\n -ms-flex-line-pack: stretch !important;\n align-content: stretch !important;\n }\n .align-self-sm-auto {\n -ms-flex-item-align: auto !important;\n align-self: auto !important;\n }\n .align-self-sm-start {\n -ms-flex-item-align: start !important;\n align-self: flex-start !important;\n }\n .align-self-sm-end {\n -ms-flex-item-align: end !important;\n align-self: flex-end !important;\n }\n .align-self-sm-center {\n -ms-flex-item-align: center !important;\n align-self: center !important;\n }\n .align-self-sm-baseline {\n -ms-flex-item-align: baseline !important;\n align-self: baseline !important;\n }\n .align-self-sm-stretch {\n -ms-flex-item-align: stretch !important;\n align-self: stretch !important;\n }\n}\n\n@media (min-width: 768px) {\n .flex-md-row {\n -ms-flex-direction: row !important;\n flex-direction: row !important;\n }\n .flex-md-column {\n -ms-flex-direction: column !important;\n flex-direction: column !important;\n }\n .flex-md-row-reverse {\n -ms-flex-direction: row-reverse !important;\n flex-direction: row-reverse !important;\n }\n .flex-md-column-reverse {\n -ms-flex-direction: column-reverse !important;\n flex-direction: column-reverse !important;\n }\n .flex-md-wrap {\n -ms-flex-wrap: wrap !important;\n flex-wrap: wrap !important;\n }\n .flex-md-nowrap {\n -ms-flex-wrap: nowrap !important;\n flex-wrap: nowrap !important;\n }\n .flex-md-wrap-reverse {\n -ms-flex-wrap: wrap-reverse !important;\n flex-wrap: wrap-reverse !important;\n }\n .flex-md-fill {\n -ms-flex: 1 1 auto !important;\n flex: 1 1 auto !important;\n }\n .flex-md-grow-0 {\n -ms-flex-positive: 0 !important;\n flex-grow: 0 !important;\n }\n .flex-md-grow-1 {\n -ms-flex-positive: 1 !important;\n flex-grow: 1 !important;\n }\n .flex-md-shrink-0 {\n -ms-flex-negative: 0 !important;\n flex-shrink: 0 !important;\n }\n .flex-md-shrink-1 {\n -ms-flex-negative: 1 !important;\n flex-shrink: 1 !important;\n }\n .justify-content-md-start {\n -ms-flex-pack: start !important;\n justify-content: flex-start !important;\n }\n .justify-content-md-end {\n -ms-flex-pack: end !important;\n justify-content: flex-end !important;\n }\n .justify-content-md-center {\n -ms-flex-pack: center !important;\n justify-content: center !important;\n }\n .justify-content-md-between {\n -ms-flex-pack: justify !important;\n justify-content: space-between !important;\n }\n .justify-content-md-around {\n -ms-flex-pack: distribute !important;\n justify-content: space-around !important;\n }\n .align-items-md-start {\n -ms-flex-align: start !important;\n align-items: flex-start !important;\n }\n .align-items-md-end {\n -ms-flex-align: end !important;\n align-items: flex-end !important;\n }\n .align-items-md-center {\n -ms-flex-align: center !important;\n align-items: center !important;\n }\n .align-items-md-baseline {\n -ms-flex-align: baseline !important;\n align-items: baseline !important;\n }\n .align-items-md-stretch {\n -ms-flex-align: stretch !important;\n align-items: stretch !important;\n }\n .align-content-md-start {\n -ms-flex-line-pack: start !important;\n align-content: flex-start !important;\n }\n .align-content-md-end {\n -ms-flex-line-pack: end !important;\n align-content: flex-end !important;\n }\n .align-content-md-center {\n -ms-flex-line-pack: center !important;\n align-content: center !important;\n }\n .align-content-md-between {\n -ms-flex-line-pack: justify !important;\n align-content: space-between !important;\n }\n .align-content-md-around {\n -ms-flex-line-pack: distribute !important;\n align-content: space-around !important;\n }\n .align-content-md-stretch {\n -ms-flex-line-pack: stretch !important;\n align-content: stretch !important;\n }\n .align-self-md-auto {\n -ms-flex-item-align: auto !important;\n align-self: auto !important;\n }\n .align-self-md-start {\n -ms-flex-item-align: start !important;\n align-self: flex-start !important;\n }\n .align-self-md-end {\n -ms-flex-item-align: end !important;\n align-self: flex-end !important;\n }\n .align-self-md-center {\n -ms-flex-item-align: center !important;\n align-self: center !important;\n }\n .align-self-md-baseline {\n -ms-flex-item-align: baseline !important;\n align-self: baseline !important;\n }\n .align-self-md-stretch {\n -ms-flex-item-align: stretch !important;\n align-self: stretch !important;\n }\n}\n\n@media (min-width: 992px) {\n .flex-lg-row {\n -ms-flex-direction: row !important;\n flex-direction: row !important;\n }\n .flex-lg-column {\n -ms-flex-direction: column !important;\n flex-direction: column !important;\n }\n .flex-lg-row-reverse {\n -ms-flex-direction: row-reverse !important;\n flex-direction: row-reverse !important;\n }\n .flex-lg-column-reverse {\n -ms-flex-direction: column-reverse !important;\n flex-direction: column-reverse !important;\n }\n .flex-lg-wrap {\n -ms-flex-wrap: wrap !important;\n flex-wrap: wrap !important;\n }\n .flex-lg-nowrap {\n -ms-flex-wrap: nowrap !important;\n flex-wrap: nowrap !important;\n }\n .flex-lg-wrap-reverse {\n -ms-flex-wrap: wrap-reverse !important;\n flex-wrap: wrap-reverse !important;\n }\n .flex-lg-fill {\n -ms-flex: 1 1 auto !important;\n flex: 1 1 auto !important;\n }\n .flex-lg-grow-0 {\n -ms-flex-positive: 0 !important;\n flex-grow: 0 !important;\n }\n .flex-lg-grow-1 {\n -ms-flex-positive: 1 !important;\n flex-grow: 1 !important;\n }\n .flex-lg-shrink-0 {\n -ms-flex-negative: 0 !important;\n flex-shrink: 0 !important;\n }\n .flex-lg-shrink-1 {\n -ms-flex-negative: 1 !important;\n flex-shrink: 1 !important;\n }\n .justify-content-lg-start {\n -ms-flex-pack: start !important;\n justify-content: flex-start !important;\n }\n .justify-content-lg-end {\n -ms-flex-pack: end !important;\n justify-content: flex-end !important;\n }\n .justify-content-lg-center {\n -ms-flex-pack: center !important;\n justify-content: center !important;\n }\n .justify-content-lg-between {\n -ms-flex-pack: justify !important;\n justify-content: space-between !important;\n }\n .justify-content-lg-around {\n -ms-flex-pack: distribute !important;\n justify-content: space-around !important;\n }\n .align-items-lg-start {\n -ms-flex-align: start !important;\n align-items: flex-start !important;\n }\n .align-items-lg-end {\n -ms-flex-align: end !important;\n align-items: flex-end !important;\n }\n .align-items-lg-center {\n -ms-flex-align: center !important;\n align-items: center !important;\n }\n .align-items-lg-baseline {\n -ms-flex-align: baseline !important;\n align-items: baseline !important;\n }\n .align-items-lg-stretch {\n -ms-flex-align: stretch !important;\n align-items: stretch !important;\n }\n .align-content-lg-start {\n -ms-flex-line-pack: start !important;\n align-content: flex-start !important;\n }\n .align-content-lg-end {\n -ms-flex-line-pack: end !important;\n align-content: flex-end !important;\n }\n .align-content-lg-center {\n -ms-flex-line-pack: center !important;\n align-content: center !important;\n }\n .align-content-lg-between {\n -ms-flex-line-pack: justify !important;\n align-content: space-between !important;\n }\n .align-content-lg-around {\n -ms-flex-line-pack: distribute !important;\n align-content: space-around !important;\n }\n .align-content-lg-stretch {\n -ms-flex-line-pack: stretch !important;\n align-content: stretch !important;\n }\n .align-self-lg-auto {\n -ms-flex-item-align: auto !important;\n align-self: auto !important;\n }\n .align-self-lg-start {\n -ms-flex-item-align: start !important;\n align-self: flex-start !important;\n }\n .align-self-lg-end {\n -ms-flex-item-align: end !important;\n align-self: flex-end !important;\n }\n .align-self-lg-center {\n -ms-flex-item-align: center !important;\n align-self: center !important;\n }\n .align-self-lg-baseline {\n -ms-flex-item-align: baseline !important;\n align-self: baseline !important;\n }\n .align-self-lg-stretch {\n -ms-flex-item-align: stretch !important;\n align-self: stretch !important;\n }\n}\n\n@media (min-width: 1200px) {\n .flex-xl-row {\n -ms-flex-direction: row !important;\n flex-direction: row !important;\n }\n .flex-xl-column {\n -ms-flex-direction: column !important;\n flex-direction: column !important;\n }\n .flex-xl-row-reverse {\n -ms-flex-direction: row-reverse !important;\n flex-direction: row-reverse !important;\n }\n .flex-xl-column-reverse {\n -ms-flex-direction: column-reverse !important;\n flex-direction: column-reverse !important;\n }\n .flex-xl-wrap {\n -ms-flex-wrap: wrap !important;\n flex-wrap: wrap !important;\n }\n .flex-xl-nowrap {\n -ms-flex-wrap: nowrap !important;\n flex-wrap: nowrap !important;\n }\n .flex-xl-wrap-reverse {\n -ms-flex-wrap: wrap-reverse !important;\n flex-wrap: wrap-reverse !important;\n }\n .flex-xl-fill {\n -ms-flex: 1 1 auto !important;\n flex: 1 1 auto !important;\n }\n .flex-xl-grow-0 {\n -ms-flex-positive: 0 !important;\n flex-grow: 0 !important;\n }\n .flex-xl-grow-1 {\n -ms-flex-positive: 1 !important;\n flex-grow: 1 !important;\n }\n .flex-xl-shrink-0 {\n -ms-flex-negative: 0 !important;\n flex-shrink: 0 !important;\n }\n .flex-xl-shrink-1 {\n -ms-flex-negative: 1 !important;\n flex-shrink: 1 !important;\n }\n .justify-content-xl-start {\n -ms-flex-pack: start !important;\n justify-content: flex-start !important;\n }\n .justify-content-xl-end {\n -ms-flex-pack: end !important;\n justify-content: flex-end !important;\n }\n .justify-content-xl-center {\n -ms-flex-pack: center !important;\n justify-content: center !important;\n }\n .justify-content-xl-between {\n -ms-flex-pack: justify !important;\n justify-content: space-between !important;\n }\n .justify-content-xl-around {\n -ms-flex-pack: distribute !important;\n justify-content: space-around !important;\n }\n .align-items-xl-start {\n -ms-flex-align: start !important;\n align-items: flex-start !important;\n }\n .align-items-xl-end {\n -ms-flex-align: end !important;\n align-items: flex-end !important;\n }\n .align-items-xl-center {\n -ms-flex-align: center !important;\n align-items: center !important;\n }\n .align-items-xl-baseline {\n -ms-flex-align: baseline !important;\n align-items: baseline !important;\n }\n .align-items-xl-stretch {\n -ms-flex-align: stretch !important;\n align-items: stretch !important;\n }\n .align-content-xl-start {\n -ms-flex-line-pack: start !important;\n align-content: flex-start !important;\n }\n .align-content-xl-end {\n -ms-flex-line-pack: end !important;\n align-content: flex-end !important;\n }\n .align-content-xl-center {\n -ms-flex-line-pack: center !important;\n align-content: center !important;\n }\n .align-content-xl-between {\n -ms-flex-line-pack: justify !important;\n align-content: space-between !important;\n }\n .align-content-xl-around {\n -ms-flex-line-pack: distribute !important;\n align-content: space-around !important;\n }\n .align-content-xl-stretch {\n -ms-flex-line-pack: stretch !important;\n align-content: stretch !important;\n }\n .align-self-xl-auto {\n -ms-flex-item-align: auto !important;\n align-self: auto !important;\n }\n .align-self-xl-start {\n -ms-flex-item-align: start !important;\n align-self: flex-start !important;\n }\n .align-self-xl-end {\n -ms-flex-item-align: end !important;\n align-self: flex-end !important;\n }\n .align-self-xl-center {\n -ms-flex-item-align: center !important;\n align-self: center !important;\n }\n .align-self-xl-baseline {\n -ms-flex-item-align: baseline !important;\n align-self: baseline !important;\n }\n .align-self-xl-stretch {\n -ms-flex-item-align: stretch !important;\n align-self: stretch !important;\n }\n}\n\n.float-left {\n float: left !important;\n}\n\n.float-right {\n float: right !important;\n}\n\n.float-none {\n float: none !important;\n}\n\n@media (min-width: 576px) {\n .float-sm-left {\n float: left !important;\n }\n .float-sm-right {\n float: right !important;\n }\n .float-sm-none {\n float: none !important;\n }\n}\n\n@media (min-width: 768px) {\n .float-md-left {\n float: left !important;\n }\n .float-md-right {\n float: right !important;\n }\n .float-md-none {\n float: none !important;\n }\n}\n\n@media (min-width: 992px) {\n .float-lg-left {\n float: left !important;\n }\n .float-lg-right {\n float: right !important;\n }\n .float-lg-none {\n float: none !important;\n }\n}\n\n@media (min-width: 1200px) {\n .float-xl-left {\n float: left !important;\n }\n .float-xl-right {\n float: right !important;\n }\n .float-xl-none {\n float: none !important;\n }\n}\n\n.overflow-auto {\n overflow: auto !important;\n}\n\n.overflow-hidden {\n overflow: hidden !important;\n}\n\n.position-static {\n position: static !important;\n}\n\n.position-relative {\n position: relative !important;\n}\n\n.position-absolute {\n position: absolute !important;\n}\n\n.position-fixed {\n position: fixed !important;\n}\n\n.position-sticky {\n position: -webkit-sticky !important;\n position: sticky !important;\n}\n\n.fixed-top {\n position: fixed;\n top: 0;\n right: 0;\n left: 0;\n z-index: 1030;\n}\n\n.fixed-bottom {\n position: fixed;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1030;\n}\n\n@supports ((position: -webkit-sticky) or (position: sticky)) {\n .sticky-top {\n position: -webkit-sticky;\n position: sticky;\n top: 0;\n z-index: 1020;\n }\n}\n\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n\n.sr-only-focusable:active, .sr-only-focusable:focus {\n position: static;\n width: auto;\n height: auto;\n overflow: visible;\n clip: auto;\n white-space: normal;\n}\n\n.shadow-sm {\n box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important;\n}\n\n.shadow {\n box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;\n}\n\n.shadow-lg {\n box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important;\n}\n\n.shadow-none {\n box-shadow: none !important;\n}\n\n.w-25 {\n width: 25% !important;\n}\n\n.w-50 {\n width: 50% !important;\n}\n\n.w-75 {\n width: 75% !important;\n}\n\n.w-100 {\n width: 100% !important;\n}\n\n.w-auto {\n width: auto !important;\n}\n\n.h-25 {\n height: 25% !important;\n}\n\n.h-50 {\n height: 50% !important;\n}\n\n.h-75 {\n height: 75% !important;\n}\n\n.h-100 {\n height: 100% !important;\n}\n\n.h-auto {\n height: auto !important;\n}\n\n.mw-100 {\n max-width: 100% !important;\n}\n\n.mh-100 {\n max-height: 100% !important;\n}\n\n.min-vw-100 {\n min-width: 100vw !important;\n}\n\n.min-vh-100 {\n min-height: 100vh !important;\n}\n\n.vw-100 {\n width: 100vw !important;\n}\n\n.vh-100 {\n height: 100vh !important;\n}\n\n.stretched-link::after {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1;\n pointer-events: auto;\n content: \"\";\n background-color: rgba(0, 0, 0, 0);\n}\n\n.m-0 {\n margin: 0 !important;\n}\n\n.mt-0,\n.my-0 {\n margin-top: 0 !important;\n}\n\n.mr-0,\n.mx-0 {\n margin-right: 0 !important;\n}\n\n.mb-0,\n.my-0 {\n margin-bottom: 0 !important;\n}\n\n.ml-0,\n.mx-0 {\n margin-left: 0 !important;\n}\n\n.m-1 {\n margin: 0.25rem !important;\n}\n\n.mt-1,\n.my-1 {\n margin-top: 0.25rem !important;\n}\n\n.mr-1,\n.mx-1 {\n margin-right: 0.25rem !important;\n}\n\n.mb-1,\n.my-1 {\n margin-bottom: 0.25rem !important;\n}\n\n.ml-1,\n.mx-1 {\n margin-left: 0.25rem !important;\n}\n\n.m-2 {\n margin: 0.5rem !important;\n}\n\n.mt-2,\n.my-2 {\n margin-top: 0.5rem !important;\n}\n\n.mr-2,\n.mx-2 {\n margin-right: 0.5rem !important;\n}\n\n.mb-2,\n.my-2 {\n margin-bottom: 0.5rem !important;\n}\n\n.ml-2,\n.mx-2 {\n margin-left: 0.5rem !important;\n}\n\n.m-3 {\n margin: 1rem !important;\n}\n\n.mt-3,\n.my-3 {\n margin-top: 1rem !important;\n}\n\n.mr-3,\n.mx-3 {\n margin-right: 1rem !important;\n}\n\n.mb-3,\n.my-3 {\n margin-bottom: 1rem !important;\n}\n\n.ml-3,\n.mx-3 {\n margin-left: 1rem !important;\n}\n\n.m-4 {\n margin: 1.5rem !important;\n}\n\n.mt-4,\n.my-4 {\n margin-top: 1.5rem !important;\n}\n\n.mr-4,\n.mx-4 {\n margin-right: 1.5rem !important;\n}\n\n.mb-4,\n.my-4 {\n margin-bottom: 1.5rem !important;\n}\n\n.ml-4,\n.mx-4 {\n margin-left: 1.5rem !important;\n}\n\n.m-5 {\n margin: 3rem !important;\n}\n\n.mt-5,\n.my-5 {\n margin-top: 3rem !important;\n}\n\n.mr-5,\n.mx-5 {\n margin-right: 3rem !important;\n}\n\n.mb-5,\n.my-5 {\n margin-bottom: 3rem !important;\n}\n\n.ml-5,\n.mx-5 {\n margin-left: 3rem !important;\n}\n\n.p-0 {\n padding: 0 !important;\n}\n\n.pt-0,\n.py-0 {\n padding-top: 0 !important;\n}\n\n.pr-0,\n.px-0 {\n padding-right: 0 !important;\n}\n\n.pb-0,\n.py-0 {\n padding-bottom: 0 !important;\n}\n\n.pl-0,\n.px-0 {\n padding-left: 0 !important;\n}\n\n.p-1 {\n padding: 0.25rem !important;\n}\n\n.pt-1,\n.py-1 {\n padding-top: 0.25rem !important;\n}\n\n.pr-1,\n.px-1 {\n padding-right: 0.25rem !important;\n}\n\n.pb-1,\n.py-1 {\n padding-bottom: 0.25rem !important;\n}\n\n.pl-1,\n.px-1 {\n padding-left: 0.25rem !important;\n}\n\n.p-2 {\n padding: 0.5rem !important;\n}\n\n.pt-2,\n.py-2 {\n padding-top: 0.5rem !important;\n}\n\n.pr-2,\n.px-2 {\n padding-right: 0.5rem !important;\n}\n\n.pb-2,\n.py-2 {\n padding-bottom: 0.5rem !important;\n}\n\n.pl-2,\n.px-2 {\n padding-left: 0.5rem !important;\n}\n\n.p-3 {\n padding: 1rem !important;\n}\n\n.pt-3,\n.py-3 {\n padding-top: 1rem !important;\n}\n\n.pr-3,\n.px-3 {\n padding-right: 1rem !important;\n}\n\n.pb-3,\n.py-3 {\n padding-bottom: 1rem !important;\n}\n\n.pl-3,\n.px-3 {\n padding-left: 1rem !important;\n}\n\n.p-4 {\n padding: 1.5rem !important;\n}\n\n.pt-4,\n.py-4 {\n padding-top: 1.5rem !important;\n}\n\n.pr-4,\n.px-4 {\n padding-right: 1.5rem !important;\n}\n\n.pb-4,\n.py-4 {\n padding-bottom: 1.5rem !important;\n}\n\n.pl-4,\n.px-4 {\n padding-left: 1.5rem !important;\n}\n\n.p-5 {\n padding: 3rem !important;\n}\n\n.pt-5,\n.py-5 {\n padding-top: 3rem !important;\n}\n\n.pr-5,\n.px-5 {\n padding-right: 3rem !important;\n}\n\n.pb-5,\n.py-5 {\n padding-bottom: 3rem !important;\n}\n\n.pl-5,\n.px-5 {\n padding-left: 3rem !important;\n}\n\n.m-n1 {\n margin: -0.25rem !important;\n}\n\n.mt-n1,\n.my-n1 {\n margin-top: -0.25rem !important;\n}\n\n.mr-n1,\n.mx-n1 {\n margin-right: -0.25rem !important;\n}\n\n.mb-n1,\n.my-n1 {\n margin-bottom: -0.25rem !important;\n}\n\n.ml-n1,\n.mx-n1 {\n margin-left: -0.25rem !important;\n}\n\n.m-n2 {\n margin: -0.5rem !important;\n}\n\n.mt-n2,\n.my-n2 {\n margin-top: -0.5rem !important;\n}\n\n.mr-n2,\n.mx-n2 {\n margin-right: -0.5rem !important;\n}\n\n.mb-n2,\n.my-n2 {\n margin-bottom: -0.5rem !important;\n}\n\n.ml-n2,\n.mx-n2 {\n margin-left: -0.5rem !important;\n}\n\n.m-n3 {\n margin: -1rem !important;\n}\n\n.mt-n3,\n.my-n3 {\n margin-top: -1rem !important;\n}\n\n.mr-n3,\n.mx-n3 {\n margin-right: -1rem !important;\n}\n\n.mb-n3,\n.my-n3 {\n margin-bottom: -1rem !important;\n}\n\n.ml-n3,\n.mx-n3 {\n margin-left: -1rem !important;\n}\n\n.m-n4 {\n margin: -1.5rem !important;\n}\n\n.mt-n4,\n.my-n4 {\n margin-top: -1.5rem !important;\n}\n\n.mr-n4,\n.mx-n4 {\n margin-right: -1.5rem !important;\n}\n\n.mb-n4,\n.my-n4 {\n margin-bottom: -1.5rem !important;\n}\n\n.ml-n4,\n.mx-n4 {\n margin-left: -1.5rem !important;\n}\n\n.m-n5 {\n margin: -3rem !important;\n}\n\n.mt-n5,\n.my-n5 {\n margin-top: -3rem !important;\n}\n\n.mr-n5,\n.mx-n5 {\n margin-right: -3rem !important;\n}\n\n.mb-n5,\n.my-n5 {\n margin-bottom: -3rem !important;\n}\n\n.ml-n5,\n.mx-n5 {\n margin-left: -3rem !important;\n}\n\n.m-auto {\n margin: auto !important;\n}\n\n.mt-auto,\n.my-auto {\n margin-top: auto !important;\n}\n\n.mr-auto,\n.mx-auto {\n margin-right: auto !important;\n}\n\n.mb-auto,\n.my-auto {\n margin-bottom: auto !important;\n}\n\n.ml-auto,\n.mx-auto {\n margin-left: auto !important;\n}\n\n@media (min-width: 576px) {\n .m-sm-0 {\n margin: 0 !important;\n }\n .mt-sm-0,\n .my-sm-0 {\n margin-top: 0 !important;\n }\n .mr-sm-0,\n .mx-sm-0 {\n margin-right: 0 !important;\n }\n .mb-sm-0,\n .my-sm-0 {\n margin-bottom: 0 !important;\n }\n .ml-sm-0,\n .mx-sm-0 {\n margin-left: 0 !important;\n }\n .m-sm-1 {\n margin: 0.25rem !important;\n }\n .mt-sm-1,\n .my-sm-1 {\n margin-top: 0.25rem !important;\n }\n .mr-sm-1,\n .mx-sm-1 {\n margin-right: 0.25rem !important;\n }\n .mb-sm-1,\n .my-sm-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-sm-1,\n .mx-sm-1 {\n margin-left: 0.25rem !important;\n }\n .m-sm-2 {\n margin: 0.5rem !important;\n }\n .mt-sm-2,\n .my-sm-2 {\n margin-top: 0.5rem !important;\n }\n .mr-sm-2,\n .mx-sm-2 {\n margin-right: 0.5rem !important;\n }\n .mb-sm-2,\n .my-sm-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-sm-2,\n .mx-sm-2 {\n margin-left: 0.5rem !important;\n }\n .m-sm-3 {\n margin: 1rem !important;\n }\n .mt-sm-3,\n .my-sm-3 {\n margin-top: 1rem !important;\n }\n .mr-sm-3,\n .mx-sm-3 {\n margin-right: 1rem !important;\n }\n .mb-sm-3,\n .my-sm-3 {\n margin-bottom: 1rem !important;\n }\n .ml-sm-3,\n .mx-sm-3 {\n margin-left: 1rem !important;\n }\n .m-sm-4 {\n margin: 1.5rem !important;\n }\n .mt-sm-4,\n .my-sm-4 {\n margin-top: 1.5rem !important;\n }\n .mr-sm-4,\n .mx-sm-4 {\n margin-right: 1.5rem !important;\n }\n .mb-sm-4,\n .my-sm-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-sm-4,\n .mx-sm-4 {\n margin-left: 1.5rem !important;\n }\n .m-sm-5 {\n margin: 3rem !important;\n }\n .mt-sm-5,\n .my-sm-5 {\n margin-top: 3rem !important;\n }\n .mr-sm-5,\n .mx-sm-5 {\n margin-right: 3rem !important;\n }\n .mb-sm-5,\n .my-sm-5 {\n margin-bottom: 3rem !important;\n }\n .ml-sm-5,\n .mx-sm-5 {\n margin-left: 3rem !important;\n }\n .p-sm-0 {\n padding: 0 !important;\n }\n .pt-sm-0,\n .py-sm-0 {\n padding-top: 0 !important;\n }\n .pr-sm-0,\n .px-sm-0 {\n padding-right: 0 !important;\n }\n .pb-sm-0,\n .py-sm-0 {\n padding-bottom: 0 !important;\n }\n .pl-sm-0,\n .px-sm-0 {\n padding-left: 0 !important;\n }\n .p-sm-1 {\n padding: 0.25rem !important;\n }\n .pt-sm-1,\n .py-sm-1 {\n padding-top: 0.25rem !important;\n }\n .pr-sm-1,\n .px-sm-1 {\n padding-right: 0.25rem !important;\n }\n .pb-sm-1,\n .py-sm-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-sm-1,\n .px-sm-1 {\n padding-left: 0.25rem !important;\n }\n .p-sm-2 {\n padding: 0.5rem !important;\n }\n .pt-sm-2,\n .py-sm-2 {\n padding-top: 0.5rem !important;\n }\n .pr-sm-2,\n .px-sm-2 {\n padding-right: 0.5rem !important;\n }\n .pb-sm-2,\n .py-sm-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-sm-2,\n .px-sm-2 {\n padding-left: 0.5rem !important;\n }\n .p-sm-3 {\n padding: 1rem !important;\n }\n .pt-sm-3,\n .py-sm-3 {\n padding-top: 1rem !important;\n }\n .pr-sm-3,\n .px-sm-3 {\n padding-right: 1rem !important;\n }\n .pb-sm-3,\n .py-sm-3 {\n padding-bottom: 1rem !important;\n }\n .pl-sm-3,\n .px-sm-3 {\n padding-left: 1rem !important;\n }\n .p-sm-4 {\n padding: 1.5rem !important;\n }\n .pt-sm-4,\n .py-sm-4 {\n padding-top: 1.5rem !important;\n }\n .pr-sm-4,\n .px-sm-4 {\n padding-right: 1.5rem !important;\n }\n .pb-sm-4,\n .py-sm-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-sm-4,\n .px-sm-4 {\n padding-left: 1.5rem !important;\n }\n .p-sm-5 {\n padding: 3rem !important;\n }\n .pt-sm-5,\n .py-sm-5 {\n padding-top: 3rem !important;\n }\n .pr-sm-5,\n .px-sm-5 {\n padding-right: 3rem !important;\n }\n .pb-sm-5,\n .py-sm-5 {\n padding-bottom: 3rem !important;\n }\n .pl-sm-5,\n .px-sm-5 {\n padding-left: 3rem !important;\n }\n .m-sm-n1 {\n margin: -0.25rem !important;\n }\n .mt-sm-n1,\n .my-sm-n1 {\n margin-top: -0.25rem !important;\n }\n .mr-sm-n1,\n .mx-sm-n1 {\n margin-right: -0.25rem !important;\n }\n .mb-sm-n1,\n .my-sm-n1 {\n margin-bottom: -0.25rem !important;\n }\n .ml-sm-n1,\n .mx-sm-n1 {\n margin-left: -0.25rem !important;\n }\n .m-sm-n2 {\n margin: -0.5rem !important;\n }\n .mt-sm-n2,\n .my-sm-n2 {\n margin-top: -0.5rem !important;\n }\n .mr-sm-n2,\n .mx-sm-n2 {\n margin-right: -0.5rem !important;\n }\n .mb-sm-n2,\n .my-sm-n2 {\n margin-bottom: -0.5rem !important;\n }\n .ml-sm-n2,\n .mx-sm-n2 {\n margin-left: -0.5rem !important;\n }\n .m-sm-n3 {\n margin: -1rem !important;\n }\n .mt-sm-n3,\n .my-sm-n3 {\n margin-top: -1rem !important;\n }\n .mr-sm-n3,\n .mx-sm-n3 {\n margin-right: -1rem !important;\n }\n .mb-sm-n3,\n .my-sm-n3 {\n margin-bottom: -1rem !important;\n }\n .ml-sm-n3,\n .mx-sm-n3 {\n margin-left: -1rem !important;\n }\n .m-sm-n4 {\n margin: -1.5rem !important;\n }\n .mt-sm-n4,\n .my-sm-n4 {\n margin-top: -1.5rem !important;\n }\n .mr-sm-n4,\n .mx-sm-n4 {\n margin-right: -1.5rem !important;\n }\n .mb-sm-n4,\n .my-sm-n4 {\n margin-bottom: -1.5rem !important;\n }\n .ml-sm-n4,\n .mx-sm-n4 {\n margin-left: -1.5rem !important;\n }\n .m-sm-n5 {\n margin: -3rem !important;\n }\n .mt-sm-n5,\n .my-sm-n5 {\n margin-top: -3rem !important;\n }\n .mr-sm-n5,\n .mx-sm-n5 {\n margin-right: -3rem !important;\n }\n .mb-sm-n5,\n .my-sm-n5 {\n margin-bottom: -3rem !important;\n }\n .ml-sm-n5,\n .mx-sm-n5 {\n margin-left: -3rem !important;\n }\n .m-sm-auto {\n margin: auto !important;\n }\n .mt-sm-auto,\n .my-sm-auto {\n margin-top: auto !important;\n }\n .mr-sm-auto,\n .mx-sm-auto {\n margin-right: auto !important;\n }\n .mb-sm-auto,\n .my-sm-auto {\n margin-bottom: auto !important;\n }\n .ml-sm-auto,\n .mx-sm-auto {\n margin-left: auto !important;\n }\n}\n\n@media (min-width: 768px) {\n .m-md-0 {\n margin: 0 !important;\n }\n .mt-md-0,\n .my-md-0 {\n margin-top: 0 !important;\n }\n .mr-md-0,\n .mx-md-0 {\n margin-right: 0 !important;\n }\n .mb-md-0,\n .my-md-0 {\n margin-bottom: 0 !important;\n }\n .ml-md-0,\n .mx-md-0 {\n margin-left: 0 !important;\n }\n .m-md-1 {\n margin: 0.25rem !important;\n }\n .mt-md-1,\n .my-md-1 {\n margin-top: 0.25rem !important;\n }\n .mr-md-1,\n .mx-md-1 {\n margin-right: 0.25rem !important;\n }\n .mb-md-1,\n .my-md-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-md-1,\n .mx-md-1 {\n margin-left: 0.25rem !important;\n }\n .m-md-2 {\n margin: 0.5rem !important;\n }\n .mt-md-2,\n .my-md-2 {\n margin-top: 0.5rem !important;\n }\n .mr-md-2,\n .mx-md-2 {\n margin-right: 0.5rem !important;\n }\n .mb-md-2,\n .my-md-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-md-2,\n .mx-md-2 {\n margin-left: 0.5rem !important;\n }\n .m-md-3 {\n margin: 1rem !important;\n }\n .mt-md-3,\n .my-md-3 {\n margin-top: 1rem !important;\n }\n .mr-md-3,\n .mx-md-3 {\n margin-right: 1rem !important;\n }\n .mb-md-3,\n .my-md-3 {\n margin-bottom: 1rem !important;\n }\n .ml-md-3,\n .mx-md-3 {\n margin-left: 1rem !important;\n }\n .m-md-4 {\n margin: 1.5rem !important;\n }\n .mt-md-4,\n .my-md-4 {\n margin-top: 1.5rem !important;\n }\n .mr-md-4,\n .mx-md-4 {\n margin-right: 1.5rem !important;\n }\n .mb-md-4,\n .my-md-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-md-4,\n .mx-md-4 {\n margin-left: 1.5rem !important;\n }\n .m-md-5 {\n margin: 3rem !important;\n }\n .mt-md-5,\n .my-md-5 {\n margin-top: 3rem !important;\n }\n .mr-md-5,\n .mx-md-5 {\n margin-right: 3rem !important;\n }\n .mb-md-5,\n .my-md-5 {\n margin-bottom: 3rem !important;\n }\n .ml-md-5,\n .mx-md-5 {\n margin-left: 3rem !important;\n }\n .p-md-0 {\n padding: 0 !important;\n }\n .pt-md-0,\n .py-md-0 {\n padding-top: 0 !important;\n }\n .pr-md-0,\n .px-md-0 {\n padding-right: 0 !important;\n }\n .pb-md-0,\n .py-md-0 {\n padding-bottom: 0 !important;\n }\n .pl-md-0,\n .px-md-0 {\n padding-left: 0 !important;\n }\n .p-md-1 {\n padding: 0.25rem !important;\n }\n .pt-md-1,\n .py-md-1 {\n padding-top: 0.25rem !important;\n }\n .pr-md-1,\n .px-md-1 {\n padding-right: 0.25rem !important;\n }\n .pb-md-1,\n .py-md-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-md-1,\n .px-md-1 {\n padding-left: 0.25rem !important;\n }\n .p-md-2 {\n padding: 0.5rem !important;\n }\n .pt-md-2,\n .py-md-2 {\n padding-top: 0.5rem !important;\n }\n .pr-md-2,\n .px-md-2 {\n padding-right: 0.5rem !important;\n }\n .pb-md-2,\n .py-md-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-md-2,\n .px-md-2 {\n padding-left: 0.5rem !important;\n }\n .p-md-3 {\n padding: 1rem !important;\n }\n .pt-md-3,\n .py-md-3 {\n padding-top: 1rem !important;\n }\n .pr-md-3,\n .px-md-3 {\n padding-right: 1rem !important;\n }\n .pb-md-3,\n .py-md-3 {\n padding-bottom: 1rem !important;\n }\n .pl-md-3,\n .px-md-3 {\n padding-left: 1rem !important;\n }\n .p-md-4 {\n padding: 1.5rem !important;\n }\n .pt-md-4,\n .py-md-4 {\n padding-top: 1.5rem !important;\n }\n .pr-md-4,\n .px-md-4 {\n padding-right: 1.5rem !important;\n }\n .pb-md-4,\n .py-md-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-md-4,\n .px-md-4 {\n padding-left: 1.5rem !important;\n }\n .p-md-5 {\n padding: 3rem !important;\n }\n .pt-md-5,\n .py-md-5 {\n padding-top: 3rem !important;\n }\n .pr-md-5,\n .px-md-5 {\n padding-right: 3rem !important;\n }\n .pb-md-5,\n .py-md-5 {\n padding-bottom: 3rem !important;\n }\n .pl-md-5,\n .px-md-5 {\n padding-left: 3rem !important;\n }\n .m-md-n1 {\n margin: -0.25rem !important;\n }\n .mt-md-n1,\n .my-md-n1 {\n margin-top: -0.25rem !important;\n }\n .mr-md-n1,\n .mx-md-n1 {\n margin-right: -0.25rem !important;\n }\n .mb-md-n1,\n .my-md-n1 {\n margin-bottom: -0.25rem !important;\n }\n .ml-md-n1,\n .mx-md-n1 {\n margin-left: -0.25rem !important;\n }\n .m-md-n2 {\n margin: -0.5rem !important;\n }\n .mt-md-n2,\n .my-md-n2 {\n margin-top: -0.5rem !important;\n }\n .mr-md-n2,\n .mx-md-n2 {\n margin-right: -0.5rem !important;\n }\n .mb-md-n2,\n .my-md-n2 {\n margin-bottom: -0.5rem !important;\n }\n .ml-md-n2,\n .mx-md-n2 {\n margin-left: -0.5rem !important;\n }\n .m-md-n3 {\n margin: -1rem !important;\n }\n .mt-md-n3,\n .my-md-n3 {\n margin-top: -1rem !important;\n }\n .mr-md-n3,\n .mx-md-n3 {\n margin-right: -1rem !important;\n }\n .mb-md-n3,\n .my-md-n3 {\n margin-bottom: -1rem !important;\n }\n .ml-md-n3,\n .mx-md-n3 {\n margin-left: -1rem !important;\n }\n .m-md-n4 {\n margin: -1.5rem !important;\n }\n .mt-md-n4,\n .my-md-n4 {\n margin-top: -1.5rem !important;\n }\n .mr-md-n4,\n .mx-md-n4 {\n margin-right: -1.5rem !important;\n }\n .mb-md-n4,\n .my-md-n4 {\n margin-bottom: -1.5rem !important;\n }\n .ml-md-n4,\n .mx-md-n4 {\n margin-left: -1.5rem !important;\n }\n .m-md-n5 {\n margin: -3rem !important;\n }\n .mt-md-n5,\n .my-md-n5 {\n margin-top: -3rem !important;\n }\n .mr-md-n5,\n .mx-md-n5 {\n margin-right: -3rem !important;\n }\n .mb-md-n5,\n .my-md-n5 {\n margin-bottom: -3rem !important;\n }\n .ml-md-n5,\n .mx-md-n5 {\n margin-left: -3rem !important;\n }\n .m-md-auto {\n margin: auto !important;\n }\n .mt-md-auto,\n .my-md-auto {\n margin-top: auto !important;\n }\n .mr-md-auto,\n .mx-md-auto {\n margin-right: auto !important;\n }\n .mb-md-auto,\n .my-md-auto {\n margin-bottom: auto !important;\n }\n .ml-md-auto,\n .mx-md-auto {\n margin-left: auto !important;\n }\n}\n\n@media (min-width: 992px) {\n .m-lg-0 {\n margin: 0 !important;\n }\n .mt-lg-0,\n .my-lg-0 {\n margin-top: 0 !important;\n }\n .mr-lg-0,\n .mx-lg-0 {\n margin-right: 0 !important;\n }\n .mb-lg-0,\n .my-lg-0 {\n margin-bottom: 0 !important;\n }\n .ml-lg-0,\n .mx-lg-0 {\n margin-left: 0 !important;\n }\n .m-lg-1 {\n margin: 0.25rem !important;\n }\n .mt-lg-1,\n .my-lg-1 {\n margin-top: 0.25rem !important;\n }\n .mr-lg-1,\n .mx-lg-1 {\n margin-right: 0.25rem !important;\n }\n .mb-lg-1,\n .my-lg-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-lg-1,\n .mx-lg-1 {\n margin-left: 0.25rem !important;\n }\n .m-lg-2 {\n margin: 0.5rem !important;\n }\n .mt-lg-2,\n .my-lg-2 {\n margin-top: 0.5rem !important;\n }\n .mr-lg-2,\n .mx-lg-2 {\n margin-right: 0.5rem !important;\n }\n .mb-lg-2,\n .my-lg-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-lg-2,\n .mx-lg-2 {\n margin-left: 0.5rem !important;\n }\n .m-lg-3 {\n margin: 1rem !important;\n }\n .mt-lg-3,\n .my-lg-3 {\n margin-top: 1rem !important;\n }\n .mr-lg-3,\n .mx-lg-3 {\n margin-right: 1rem !important;\n }\n .mb-lg-3,\n .my-lg-3 {\n margin-bottom: 1rem !important;\n }\n .ml-lg-3,\n .mx-lg-3 {\n margin-left: 1rem !important;\n }\n .m-lg-4 {\n margin: 1.5rem !important;\n }\n .mt-lg-4,\n .my-lg-4 {\n margin-top: 1.5rem !important;\n }\n .mr-lg-4,\n .mx-lg-4 {\n margin-right: 1.5rem !important;\n }\n .mb-lg-4,\n .my-lg-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-lg-4,\n .mx-lg-4 {\n margin-left: 1.5rem !important;\n }\n .m-lg-5 {\n margin: 3rem !important;\n }\n .mt-lg-5,\n .my-lg-5 {\n margin-top: 3rem !important;\n }\n .mr-lg-5,\n .mx-lg-5 {\n margin-right: 3rem !important;\n }\n .mb-lg-5,\n .my-lg-5 {\n margin-bottom: 3rem !important;\n }\n .ml-lg-5,\n .mx-lg-5 {\n margin-left: 3rem !important;\n }\n .p-lg-0 {\n padding: 0 !important;\n }\n .pt-lg-0,\n .py-lg-0 {\n padding-top: 0 !important;\n }\n .pr-lg-0,\n .px-lg-0 {\n padding-right: 0 !important;\n }\n .pb-lg-0,\n .py-lg-0 {\n padding-bottom: 0 !important;\n }\n .pl-lg-0,\n .px-lg-0 {\n padding-left: 0 !important;\n }\n .p-lg-1 {\n padding: 0.25rem !important;\n }\n .pt-lg-1,\n .py-lg-1 {\n padding-top: 0.25rem !important;\n }\n .pr-lg-1,\n .px-lg-1 {\n padding-right: 0.25rem !important;\n }\n .pb-lg-1,\n .py-lg-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-lg-1,\n .px-lg-1 {\n padding-left: 0.25rem !important;\n }\n .p-lg-2 {\n padding: 0.5rem !important;\n }\n .pt-lg-2,\n .py-lg-2 {\n padding-top: 0.5rem !important;\n }\n .pr-lg-2,\n .px-lg-2 {\n padding-right: 0.5rem !important;\n }\n .pb-lg-2,\n .py-lg-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-lg-2,\n .px-lg-2 {\n padding-left: 0.5rem !important;\n }\n .p-lg-3 {\n padding: 1rem !important;\n }\n .pt-lg-3,\n .py-lg-3 {\n padding-top: 1rem !important;\n }\n .pr-lg-3,\n .px-lg-3 {\n padding-right: 1rem !important;\n }\n .pb-lg-3,\n .py-lg-3 {\n padding-bottom: 1rem !important;\n }\n .pl-lg-3,\n .px-lg-3 {\n padding-left: 1rem !important;\n }\n .p-lg-4 {\n padding: 1.5rem !important;\n }\n .pt-lg-4,\n .py-lg-4 {\n padding-top: 1.5rem !important;\n }\n .pr-lg-4,\n .px-lg-4 {\n padding-right: 1.5rem !important;\n }\n .pb-lg-4,\n .py-lg-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-lg-4,\n .px-lg-4 {\n padding-left: 1.5rem !important;\n }\n .p-lg-5 {\n padding: 3rem !important;\n }\n .pt-lg-5,\n .py-lg-5 {\n padding-top: 3rem !important;\n }\n .pr-lg-5,\n .px-lg-5 {\n padding-right: 3rem !important;\n }\n .pb-lg-5,\n .py-lg-5 {\n padding-bottom: 3rem !important;\n }\n .pl-lg-5,\n .px-lg-5 {\n padding-left: 3rem !important;\n }\n .m-lg-n1 {\n margin: -0.25rem !important;\n }\n .mt-lg-n1,\n .my-lg-n1 {\n margin-top: -0.25rem !important;\n }\n .mr-lg-n1,\n .mx-lg-n1 {\n margin-right: -0.25rem !important;\n }\n .mb-lg-n1,\n .my-lg-n1 {\n margin-bottom: -0.25rem !important;\n }\n .ml-lg-n1,\n .mx-lg-n1 {\n margin-left: -0.25rem !important;\n }\n .m-lg-n2 {\n margin: -0.5rem !important;\n }\n .mt-lg-n2,\n .my-lg-n2 {\n margin-top: -0.5rem !important;\n }\n .mr-lg-n2,\n .mx-lg-n2 {\n margin-right: -0.5rem !important;\n }\n .mb-lg-n2,\n .my-lg-n2 {\n margin-bottom: -0.5rem !important;\n }\n .ml-lg-n2,\n .mx-lg-n2 {\n margin-left: -0.5rem !important;\n }\n .m-lg-n3 {\n margin: -1rem !important;\n }\n .mt-lg-n3,\n .my-lg-n3 {\n margin-top: -1rem !important;\n }\n .mr-lg-n3,\n .mx-lg-n3 {\n margin-right: -1rem !important;\n }\n .mb-lg-n3,\n .my-lg-n3 {\n margin-bottom: -1rem !important;\n }\n .ml-lg-n3,\n .mx-lg-n3 {\n margin-left: -1rem !important;\n }\n .m-lg-n4 {\n margin: -1.5rem !important;\n }\n .mt-lg-n4,\n .my-lg-n4 {\n margin-top: -1.5rem !important;\n }\n .mr-lg-n4,\n .mx-lg-n4 {\n margin-right: -1.5rem !important;\n }\n .mb-lg-n4,\n .my-lg-n4 {\n margin-bottom: -1.5rem !important;\n }\n .ml-lg-n4,\n .mx-lg-n4 {\n margin-left: -1.5rem !important;\n }\n .m-lg-n5 {\n margin: -3rem !important;\n }\n .mt-lg-n5,\n .my-lg-n5 {\n margin-top: -3rem !important;\n }\n .mr-lg-n5,\n .mx-lg-n5 {\n margin-right: -3rem !important;\n }\n .mb-lg-n5,\n .my-lg-n5 {\n margin-bottom: -3rem !important;\n }\n .ml-lg-n5,\n .mx-lg-n5 {\n margin-left: -3rem !important;\n }\n .m-lg-auto {\n margin: auto !important;\n }\n .mt-lg-auto,\n .my-lg-auto {\n margin-top: auto !important;\n }\n .mr-lg-auto,\n .mx-lg-auto {\n margin-right: auto !important;\n }\n .mb-lg-auto,\n .my-lg-auto {\n margin-bottom: auto !important;\n }\n .ml-lg-auto,\n .mx-lg-auto {\n margin-left: auto !important;\n }\n}\n\n@media (min-width: 1200px) {\n .m-xl-0 {\n margin: 0 !important;\n }\n .mt-xl-0,\n .my-xl-0 {\n margin-top: 0 !important;\n }\n .mr-xl-0,\n .mx-xl-0 {\n margin-right: 0 !important;\n }\n .mb-xl-0,\n .my-xl-0 {\n margin-bottom: 0 !important;\n }\n .ml-xl-0,\n .mx-xl-0 {\n margin-left: 0 !important;\n }\n .m-xl-1 {\n margin: 0.25rem !important;\n }\n .mt-xl-1,\n .my-xl-1 {\n margin-top: 0.25rem !important;\n }\n .mr-xl-1,\n .mx-xl-1 {\n margin-right: 0.25rem !important;\n }\n .mb-xl-1,\n .my-xl-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-xl-1,\n .mx-xl-1 {\n margin-left: 0.25rem !important;\n }\n .m-xl-2 {\n margin: 0.5rem !important;\n }\n .mt-xl-2,\n .my-xl-2 {\n margin-top: 0.5rem !important;\n }\n .mr-xl-2,\n .mx-xl-2 {\n margin-right: 0.5rem !important;\n }\n .mb-xl-2,\n .my-xl-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-xl-2,\n .mx-xl-2 {\n margin-left: 0.5rem !important;\n }\n .m-xl-3 {\n margin: 1rem !important;\n }\n .mt-xl-3,\n .my-xl-3 {\n margin-top: 1rem !important;\n }\n .mr-xl-3,\n .mx-xl-3 {\n margin-right: 1rem !important;\n }\n .mb-xl-3,\n .my-xl-3 {\n margin-bottom: 1rem !important;\n }\n .ml-xl-3,\n .mx-xl-3 {\n margin-left: 1rem !important;\n }\n .m-xl-4 {\n margin: 1.5rem !important;\n }\n .mt-xl-4,\n .my-xl-4 {\n margin-top: 1.5rem !important;\n }\n .mr-xl-4,\n .mx-xl-4 {\n margin-right: 1.5rem !important;\n }\n .mb-xl-4,\n .my-xl-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-xl-4,\n .mx-xl-4 {\n margin-left: 1.5rem !important;\n }\n .m-xl-5 {\n margin: 3rem !important;\n }\n .mt-xl-5,\n .my-xl-5 {\n margin-top: 3rem !important;\n }\n .mr-xl-5,\n .mx-xl-5 {\n margin-right: 3rem !important;\n }\n .mb-xl-5,\n .my-xl-5 {\n margin-bottom: 3rem !important;\n }\n .ml-xl-5,\n .mx-xl-5 {\n margin-left: 3rem !important;\n }\n .p-xl-0 {\n padding: 0 !important;\n }\n .pt-xl-0,\n .py-xl-0 {\n padding-top: 0 !important;\n }\n .pr-xl-0,\n .px-xl-0 {\n padding-right: 0 !important;\n }\n .pb-xl-0,\n .py-xl-0 {\n padding-bottom: 0 !important;\n }\n .pl-xl-0,\n .px-xl-0 {\n padding-left: 0 !important;\n }\n .p-xl-1 {\n padding: 0.25rem !important;\n }\n .pt-xl-1,\n .py-xl-1 {\n padding-top: 0.25rem !important;\n }\n .pr-xl-1,\n .px-xl-1 {\n padding-right: 0.25rem !important;\n }\n .pb-xl-1,\n .py-xl-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-xl-1,\n .px-xl-1 {\n padding-left: 0.25rem !important;\n }\n .p-xl-2 {\n padding: 0.5rem !important;\n }\n .pt-xl-2,\n .py-xl-2 {\n padding-top: 0.5rem !important;\n }\n .pr-xl-2,\n .px-xl-2 {\n padding-right: 0.5rem !important;\n }\n .pb-xl-2,\n .py-xl-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-xl-2,\n .px-xl-2 {\n padding-left: 0.5rem !important;\n }\n .p-xl-3 {\n padding: 1rem !important;\n }\n .pt-xl-3,\n .py-xl-3 {\n padding-top: 1rem !important;\n }\n .pr-xl-3,\n .px-xl-3 {\n padding-right: 1rem !important;\n }\n .pb-xl-3,\n .py-xl-3 {\n padding-bottom: 1rem !important;\n }\n .pl-xl-3,\n .px-xl-3 {\n padding-left: 1rem !important;\n }\n .p-xl-4 {\n padding: 1.5rem !important;\n }\n .pt-xl-4,\n .py-xl-4 {\n padding-top: 1.5rem !important;\n }\n .pr-xl-4,\n .px-xl-4 {\n padding-right: 1.5rem !important;\n }\n .pb-xl-4,\n .py-xl-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-xl-4,\n .px-xl-4 {\n padding-left: 1.5rem !important;\n }\n .p-xl-5 {\n padding: 3rem !important;\n }\n .pt-xl-5,\n .py-xl-5 {\n padding-top: 3rem !important;\n }\n .pr-xl-5,\n .px-xl-5 {\n padding-right: 3rem !important;\n }\n .pb-xl-5,\n .py-xl-5 {\n padding-bottom: 3rem !important;\n }\n .pl-xl-5,\n .px-xl-5 {\n padding-left: 3rem !important;\n }\n .m-xl-n1 {\n margin: -0.25rem !important;\n }\n .mt-xl-n1,\n .my-xl-n1 {\n margin-top: -0.25rem !important;\n }\n .mr-xl-n1,\n .mx-xl-n1 {\n margin-right: -0.25rem !important;\n }\n .mb-xl-n1,\n .my-xl-n1 {\n margin-bottom: -0.25rem !important;\n }\n .ml-xl-n1,\n .mx-xl-n1 {\n margin-left: -0.25rem !important;\n }\n .m-xl-n2 {\n margin: -0.5rem !important;\n }\n .mt-xl-n2,\n .my-xl-n2 {\n margin-top: -0.5rem !important;\n }\n .mr-xl-n2,\n .mx-xl-n2 {\n margin-right: -0.5rem !important;\n }\n .mb-xl-n2,\n .my-xl-n2 {\n margin-bottom: -0.5rem !important;\n }\n .ml-xl-n2,\n .mx-xl-n2 {\n margin-left: -0.5rem !important;\n }\n .m-xl-n3 {\n margin: -1rem !important;\n }\n .mt-xl-n3,\n .my-xl-n3 {\n margin-top: -1rem !important;\n }\n .mr-xl-n3,\n .mx-xl-n3 {\n margin-right: -1rem !important;\n }\n .mb-xl-n3,\n .my-xl-n3 {\n margin-bottom: -1rem !important;\n }\n .ml-xl-n3,\n .mx-xl-n3 {\n margin-left: -1rem !important;\n }\n .m-xl-n4 {\n margin: -1.5rem !important;\n }\n .mt-xl-n4,\n .my-xl-n4 {\n margin-top: -1.5rem !important;\n }\n .mr-xl-n4,\n .mx-xl-n4 {\n margin-right: -1.5rem !important;\n }\n .mb-xl-n4,\n .my-xl-n4 {\n margin-bottom: -1.5rem !important;\n }\n .ml-xl-n4,\n .mx-xl-n4 {\n margin-left: -1.5rem !important;\n }\n .m-xl-n5 {\n margin: -3rem !important;\n }\n .mt-xl-n5,\n .my-xl-n5 {\n margin-top: -3rem !important;\n }\n .mr-xl-n5,\n .mx-xl-n5 {\n margin-right: -3rem !important;\n }\n .mb-xl-n5,\n .my-xl-n5 {\n margin-bottom: -3rem !important;\n }\n .ml-xl-n5,\n .mx-xl-n5 {\n margin-left: -3rem !important;\n }\n .m-xl-auto {\n margin: auto !important;\n }\n .mt-xl-auto,\n .my-xl-auto {\n margin-top: auto !important;\n }\n .mr-xl-auto,\n .mx-xl-auto {\n margin-right: auto !important;\n }\n .mb-xl-auto,\n .my-xl-auto {\n margin-bottom: auto !important;\n }\n .ml-xl-auto,\n .mx-xl-auto {\n margin-left: auto !important;\n }\n}\n\n.text-monospace {\n font-family: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace !important;\n}\n\n.text-justify {\n text-align: justify !important;\n}\n\n.text-wrap {\n white-space: normal !important;\n}\n\n.text-nowrap {\n white-space: nowrap !important;\n}\n\n.text-truncate {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.text-left {\n text-align: left !important;\n}\n\n.text-right {\n text-align: right !important;\n}\n\n.text-center {\n text-align: center !important;\n}\n\n@media (min-width: 576px) {\n .text-sm-left {\n text-align: left !important;\n }\n .text-sm-right {\n text-align: right !important;\n }\n .text-sm-center {\n text-align: center !important;\n }\n}\n\n@media (min-width: 768px) {\n .text-md-left {\n text-align: left !important;\n }\n .text-md-right {\n text-align: right !important;\n }\n .text-md-center {\n text-align: center !important;\n }\n}\n\n@media (min-width: 992px) {\n .text-lg-left {\n text-align: left !important;\n }\n .text-lg-right {\n text-align: right !important;\n }\n .text-lg-center {\n text-align: center !important;\n }\n}\n\n@media (min-width: 1200px) {\n .text-xl-left {\n text-align: left !important;\n }\n .text-xl-right {\n text-align: right !important;\n }\n .text-xl-center {\n text-align: center !important;\n }\n}\n\n.text-lowercase {\n text-transform: lowercase !important;\n}\n\n.text-uppercase {\n text-transform: uppercase !important;\n}\n\n.text-capitalize {\n text-transform: capitalize !important;\n}\n\n.font-weight-light {\n font-weight: 300 !important;\n}\n\n.font-weight-lighter {\n font-weight: lighter !important;\n}\n\n.font-weight-normal {\n font-weight: 400 !important;\n}\n\n.font-weight-bold {\n font-weight: 700 !important;\n}\n\n.font-weight-bolder {\n font-weight: bolder !important;\n}\n\n.font-italic {\n font-style: italic !important;\n}\n\n.text-white {\n color: #fff !important;\n}\n\n.text-primary {\n color: #007bff !important;\n}\n\na.text-primary:hover, a.text-primary:focus {\n color: #0056b3 !important;\n}\n\n.text-secondary {\n color: #6c757d !important;\n}\n\na.text-secondary:hover, a.text-secondary:focus {\n color: #494f54 !important;\n}\n\n.text-success {\n color: #28a745 !important;\n}\n\na.text-success:hover, a.text-success:focus {\n color: #19692c !important;\n}\n\n.text-info {\n color: #17a2b8 !important;\n}\n\na.text-info:hover, a.text-info:focus {\n color: #0f6674 !important;\n}\n\n.text-warning {\n color: #ffc107 !important;\n}\n\na.text-warning:hover, a.text-warning:focus {\n color: #ba8b00 !important;\n}\n\n.text-danger {\n color: #dc3545 !important;\n}\n\na.text-danger:hover, a.text-danger:focus {\n color: #a71d2a !important;\n}\n\n.text-light {\n color: #f8f9fa !important;\n}\n\na.text-light:hover, a.text-light:focus {\n color: #cbd3da !important;\n}\n\n.text-dark {\n color: #343a40 !important;\n}\n\na.text-dark:hover, a.text-dark:focus {\n color: #121416 !important;\n}\n\n.text-body {\n color: #212529 !important;\n}\n\n.text-muted {\n color: #6c757d !important;\n}\n\n.text-black-50 {\n color: rgba(0, 0, 0, 0.5) !important;\n}\n\n.text-white-50 {\n color: rgba(255, 255, 255, 0.5) !important;\n}\n\n.text-hide {\n font: 0/0 a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n\n.text-decoration-none {\n text-decoration: none !important;\n}\n\n.text-break {\n word-break: break-word !important;\n overflow-wrap: break-word !important;\n}\n\n.text-reset {\n color: inherit !important;\n}\n\n.visible {\n visibility: visible !important;\n}\n\n.invisible {\n visibility: hidden !important;\n}\n\n@media print {\n *,\n *::before,\n *::after {\n text-shadow: none !important;\n box-shadow: none !important;\n }\n a:not(.btn) {\n text-decoration: underline;\n }\n abbr[title]::after {\n content: \" (\" attr(title) \")\";\n }\n pre {\n white-space: pre-wrap !important;\n }\n pre,\n blockquote {\n border: 1px solid #adb5bd;\n page-break-inside: avoid;\n }\n thead {\n display: table-header-group;\n }\n tr,\n img {\n page-break-inside: avoid;\n }\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n h2,\n h3 {\n page-break-after: avoid;\n }\n @page {\n size: a3;\n }\n body {\n min-width: 992px !important;\n }\n .container {\n min-width: 992px !important;\n }\n .navbar {\n display: none;\n }\n .badge {\n border: 1px solid #000;\n }\n .table {\n border-collapse: collapse !important;\n }\n .table td,\n .table th {\n background-color: #fff !important;\n }\n .table-bordered th,\n .table-bordered td {\n border: 1px solid #dee2e6 !important;\n }\n .table-dark {\n color: inherit;\n }\n .table-dark th,\n .table-dark td,\n .table-dark thead th,\n .table-dark tbody + tbody {\n border-color: #dee2e6;\n }\n .table .thead-dark th {\n color: inherit;\n border-color: #dee2e6;\n }\n}\n/*# sourceMappingURL=bootstrap.css.map */","// stylelint-disable property-blacklist, scss/dollar-variable-default\n\n// SCSS RFS mixin\n//\n// Automated font-resizing\n//\n// See https://github.com/twbs/rfs\n\n// Configuration\n\n// Base font size\n$rfs-base-font-size: 1.25rem !default;\n$rfs-font-size-unit: rem !default;\n\n// Breakpoint at where font-size starts decreasing if screen width is smaller\n$rfs-breakpoint: 1200px !default;\n$rfs-breakpoint-unit: px !default;\n\n// Resize font-size based on screen height and width\n$rfs-two-dimensional: false !default;\n\n// Factor of decrease\n$rfs-factor: 10 !default;\n\n@if type-of($rfs-factor) != \"number\" or $rfs-factor <= 1 {\n @error \"`#{$rfs-factor}` is not a valid $rfs-factor, it must be greater than 1.\";\n}\n\n// Generate enable or disable classes. Possibilities: false, \"enable\" or \"disable\"\n$rfs-class: false !default;\n\n// 1 rem = $rfs-rem-value px\n$rfs-rem-value: 16 !default;\n\n// Safari iframe resize bug: https://github.com/twbs/rfs/issues/14\n$rfs-safari-iframe-resize-bug-fix: false !default;\n\n// Disable RFS by setting $enable-responsive-font-sizes to false\n$enable-responsive-font-sizes: true !default;\n\n// Cache $rfs-base-font-size unit\n$rfs-base-font-size-unit: unit($rfs-base-font-size);\n\n// Remove px-unit from $rfs-base-font-size for calculations\n@if $rfs-base-font-size-unit == \"px\" {\n $rfs-base-font-size: $rfs-base-font-size / ($rfs-base-font-size * 0 + 1);\n}\n@else if $rfs-base-font-size-unit == \"rem\" {\n $rfs-base-font-size: $rfs-base-font-size / ($rfs-base-font-size * 0 + 1 / $rfs-rem-value);\n}\n\n// Cache $rfs-breakpoint unit to prevent multiple calls\n$rfs-breakpoint-unit-cache: unit($rfs-breakpoint);\n\n// Remove unit from $rfs-breakpoint for calculations\n@if $rfs-breakpoint-unit-cache == \"px\" {\n $rfs-breakpoint: $rfs-breakpoint / ($rfs-breakpoint * 0 + 1);\n}\n@else if $rfs-breakpoint-unit-cache == \"rem\" or $rfs-breakpoint-unit-cache == \"em\" {\n $rfs-breakpoint: $rfs-breakpoint / ($rfs-breakpoint * 0 + 1 / $rfs-rem-value);\n}\n\n// Responsive font-size mixin\n@mixin rfs($fs, $important: false) {\n // Cache $fs unit\n $fs-unit: if(type-of($fs) == \"number\", unit($fs), false);\n\n // Add !important suffix if needed\n $rfs-suffix: if($important, \" !important\", \"\");\n\n // If $fs isn't a number (like inherit) or $fs has a unit (not px or rem, like 1.5em) or $ is 0, just print the value\n @if not $fs-unit or $fs-unit != \"\" and $fs-unit != \"px\" and $fs-unit != \"rem\" or $fs == 0 {\n font-size: #{$fs}#{$rfs-suffix};\n }\n @else {\n // Variables for storing static and fluid rescaling\n $rfs-static: null;\n $rfs-fluid: null;\n\n // Remove px-unit from $fs for calculations\n @if $fs-unit == \"px\" {\n $fs: $fs / ($fs * 0 + 1);\n }\n @else if $fs-unit == \"rem\" {\n $fs: $fs / ($fs * 0 + 1 / $rfs-rem-value);\n }\n\n // Set default font-size\n @if $rfs-font-size-unit == rem {\n $rfs-static: #{$fs / $rfs-rem-value}rem#{$rfs-suffix};\n }\n @else if $rfs-font-size-unit == px {\n $rfs-static: #{$fs}px#{$rfs-suffix};\n }\n @else {\n @error \"`#{$rfs-font-size-unit}` is not a valid unit for $rfs-font-size-unit. Use `px` or `rem`.\";\n }\n\n // Only add media query if font-size is bigger as the minimum font-size\n // If $rfs-factor == 1, no rescaling will take place\n @if $fs > $rfs-base-font-size and $enable-responsive-font-sizes {\n $min-width: null;\n $variable-unit: null;\n\n // Calculate minimum font-size for given font-size\n $fs-min: $rfs-base-font-size + ($fs - $rfs-base-font-size) / $rfs-factor;\n\n // Calculate difference between given font-size and minimum font-size for given font-size\n $fs-diff: $fs - $fs-min;\n\n // Base font-size formatting\n // No need to check if the unit is valid, because we did that before\n $min-width: if($rfs-font-size-unit == rem, #{$fs-min / $rfs-rem-value}rem, #{$fs-min}px);\n\n // If two-dimensional, use smallest of screen width and height\n $variable-unit: if($rfs-two-dimensional, vmin, vw);\n\n // Calculate the variable width between 0 and $rfs-breakpoint\n $variable-width: #{$fs-diff * 100 / $rfs-breakpoint}#{$variable-unit};\n\n // Set the calculated font-size.\n $rfs-fluid: calc(#{$min-width} + #{$variable-width}) #{$rfs-suffix};\n }\n\n // Rendering\n @if $rfs-fluid == null {\n // Only render static font-size if no fluid font-size is available\n font-size: $rfs-static;\n }\n @else {\n $mq-value: null;\n\n // RFS breakpoint formatting\n @if $rfs-breakpoint-unit == em or $rfs-breakpoint-unit == rem {\n $mq-value: #{$rfs-breakpoint / $rfs-rem-value}#{$rfs-breakpoint-unit};\n }\n @else if $rfs-breakpoint-unit == px {\n $mq-value: #{$rfs-breakpoint}px;\n }\n @else {\n @error \"`#{$rfs-breakpoint-unit}` is not a valid unit for $rfs-breakpoint-unit. Use `px`, `em` or `rem`.\";\n }\n\n @if $rfs-class == \"disable\" {\n // Adding an extra class increases specificity,\n // which prevents the media query to override the font size\n &,\n .disable-responsive-font-size &,\n &.disable-responsive-font-size {\n font-size: $rfs-static;\n }\n }\n @else {\n font-size: $rfs-static;\n }\n\n @if $rfs-two-dimensional {\n @media (max-width: #{$mq-value}), (max-height: #{$mq-value}) {\n @if $rfs-class == \"enable\" {\n .enable-responsive-font-size &,\n &.enable-responsive-font-size {\n font-size: $rfs-fluid;\n }\n }\n @else {\n font-size: $rfs-fluid;\n }\n\n @if $rfs-safari-iframe-resize-bug-fix {\n // stylelint-disable-next-line length-zero-no-unit\n min-width: 0vw;\n }\n }\n }\n @else {\n @media (max-width: #{$mq-value}) {\n @if $rfs-class == \"enable\" {\n .enable-responsive-font-size &,\n &.enable-responsive-font-size {\n font-size: $rfs-fluid;\n }\n }\n @else {\n font-size: $rfs-fluid;\n }\n\n @if $rfs-safari-iframe-resize-bug-fix {\n // stylelint-disable-next-line length-zero-no-unit\n min-width: 0vw;\n }\n }\n }\n }\n }\n}\n\n// The font-size & responsive-font-size mixin uses RFS to rescale font sizes\n@mixin font-size($fs, $important: false) {\n @include rfs($fs, $important);\n}\n\n@mixin responsive-font-size($fs, $important: false) {\n @include rfs($fs, $important);\n}\n","/*!\n * Bootstrap v4.3.1 (https://getbootstrap.com/)\n * Copyright 2011-2019 The Bootstrap Authors\n * Copyright 2011-2019 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n */\n:root {\n --blue: #007bff;\n --indigo: #6610f2;\n --purple: #6f42c1;\n --pink: #e83e8c;\n --red: #dc3545;\n --orange: #fd7e14;\n --yellow: #ffc107;\n --green: #28a745;\n --teal: #20c997;\n --cyan: #17a2b8;\n --white: #fff;\n --gray: #6c757d;\n --gray-dark: #343a40;\n --primary: #007bff;\n --secondary: #6c757d;\n --success: #28a745;\n --info: #17a2b8;\n --warning: #ffc107;\n --danger: #dc3545;\n --light: #f8f9fa;\n --dark: #343a40;\n --breakpoint-xs: 0;\n --breakpoint-sm: 576px;\n --breakpoint-md: 768px;\n --breakpoint-lg: 992px;\n --breakpoint-xl: 1200px;\n --font-family-sans-serif: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n --font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n}\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\nhtml {\n font-family: sans-serif;\n line-height: 1.15;\n -webkit-text-size-adjust: 100%;\n -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\n\narticle, aside, figcaption, figure, footer, header, hgroup, main, nav, section {\n display: block;\n}\n\nbody {\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: #212529;\n text-align: left;\n background-color: #fff;\n}\n\n[tabindex=\"-1\"]:focus {\n outline: 0 !important;\n}\n\nhr {\n box-sizing: content-box;\n height: 0;\n overflow: visible;\n}\n\nh1, h2, h3, h4, h5, h6 {\n margin-top: 0;\n margin-bottom: 0.5rem;\n}\n\np {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nabbr[title],\nabbr[data-original-title] {\n text-decoration: underline;\n text-decoration: underline dotted;\n cursor: help;\n border-bottom: 0;\n text-decoration-skip-ink: none;\n}\n\naddress {\n margin-bottom: 1rem;\n font-style: normal;\n line-height: inherit;\n}\n\nol,\nul,\ndl {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n margin-bottom: 0;\n}\n\ndt {\n font-weight: 700;\n}\n\ndd {\n margin-bottom: .5rem;\n margin-left: 0;\n}\n\nblockquote {\n margin: 0 0 1rem;\n}\n\nb,\nstrong {\n font-weight: bolder;\n}\n\nsmall {\n font-size: 80%;\n}\n\nsub,\nsup {\n position: relative;\n font-size: 75%;\n line-height: 0;\n vertical-align: baseline;\n}\n\nsub {\n bottom: -.25em;\n}\n\nsup {\n top: -.5em;\n}\n\na {\n color: #007bff;\n text-decoration: none;\n background-color: transparent;\n}\n\na:hover {\n color: #0056b3;\n text-decoration: underline;\n}\n\na:not([href]):not([tabindex]) {\n color: inherit;\n text-decoration: none;\n}\n\na:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus {\n color: inherit;\n text-decoration: none;\n}\n\na:not([href]):not([tabindex]):focus {\n outline: 0;\n}\n\npre,\ncode,\nkbd,\nsamp {\n font-family: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace;\n font-size: 1em;\n}\n\npre {\n margin-top: 0;\n margin-bottom: 1rem;\n overflow: auto;\n}\n\nfigure {\n margin: 0 0 1rem;\n}\n\nimg {\n vertical-align: middle;\n border-style: none;\n}\n\nsvg {\n overflow: hidden;\n vertical-align: middle;\n}\n\ntable {\n border-collapse: collapse;\n}\n\ncaption {\n padding-top: 0.75rem;\n padding-bottom: 0.75rem;\n color: #6c757d;\n text-align: left;\n caption-side: bottom;\n}\n\nth {\n text-align: inherit;\n}\n\nlabel {\n display: inline-block;\n margin-bottom: 0.5rem;\n}\n\nbutton {\n border-radius: 0;\n}\n\nbutton:focus {\n outline: 1px dotted;\n outline: 5px auto -webkit-focus-ring-color;\n}\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n margin: 0;\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\n\nbutton,\ninput {\n overflow: visible;\n}\n\nbutton,\nselect {\n text-transform: none;\n}\n\nselect {\n word-wrap: normal;\n}\n\nbutton,\n[type=\"button\"],\n[type=\"reset\"],\n[type=\"submit\"] {\n -webkit-appearance: button;\n}\n\nbutton:not(:disabled),\n[type=\"button\"]:not(:disabled),\n[type=\"reset\"]:not(:disabled),\n[type=\"submit\"]:not(:disabled) {\n cursor: pointer;\n}\n\nbutton::-moz-focus-inner,\n[type=\"button\"]::-moz-focus-inner,\n[type=\"reset\"]::-moz-focus-inner,\n[type=\"submit\"]::-moz-focus-inner {\n padding: 0;\n border-style: none;\n}\n\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n box-sizing: border-box;\n padding: 0;\n}\n\ninput[type=\"date\"],\ninput[type=\"time\"],\ninput[type=\"datetime-local\"],\ninput[type=\"month\"] {\n -webkit-appearance: listbox;\n}\n\ntextarea {\n overflow: auto;\n resize: vertical;\n}\n\nfieldset {\n min-width: 0;\n padding: 0;\n margin: 0;\n border: 0;\n}\n\nlegend {\n display: block;\n width: 100%;\n max-width: 100%;\n padding: 0;\n margin-bottom: .5rem;\n font-size: 1.5rem;\n line-height: inherit;\n color: inherit;\n white-space: normal;\n}\n\nprogress {\n vertical-align: baseline;\n}\n\n[type=\"number\"]::-webkit-inner-spin-button,\n[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n[type=\"search\"] {\n outline-offset: -2px;\n -webkit-appearance: none;\n}\n\n[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n::-webkit-file-upload-button {\n font: inherit;\n -webkit-appearance: button;\n}\n\noutput {\n display: inline-block;\n}\n\nsummary {\n display: list-item;\n cursor: pointer;\n}\n\ntemplate {\n display: none;\n}\n\n[hidden] {\n display: none !important;\n}\n\nh1, h2, h3, h4, h5, h6,\n.h1, .h2, .h3, .h4, .h5, .h6 {\n margin-bottom: 0.5rem;\n font-weight: 500;\n line-height: 1.2;\n}\n\nh1, .h1 {\n font-size: 2.5rem;\n}\n\nh2, .h2 {\n font-size: 2rem;\n}\n\nh3, .h3 {\n font-size: 1.75rem;\n}\n\nh4, .h4 {\n font-size: 1.5rem;\n}\n\nh5, .h5 {\n font-size: 1.25rem;\n}\n\nh6, .h6 {\n font-size: 1rem;\n}\n\n.lead {\n font-size: 1.25rem;\n font-weight: 300;\n}\n\n.display-1 {\n font-size: 6rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\n.display-2 {\n font-size: 5.5rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\n.display-3 {\n font-size: 4.5rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\n.display-4 {\n font-size: 3.5rem;\n font-weight: 300;\n line-height: 1.2;\n}\n\nhr {\n margin-top: 1rem;\n margin-bottom: 1rem;\n border: 0;\n border-top: 1px solid rgba(0, 0, 0, 0.1);\n}\n\nsmall,\n.small {\n font-size: 80%;\n font-weight: 400;\n}\n\nmark,\n.mark {\n padding: 0.2em;\n background-color: #fcf8e3;\n}\n\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n\n.list-inline {\n padding-left: 0;\n list-style: none;\n}\n\n.list-inline-item {\n display: inline-block;\n}\n\n.list-inline-item:not(:last-child) {\n margin-right: 0.5rem;\n}\n\n.initialism {\n font-size: 90%;\n text-transform: uppercase;\n}\n\n.blockquote {\n margin-bottom: 1rem;\n font-size: 1.25rem;\n}\n\n.blockquote-footer {\n display: block;\n font-size: 80%;\n color: #6c757d;\n}\n\n.blockquote-footer::before {\n content: \"\\2014\\00A0\";\n}\n\n.img-fluid {\n max-width: 100%;\n height: auto;\n}\n\n.img-thumbnail {\n padding: 0.25rem;\n background-color: #fff;\n border: 1px solid #dee2e6;\n border-radius: 0.25rem;\n max-width: 100%;\n height: auto;\n}\n\n.figure {\n display: inline-block;\n}\n\n.figure-img {\n margin-bottom: 0.5rem;\n line-height: 1;\n}\n\n.figure-caption {\n font-size: 90%;\n color: #6c757d;\n}\n\ncode {\n font-size: 87.5%;\n color: #e83e8c;\n word-break: break-word;\n}\n\na > code {\n color: inherit;\n}\n\nkbd {\n padding: 0.2rem 0.4rem;\n font-size: 87.5%;\n color: #fff;\n background-color: #212529;\n border-radius: 0.2rem;\n}\n\nkbd kbd {\n padding: 0;\n font-size: 100%;\n font-weight: 700;\n}\n\npre {\n display: block;\n font-size: 87.5%;\n color: #212529;\n}\n\npre code {\n font-size: inherit;\n color: inherit;\n word-break: normal;\n}\n\n.pre-scrollable {\n max-height: 340px;\n overflow-y: scroll;\n}\n\n.container {\n width: 100%;\n padding-right: 15px;\n padding-left: 15px;\n margin-right: auto;\n margin-left: auto;\n}\n\n@media (min-width: 576px) {\n .container {\n max-width: 540px;\n }\n}\n\n@media (min-width: 768px) {\n .container {\n max-width: 720px;\n }\n}\n\n@media (min-width: 992px) {\n .container {\n max-width: 960px;\n }\n}\n\n@media (min-width: 1200px) {\n .container {\n max-width: 1140px;\n }\n}\n\n.container-fluid {\n width: 100%;\n padding-right: 15px;\n padding-left: 15px;\n margin-right: auto;\n margin-left: auto;\n}\n\n.row {\n display: flex;\n flex-wrap: wrap;\n margin-right: -15px;\n margin-left: -15px;\n}\n\n.no-gutters {\n margin-right: 0;\n margin-left: 0;\n}\n\n.no-gutters > .col,\n.no-gutters > [class*=\"col-\"] {\n padding-right: 0;\n padding-left: 0;\n}\n\n.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col,\n.col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm,\n.col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md,\n.col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg,\n.col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl,\n.col-xl-auto {\n position: relative;\n width: 100%;\n padding-right: 15px;\n padding-left: 15px;\n}\n\n.col {\n flex-basis: 0;\n flex-grow: 1;\n max-width: 100%;\n}\n\n.col-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n}\n\n.col-1 {\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n}\n\n.col-2 {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n}\n\n.col-3 {\n flex: 0 0 25%;\n max-width: 25%;\n}\n\n.col-4 {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n}\n\n.col-5 {\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n}\n\n.col-6 {\n flex: 0 0 50%;\n max-width: 50%;\n}\n\n.col-7 {\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n}\n\n.col-8 {\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n}\n\n.col-9 {\n flex: 0 0 75%;\n max-width: 75%;\n}\n\n.col-10 {\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n}\n\n.col-11 {\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n}\n\n.col-12 {\n flex: 0 0 100%;\n max-width: 100%;\n}\n\n.order-first {\n order: -1;\n}\n\n.order-last {\n order: 13;\n}\n\n.order-0 {\n order: 0;\n}\n\n.order-1 {\n order: 1;\n}\n\n.order-2 {\n order: 2;\n}\n\n.order-3 {\n order: 3;\n}\n\n.order-4 {\n order: 4;\n}\n\n.order-5 {\n order: 5;\n}\n\n.order-6 {\n order: 6;\n}\n\n.order-7 {\n order: 7;\n}\n\n.order-8 {\n order: 8;\n}\n\n.order-9 {\n order: 9;\n}\n\n.order-10 {\n order: 10;\n}\n\n.order-11 {\n order: 11;\n}\n\n.order-12 {\n order: 12;\n}\n\n.offset-1 {\n margin-left: 8.333333%;\n}\n\n.offset-2 {\n margin-left: 16.666667%;\n}\n\n.offset-3 {\n margin-left: 25%;\n}\n\n.offset-4 {\n margin-left: 33.333333%;\n}\n\n.offset-5 {\n margin-left: 41.666667%;\n}\n\n.offset-6 {\n margin-left: 50%;\n}\n\n.offset-7 {\n margin-left: 58.333333%;\n}\n\n.offset-8 {\n margin-left: 66.666667%;\n}\n\n.offset-9 {\n margin-left: 75%;\n}\n\n.offset-10 {\n margin-left: 83.333333%;\n}\n\n.offset-11 {\n margin-left: 91.666667%;\n}\n\n@media (min-width: 576px) {\n .col-sm {\n flex-basis: 0;\n flex-grow: 1;\n max-width: 100%;\n }\n .col-sm-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n }\n .col-sm-1 {\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-sm-2 {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-sm-3 {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-sm-4 {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-sm-5 {\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-sm-6 {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-sm-7 {\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-sm-8 {\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-sm-9 {\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-sm-10 {\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-sm-11 {\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-sm-12 {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-sm-first {\n order: -1;\n }\n .order-sm-last {\n order: 13;\n }\n .order-sm-0 {\n order: 0;\n }\n .order-sm-1 {\n order: 1;\n }\n .order-sm-2 {\n order: 2;\n }\n .order-sm-3 {\n order: 3;\n }\n .order-sm-4 {\n order: 4;\n }\n .order-sm-5 {\n order: 5;\n }\n .order-sm-6 {\n order: 6;\n }\n .order-sm-7 {\n order: 7;\n }\n .order-sm-8 {\n order: 8;\n }\n .order-sm-9 {\n order: 9;\n }\n .order-sm-10 {\n order: 10;\n }\n .order-sm-11 {\n order: 11;\n }\n .order-sm-12 {\n order: 12;\n }\n .offset-sm-0 {\n margin-left: 0;\n }\n .offset-sm-1 {\n margin-left: 8.333333%;\n }\n .offset-sm-2 {\n margin-left: 16.666667%;\n }\n .offset-sm-3 {\n margin-left: 25%;\n }\n .offset-sm-4 {\n margin-left: 33.333333%;\n }\n .offset-sm-5 {\n margin-left: 41.666667%;\n }\n .offset-sm-6 {\n margin-left: 50%;\n }\n .offset-sm-7 {\n margin-left: 58.333333%;\n }\n .offset-sm-8 {\n margin-left: 66.666667%;\n }\n .offset-sm-9 {\n margin-left: 75%;\n }\n .offset-sm-10 {\n margin-left: 83.333333%;\n }\n .offset-sm-11 {\n margin-left: 91.666667%;\n }\n}\n\n@media (min-width: 768px) {\n .col-md {\n flex-basis: 0;\n flex-grow: 1;\n max-width: 100%;\n }\n .col-md-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n }\n .col-md-1 {\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-md-2 {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-md-3 {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-md-4 {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-md-5 {\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-md-6 {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-md-7 {\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-md-8 {\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-md-9 {\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-md-10 {\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-md-11 {\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-md-12 {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-md-first {\n order: -1;\n }\n .order-md-last {\n order: 13;\n }\n .order-md-0 {\n order: 0;\n }\n .order-md-1 {\n order: 1;\n }\n .order-md-2 {\n order: 2;\n }\n .order-md-3 {\n order: 3;\n }\n .order-md-4 {\n order: 4;\n }\n .order-md-5 {\n order: 5;\n }\n .order-md-6 {\n order: 6;\n }\n .order-md-7 {\n order: 7;\n }\n .order-md-8 {\n order: 8;\n }\n .order-md-9 {\n order: 9;\n }\n .order-md-10 {\n order: 10;\n }\n .order-md-11 {\n order: 11;\n }\n .order-md-12 {\n order: 12;\n }\n .offset-md-0 {\n margin-left: 0;\n }\n .offset-md-1 {\n margin-left: 8.333333%;\n }\n .offset-md-2 {\n margin-left: 16.666667%;\n }\n .offset-md-3 {\n margin-left: 25%;\n }\n .offset-md-4 {\n margin-left: 33.333333%;\n }\n .offset-md-5 {\n margin-left: 41.666667%;\n }\n .offset-md-6 {\n margin-left: 50%;\n }\n .offset-md-7 {\n margin-left: 58.333333%;\n }\n .offset-md-8 {\n margin-left: 66.666667%;\n }\n .offset-md-9 {\n margin-left: 75%;\n }\n .offset-md-10 {\n margin-left: 83.333333%;\n }\n .offset-md-11 {\n margin-left: 91.666667%;\n }\n}\n\n@media (min-width: 992px) {\n .col-lg {\n flex-basis: 0;\n flex-grow: 1;\n max-width: 100%;\n }\n .col-lg-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n }\n .col-lg-1 {\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-lg-2 {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-lg-3 {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-lg-4 {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-lg-5 {\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-lg-6 {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-lg-7 {\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-lg-8 {\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-lg-9 {\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-lg-10 {\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-lg-11 {\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-lg-12 {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-lg-first {\n order: -1;\n }\n .order-lg-last {\n order: 13;\n }\n .order-lg-0 {\n order: 0;\n }\n .order-lg-1 {\n order: 1;\n }\n .order-lg-2 {\n order: 2;\n }\n .order-lg-3 {\n order: 3;\n }\n .order-lg-4 {\n order: 4;\n }\n .order-lg-5 {\n order: 5;\n }\n .order-lg-6 {\n order: 6;\n }\n .order-lg-7 {\n order: 7;\n }\n .order-lg-8 {\n order: 8;\n }\n .order-lg-9 {\n order: 9;\n }\n .order-lg-10 {\n order: 10;\n }\n .order-lg-11 {\n order: 11;\n }\n .order-lg-12 {\n order: 12;\n }\n .offset-lg-0 {\n margin-left: 0;\n }\n .offset-lg-1 {\n margin-left: 8.333333%;\n }\n .offset-lg-2 {\n margin-left: 16.666667%;\n }\n .offset-lg-3 {\n margin-left: 25%;\n }\n .offset-lg-4 {\n margin-left: 33.333333%;\n }\n .offset-lg-5 {\n margin-left: 41.666667%;\n }\n .offset-lg-6 {\n margin-left: 50%;\n }\n .offset-lg-7 {\n margin-left: 58.333333%;\n }\n .offset-lg-8 {\n margin-left: 66.666667%;\n }\n .offset-lg-9 {\n margin-left: 75%;\n }\n .offset-lg-10 {\n margin-left: 83.333333%;\n }\n .offset-lg-11 {\n margin-left: 91.666667%;\n }\n}\n\n@media (min-width: 1200px) {\n .col-xl {\n flex-basis: 0;\n flex-grow: 1;\n max-width: 100%;\n }\n .col-xl-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: 100%;\n }\n .col-xl-1 {\n flex: 0 0 8.333333%;\n max-width: 8.333333%;\n }\n .col-xl-2 {\n flex: 0 0 16.666667%;\n max-width: 16.666667%;\n }\n .col-xl-3 {\n flex: 0 0 25%;\n max-width: 25%;\n }\n .col-xl-4 {\n flex: 0 0 33.333333%;\n max-width: 33.333333%;\n }\n .col-xl-5 {\n flex: 0 0 41.666667%;\n max-width: 41.666667%;\n }\n .col-xl-6 {\n flex: 0 0 50%;\n max-width: 50%;\n }\n .col-xl-7 {\n flex: 0 0 58.333333%;\n max-width: 58.333333%;\n }\n .col-xl-8 {\n flex: 0 0 66.666667%;\n max-width: 66.666667%;\n }\n .col-xl-9 {\n flex: 0 0 75%;\n max-width: 75%;\n }\n .col-xl-10 {\n flex: 0 0 83.333333%;\n max-width: 83.333333%;\n }\n .col-xl-11 {\n flex: 0 0 91.666667%;\n max-width: 91.666667%;\n }\n .col-xl-12 {\n flex: 0 0 100%;\n max-width: 100%;\n }\n .order-xl-first {\n order: -1;\n }\n .order-xl-last {\n order: 13;\n }\n .order-xl-0 {\n order: 0;\n }\n .order-xl-1 {\n order: 1;\n }\n .order-xl-2 {\n order: 2;\n }\n .order-xl-3 {\n order: 3;\n }\n .order-xl-4 {\n order: 4;\n }\n .order-xl-5 {\n order: 5;\n }\n .order-xl-6 {\n order: 6;\n }\n .order-xl-7 {\n order: 7;\n }\n .order-xl-8 {\n order: 8;\n }\n .order-xl-9 {\n order: 9;\n }\n .order-xl-10 {\n order: 10;\n }\n .order-xl-11 {\n order: 11;\n }\n .order-xl-12 {\n order: 12;\n }\n .offset-xl-0 {\n margin-left: 0;\n }\n .offset-xl-1 {\n margin-left: 8.333333%;\n }\n .offset-xl-2 {\n margin-left: 16.666667%;\n }\n .offset-xl-3 {\n margin-left: 25%;\n }\n .offset-xl-4 {\n margin-left: 33.333333%;\n }\n .offset-xl-5 {\n margin-left: 41.666667%;\n }\n .offset-xl-6 {\n margin-left: 50%;\n }\n .offset-xl-7 {\n margin-left: 58.333333%;\n }\n .offset-xl-8 {\n margin-left: 66.666667%;\n }\n .offset-xl-9 {\n margin-left: 75%;\n }\n .offset-xl-10 {\n margin-left: 83.333333%;\n }\n .offset-xl-11 {\n margin-left: 91.666667%;\n }\n}\n\n.table {\n width: 100%;\n margin-bottom: 1rem;\n color: #212529;\n}\n\n.table th,\n.table td {\n padding: 0.75rem;\n vertical-align: top;\n border-top: 1px solid #dee2e6;\n}\n\n.table thead th {\n vertical-align: bottom;\n border-bottom: 2px solid #dee2e6;\n}\n\n.table tbody + tbody {\n border-top: 2px solid #dee2e6;\n}\n\n.table-sm th,\n.table-sm td {\n padding: 0.3rem;\n}\n\n.table-bordered {\n border: 1px solid #dee2e6;\n}\n\n.table-bordered th,\n.table-bordered td {\n border: 1px solid #dee2e6;\n}\n\n.table-bordered thead th,\n.table-bordered thead td {\n border-bottom-width: 2px;\n}\n\n.table-borderless th,\n.table-borderless td,\n.table-borderless thead th,\n.table-borderless tbody + tbody {\n border: 0;\n}\n\n.table-striped tbody tr:nth-of-type(odd) {\n background-color: rgba(0, 0, 0, 0.05);\n}\n\n.table-hover tbody tr:hover {\n color: #212529;\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table-primary,\n.table-primary > th,\n.table-primary > td {\n background-color: #b8daff;\n}\n\n.table-primary th,\n.table-primary td,\n.table-primary thead th,\n.table-primary tbody + tbody {\n border-color: #7abaff;\n}\n\n.table-hover .table-primary:hover {\n background-color: #9fcdff;\n}\n\n.table-hover .table-primary:hover > td,\n.table-hover .table-primary:hover > th {\n background-color: #9fcdff;\n}\n\n.table-secondary,\n.table-secondary > th,\n.table-secondary > td {\n background-color: #d6d8db;\n}\n\n.table-secondary th,\n.table-secondary td,\n.table-secondary thead th,\n.table-secondary tbody + tbody {\n border-color: #b3b7bb;\n}\n\n.table-hover .table-secondary:hover {\n background-color: #c8cbcf;\n}\n\n.table-hover .table-secondary:hover > td,\n.table-hover .table-secondary:hover > th {\n background-color: #c8cbcf;\n}\n\n.table-success,\n.table-success > th,\n.table-success > td {\n background-color: #c3e6cb;\n}\n\n.table-success th,\n.table-success td,\n.table-success thead th,\n.table-success tbody + tbody {\n border-color: #8fd19e;\n}\n\n.table-hover .table-success:hover {\n background-color: #b1dfbb;\n}\n\n.table-hover .table-success:hover > td,\n.table-hover .table-success:hover > th {\n background-color: #b1dfbb;\n}\n\n.table-info,\n.table-info > th,\n.table-info > td {\n background-color: #bee5eb;\n}\n\n.table-info th,\n.table-info td,\n.table-info thead th,\n.table-info tbody + tbody {\n border-color: #86cfda;\n}\n\n.table-hover .table-info:hover {\n background-color: #abdde5;\n}\n\n.table-hover .table-info:hover > td,\n.table-hover .table-info:hover > th {\n background-color: #abdde5;\n}\n\n.table-warning,\n.table-warning > th,\n.table-warning > td {\n background-color: #ffeeba;\n}\n\n.table-warning th,\n.table-warning td,\n.table-warning thead th,\n.table-warning tbody + tbody {\n border-color: #ffdf7e;\n}\n\n.table-hover .table-warning:hover {\n background-color: #ffe8a1;\n}\n\n.table-hover .table-warning:hover > td,\n.table-hover .table-warning:hover > th {\n background-color: #ffe8a1;\n}\n\n.table-danger,\n.table-danger > th,\n.table-danger > td {\n background-color: #f5c6cb;\n}\n\n.table-danger th,\n.table-danger td,\n.table-danger thead th,\n.table-danger tbody + tbody {\n border-color: #ed969e;\n}\n\n.table-hover .table-danger:hover {\n background-color: #f1b0b7;\n}\n\n.table-hover .table-danger:hover > td,\n.table-hover .table-danger:hover > th {\n background-color: #f1b0b7;\n}\n\n.table-light,\n.table-light > th,\n.table-light > td {\n background-color: #fdfdfe;\n}\n\n.table-light th,\n.table-light td,\n.table-light thead th,\n.table-light tbody + tbody {\n border-color: #fbfcfc;\n}\n\n.table-hover .table-light:hover {\n background-color: #ececf6;\n}\n\n.table-hover .table-light:hover > td,\n.table-hover .table-light:hover > th {\n background-color: #ececf6;\n}\n\n.table-dark,\n.table-dark > th,\n.table-dark > td {\n background-color: #c6c8ca;\n}\n\n.table-dark th,\n.table-dark td,\n.table-dark thead th,\n.table-dark tbody + tbody {\n border-color: #95999c;\n}\n\n.table-hover .table-dark:hover {\n background-color: #b9bbbe;\n}\n\n.table-hover .table-dark:hover > td,\n.table-hover .table-dark:hover > th {\n background-color: #b9bbbe;\n}\n\n.table-active,\n.table-active > th,\n.table-active > td {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table-hover .table-active:hover {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table-hover .table-active:hover > td,\n.table-hover .table-active:hover > th {\n background-color: rgba(0, 0, 0, 0.075);\n}\n\n.table .thead-dark th {\n color: #fff;\n background-color: #343a40;\n border-color: #454d55;\n}\n\n.table .thead-light th {\n color: #495057;\n background-color: #e9ecef;\n border-color: #dee2e6;\n}\n\n.table-dark {\n color: #fff;\n background-color: #343a40;\n}\n\n.table-dark th,\n.table-dark td,\n.table-dark thead th {\n border-color: #454d55;\n}\n\n.table-dark.table-bordered {\n border: 0;\n}\n\n.table-dark.table-striped tbody tr:nth-of-type(odd) {\n background-color: rgba(255, 255, 255, 0.05);\n}\n\n.table-dark.table-hover tbody tr:hover {\n color: #fff;\n background-color: rgba(255, 255, 255, 0.075);\n}\n\n@media (max-width: 575.98px) {\n .table-responsive-sm {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n }\n .table-responsive-sm > .table-bordered {\n border: 0;\n }\n}\n\n@media (max-width: 767.98px) {\n .table-responsive-md {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n }\n .table-responsive-md > .table-bordered {\n border: 0;\n }\n}\n\n@media (max-width: 991.98px) {\n .table-responsive-lg {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n }\n .table-responsive-lg > .table-bordered {\n border: 0;\n }\n}\n\n@media (max-width: 1199.98px) {\n .table-responsive-xl {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n }\n .table-responsive-xl > .table-bordered {\n border: 0;\n }\n}\n\n.table-responsive {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n}\n\n.table-responsive > .table-bordered {\n border: 0;\n}\n\n.form-control {\n display: block;\n width: 100%;\n height: calc(1.5em + 0.75rem + 2px);\n padding: 0.375rem 0.75rem;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: #495057;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .form-control {\n transition: none;\n }\n}\n\n.form-control::-ms-expand {\n background-color: transparent;\n border: 0;\n}\n\n.form-control:focus {\n color: #495057;\n background-color: #fff;\n border-color: #80bdff;\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.form-control::placeholder {\n color: #6c757d;\n opacity: 1;\n}\n\n.form-control:disabled, .form-control[readonly] {\n background-color: #e9ecef;\n opacity: 1;\n}\n\nselect.form-control:focus::-ms-value {\n color: #495057;\n background-color: #fff;\n}\n\n.form-control-file,\n.form-control-range {\n display: block;\n width: 100%;\n}\n\n.col-form-label {\n padding-top: calc(0.375rem + 1px);\n padding-bottom: calc(0.375rem + 1px);\n margin-bottom: 0;\n font-size: inherit;\n line-height: 1.5;\n}\n\n.col-form-label-lg {\n padding-top: calc(0.5rem + 1px);\n padding-bottom: calc(0.5rem + 1px);\n font-size: 1.25rem;\n line-height: 1.5;\n}\n\n.col-form-label-sm {\n padding-top: calc(0.25rem + 1px);\n padding-bottom: calc(0.25rem + 1px);\n font-size: 0.875rem;\n line-height: 1.5;\n}\n\n.form-control-plaintext {\n display: block;\n width: 100%;\n padding-top: 0.375rem;\n padding-bottom: 0.375rem;\n margin-bottom: 0;\n line-height: 1.5;\n color: #212529;\n background-color: transparent;\n border: solid transparent;\n border-width: 1px 0;\n}\n\n.form-control-plaintext.form-control-sm, .form-control-plaintext.form-control-lg {\n padding-right: 0;\n padding-left: 0;\n}\n\n.form-control-sm {\n height: calc(1.5em + 0.5rem + 2px);\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n border-radius: 0.2rem;\n}\n\n.form-control-lg {\n height: calc(1.5em + 1rem + 2px);\n padding: 0.5rem 1rem;\n font-size: 1.25rem;\n line-height: 1.5;\n border-radius: 0.3rem;\n}\n\nselect.form-control[size], select.form-control[multiple] {\n height: auto;\n}\n\ntextarea.form-control {\n height: auto;\n}\n\n.form-group {\n margin-bottom: 1rem;\n}\n\n.form-text {\n display: block;\n margin-top: 0.25rem;\n}\n\n.form-row {\n display: flex;\n flex-wrap: wrap;\n margin-right: -5px;\n margin-left: -5px;\n}\n\n.form-row > .col,\n.form-row > [class*=\"col-\"] {\n padding-right: 5px;\n padding-left: 5px;\n}\n\n.form-check {\n position: relative;\n display: block;\n padding-left: 1.25rem;\n}\n\n.form-check-input {\n position: absolute;\n margin-top: 0.3rem;\n margin-left: -1.25rem;\n}\n\n.form-check-input:disabled ~ .form-check-label {\n color: #6c757d;\n}\n\n.form-check-label {\n margin-bottom: 0;\n}\n\n.form-check-inline {\n display: inline-flex;\n align-items: center;\n padding-left: 0;\n margin-right: 0.75rem;\n}\n\n.form-check-inline .form-check-input {\n position: static;\n margin-top: 0;\n margin-right: 0.3125rem;\n margin-left: 0;\n}\n\n.valid-feedback {\n display: none;\n width: 100%;\n margin-top: 0.25rem;\n font-size: 80%;\n color: #28a745;\n}\n\n.valid-tooltip {\n position: absolute;\n top: 100%;\n z-index: 5;\n display: none;\n max-width: 100%;\n padding: 0.25rem 0.5rem;\n margin-top: .1rem;\n font-size: 0.875rem;\n line-height: 1.5;\n color: #fff;\n background-color: rgba(40, 167, 69, 0.9);\n border-radius: 0.25rem;\n}\n\n.was-validated .form-control:valid, .form-control.is-valid {\n border-color: #28a745;\n padding-right: calc(1.5em + 0.75rem);\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e\");\n background-repeat: no-repeat;\n background-position: center right calc(0.375em + 0.1875rem);\n background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n\n.was-validated .form-control:valid:focus, .form-control.is-valid:focus {\n border-color: #28a745;\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.was-validated .form-control:valid ~ .valid-feedback,\n.was-validated .form-control:valid ~ .valid-tooltip, .form-control.is-valid ~ .valid-feedback,\n.form-control.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated textarea.form-control:valid, textarea.form-control.is-valid {\n padding-right: calc(1.5em + 0.75rem);\n background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem);\n}\n\n.was-validated .custom-select:valid, .custom-select.is-valid {\n border-color: #28a745;\n padding-right: calc((1em + 0.75rem) * 3 / 4 + 1.75rem);\n background: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e\") no-repeat right 0.75rem center/8px 10px, url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e\") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n\n.was-validated .custom-select:valid:focus, .custom-select.is-valid:focus {\n border-color: #28a745;\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.was-validated .custom-select:valid ~ .valid-feedback,\n.was-validated .custom-select:valid ~ .valid-tooltip, .custom-select.is-valid ~ .valid-feedback,\n.custom-select.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .form-control-file:valid ~ .valid-feedback,\n.was-validated .form-control-file:valid ~ .valid-tooltip, .form-control-file.is-valid ~ .valid-feedback,\n.form-control-file.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label {\n color: #28a745;\n}\n\n.was-validated .form-check-input:valid ~ .valid-feedback,\n.was-validated .form-check-input:valid ~ .valid-tooltip, .form-check-input.is-valid ~ .valid-feedback,\n.form-check-input.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .custom-control-input:valid ~ .custom-control-label, .custom-control-input.is-valid ~ .custom-control-label {\n color: #28a745;\n}\n\n.was-validated .custom-control-input:valid ~ .custom-control-label::before, .custom-control-input.is-valid ~ .custom-control-label::before {\n border-color: #28a745;\n}\n\n.was-validated .custom-control-input:valid ~ .valid-feedback,\n.was-validated .custom-control-input:valid ~ .valid-tooltip, .custom-control-input.is-valid ~ .valid-feedback,\n.custom-control-input.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .custom-control-input:valid:checked ~ .custom-control-label::before, .custom-control-input.is-valid:checked ~ .custom-control-label::before {\n border-color: #34ce57;\n background-color: #34ce57;\n}\n\n.was-validated .custom-control-input:valid:focus ~ .custom-control-label::before, .custom-control-input.is-valid:focus ~ .custom-control-label::before {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.was-validated .custom-control-input:valid:focus:not(:checked) ~ .custom-control-label::before, .custom-control-input.is-valid:focus:not(:checked) ~ .custom-control-label::before {\n border-color: #28a745;\n}\n\n.was-validated .custom-file-input:valid ~ .custom-file-label, .custom-file-input.is-valid ~ .custom-file-label {\n border-color: #28a745;\n}\n\n.was-validated .custom-file-input:valid ~ .valid-feedback,\n.was-validated .custom-file-input:valid ~ .valid-tooltip, .custom-file-input.is-valid ~ .valid-feedback,\n.custom-file-input.is-valid ~ .valid-tooltip {\n display: block;\n}\n\n.was-validated .custom-file-input:valid:focus ~ .custom-file-label, .custom-file-input.is-valid:focus ~ .custom-file-label {\n border-color: #28a745;\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25);\n}\n\n.invalid-feedback {\n display: none;\n width: 100%;\n margin-top: 0.25rem;\n font-size: 80%;\n color: #dc3545;\n}\n\n.invalid-tooltip {\n position: absolute;\n top: 100%;\n z-index: 5;\n display: none;\n max-width: 100%;\n padding: 0.25rem 0.5rem;\n margin-top: .1rem;\n font-size: 0.875rem;\n line-height: 1.5;\n color: #fff;\n background-color: rgba(220, 53, 69, 0.9);\n border-radius: 0.25rem;\n}\n\n.was-validated .form-control:invalid, .form-control.is-invalid {\n border-color: #dc3545;\n padding-right: calc(1.5em + 0.75rem);\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23dc3545' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23dc3545' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E\");\n background-repeat: no-repeat;\n background-position: center right calc(0.375em + 0.1875rem);\n background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n\n.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus {\n border-color: #dc3545;\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.was-validated .form-control:invalid ~ .invalid-feedback,\n.was-validated .form-control:invalid ~ .invalid-tooltip, .form-control.is-invalid ~ .invalid-feedback,\n.form-control.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated textarea.form-control:invalid, textarea.form-control.is-invalid {\n padding-right: calc(1.5em + 0.75rem);\n background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem);\n}\n\n.was-validated .custom-select:invalid, .custom-select.is-invalid {\n border-color: #dc3545;\n padding-right: calc((1em + 0.75rem) * 3 / 4 + 1.75rem);\n background: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e\") no-repeat right 0.75rem center/8px 10px, url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23dc3545' viewBox='-2 -2 7 7'%3e%3cpath stroke='%23dc3545' d='M0 0l3 3m0-3L0 3'/%3e%3ccircle r='.5'/%3e%3ccircle cx='3' r='.5'/%3e%3ccircle cy='3' r='.5'/%3e%3ccircle cx='3' cy='3' r='.5'/%3e%3c/svg%3E\") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem);\n}\n\n.was-validated .custom-select:invalid:focus, .custom-select.is-invalid:focus {\n border-color: #dc3545;\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.was-validated .custom-select:invalid ~ .invalid-feedback,\n.was-validated .custom-select:invalid ~ .invalid-tooltip, .custom-select.is-invalid ~ .invalid-feedback,\n.custom-select.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .form-control-file:invalid ~ .invalid-feedback,\n.was-validated .form-control-file:invalid ~ .invalid-tooltip, .form-control-file.is-invalid ~ .invalid-feedback,\n.form-control-file.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label {\n color: #dc3545;\n}\n\n.was-validated .form-check-input:invalid ~ .invalid-feedback,\n.was-validated .form-check-input:invalid ~ .invalid-tooltip, .form-check-input.is-invalid ~ .invalid-feedback,\n.form-check-input.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .custom-control-input:invalid ~ .custom-control-label, .custom-control-input.is-invalid ~ .custom-control-label {\n color: #dc3545;\n}\n\n.was-validated .custom-control-input:invalid ~ .custom-control-label::before, .custom-control-input.is-invalid ~ .custom-control-label::before {\n border-color: #dc3545;\n}\n\n.was-validated .custom-control-input:invalid ~ .invalid-feedback,\n.was-validated .custom-control-input:invalid ~ .invalid-tooltip, .custom-control-input.is-invalid ~ .invalid-feedback,\n.custom-control-input.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .custom-control-input:invalid:checked ~ .custom-control-label::before, .custom-control-input.is-invalid:checked ~ .custom-control-label::before {\n border-color: #e4606d;\n background-color: #e4606d;\n}\n\n.was-validated .custom-control-input:invalid:focus ~ .custom-control-label::before, .custom-control-input.is-invalid:focus ~ .custom-control-label::before {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.was-validated .custom-control-input:invalid:focus:not(:checked) ~ .custom-control-label::before, .custom-control-input.is-invalid:focus:not(:checked) ~ .custom-control-label::before {\n border-color: #dc3545;\n}\n\n.was-validated .custom-file-input:invalid ~ .custom-file-label, .custom-file-input.is-invalid ~ .custom-file-label {\n border-color: #dc3545;\n}\n\n.was-validated .custom-file-input:invalid ~ .invalid-feedback,\n.was-validated .custom-file-input:invalid ~ .invalid-tooltip, .custom-file-input.is-invalid ~ .invalid-feedback,\n.custom-file-input.is-invalid ~ .invalid-tooltip {\n display: block;\n}\n\n.was-validated .custom-file-input:invalid:focus ~ .custom-file-label, .custom-file-input.is-invalid:focus ~ .custom-file-label {\n border-color: #dc3545;\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25);\n}\n\n.form-inline {\n display: flex;\n flex-flow: row wrap;\n align-items: center;\n}\n\n.form-inline .form-check {\n width: 100%;\n}\n\n@media (min-width: 576px) {\n .form-inline label {\n display: flex;\n align-items: center;\n justify-content: center;\n margin-bottom: 0;\n }\n .form-inline .form-group {\n display: flex;\n flex: 0 0 auto;\n flex-flow: row wrap;\n align-items: center;\n margin-bottom: 0;\n }\n .form-inline .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .form-inline .form-control-plaintext {\n display: inline-block;\n }\n .form-inline .input-group,\n .form-inline .custom-select {\n width: auto;\n }\n .form-inline .form-check {\n display: flex;\n align-items: center;\n justify-content: center;\n width: auto;\n padding-left: 0;\n }\n .form-inline .form-check-input {\n position: relative;\n flex-shrink: 0;\n margin-top: 0;\n margin-right: 0.25rem;\n margin-left: 0;\n }\n .form-inline .custom-control {\n align-items: center;\n justify-content: center;\n }\n .form-inline .custom-control-label {\n margin-bottom: 0;\n }\n}\n\n.btn {\n display: inline-block;\n font-weight: 400;\n color: #212529;\n text-align: center;\n vertical-align: middle;\n user-select: none;\n background-color: transparent;\n border: 1px solid transparent;\n padding: 0.375rem 0.75rem;\n font-size: 1rem;\n line-height: 1.5;\n border-radius: 0.25rem;\n transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .btn {\n transition: none;\n }\n}\n\n.btn:hover {\n color: #212529;\n text-decoration: none;\n}\n\n.btn:focus, .btn.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.btn.disabled, .btn:disabled {\n opacity: 0.65;\n}\n\na.btn.disabled,\nfieldset:disabled a.btn {\n pointer-events: none;\n}\n\n.btn-primary {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-primary:hover {\n color: #fff;\n background-color: #0069d9;\n border-color: #0062cc;\n}\n\n.btn-primary:focus, .btn-primary.focus {\n box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.5);\n}\n\n.btn-primary.disabled, .btn-primary:disabled {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-primary:not(:disabled):not(.disabled):active, .btn-primary:not(:disabled):not(.disabled).active,\n.show > .btn-primary.dropdown-toggle {\n color: #fff;\n background-color: #0062cc;\n border-color: #005cbf;\n}\n\n.btn-primary:not(:disabled):not(.disabled):active:focus, .btn-primary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-primary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.5);\n}\n\n.btn-secondary {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-secondary:hover {\n color: #fff;\n background-color: #5a6268;\n border-color: #545b62;\n}\n\n.btn-secondary:focus, .btn-secondary.focus {\n box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5);\n}\n\n.btn-secondary.disabled, .btn-secondary:disabled {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-secondary:not(:disabled):not(.disabled):active, .btn-secondary:not(:disabled):not(.disabled).active,\n.show > .btn-secondary.dropdown-toggle {\n color: #fff;\n background-color: #545b62;\n border-color: #4e555b;\n}\n\n.btn-secondary:not(:disabled):not(.disabled):active:focus, .btn-secondary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-secondary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5);\n}\n\n.btn-success {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-success:hover {\n color: #fff;\n background-color: #218838;\n border-color: #1e7e34;\n}\n\n.btn-success:focus, .btn-success.focus {\n box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5);\n}\n\n.btn-success.disabled, .btn-success:disabled {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-success:not(:disabled):not(.disabled):active, .btn-success:not(:disabled):not(.disabled).active,\n.show > .btn-success.dropdown-toggle {\n color: #fff;\n background-color: #1e7e34;\n border-color: #1c7430;\n}\n\n.btn-success:not(:disabled):not(.disabled):active:focus, .btn-success:not(:disabled):not(.disabled).active:focus,\n.show > .btn-success.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5);\n}\n\n.btn-info {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-info:hover {\n color: #fff;\n background-color: #138496;\n border-color: #117a8b;\n}\n\n.btn-info:focus, .btn-info.focus {\n box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5);\n}\n\n.btn-info.disabled, .btn-info:disabled {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-info:not(:disabled):not(.disabled):active, .btn-info:not(:disabled):not(.disabled).active,\n.show > .btn-info.dropdown-toggle {\n color: #fff;\n background-color: #117a8b;\n border-color: #10707f;\n}\n\n.btn-info:not(:disabled):not(.disabled):active:focus, .btn-info:not(:disabled):not(.disabled).active:focus,\n.show > .btn-info.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5);\n}\n\n.btn-warning {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-warning:hover {\n color: #212529;\n background-color: #e0a800;\n border-color: #d39e00;\n}\n\n.btn-warning:focus, .btn-warning.focus {\n box-shadow: 0 0 0 0.2rem rgba(222, 170, 12, 0.5);\n}\n\n.btn-warning.disabled, .btn-warning:disabled {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-warning:not(:disabled):not(.disabled):active, .btn-warning:not(:disabled):not(.disabled).active,\n.show > .btn-warning.dropdown-toggle {\n color: #212529;\n background-color: #d39e00;\n border-color: #c69500;\n}\n\n.btn-warning:not(:disabled):not(.disabled):active:focus, .btn-warning:not(:disabled):not(.disabled).active:focus,\n.show > .btn-warning.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(222, 170, 12, 0.5);\n}\n\n.btn-danger {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-danger:hover {\n color: #fff;\n background-color: #c82333;\n border-color: #bd2130;\n}\n\n.btn-danger:focus, .btn-danger.focus {\n box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5);\n}\n\n.btn-danger.disabled, .btn-danger:disabled {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-danger:not(:disabled):not(.disabled):active, .btn-danger:not(:disabled):not(.disabled).active,\n.show > .btn-danger.dropdown-toggle {\n color: #fff;\n background-color: #bd2130;\n border-color: #b21f2d;\n}\n\n.btn-danger:not(:disabled):not(.disabled):active:focus, .btn-danger:not(:disabled):not(.disabled).active:focus,\n.show > .btn-danger.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5);\n}\n\n.btn-light {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-light:hover {\n color: #212529;\n background-color: #e2e6ea;\n border-color: #dae0e5;\n}\n\n.btn-light:focus, .btn-light.focus {\n box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5);\n}\n\n.btn-light.disabled, .btn-light:disabled {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-light:not(:disabled):not(.disabled):active, .btn-light:not(:disabled):not(.disabled).active,\n.show > .btn-light.dropdown-toggle {\n color: #212529;\n background-color: #dae0e5;\n border-color: #d3d9df;\n}\n\n.btn-light:not(:disabled):not(.disabled):active:focus, .btn-light:not(:disabled):not(.disabled).active:focus,\n.show > .btn-light.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5);\n}\n\n.btn-dark {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-dark:hover {\n color: #fff;\n background-color: #23272b;\n border-color: #1d2124;\n}\n\n.btn-dark:focus, .btn-dark.focus {\n box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5);\n}\n\n.btn-dark.disabled, .btn-dark:disabled {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-dark:not(:disabled):not(.disabled):active, .btn-dark:not(:disabled):not(.disabled).active,\n.show > .btn-dark.dropdown-toggle {\n color: #fff;\n background-color: #1d2124;\n border-color: #171a1d;\n}\n\n.btn-dark:not(:disabled):not(.disabled):active:focus, .btn-dark:not(:disabled):not(.disabled).active:focus,\n.show > .btn-dark.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5);\n}\n\n.btn-outline-primary {\n color: #007bff;\n border-color: #007bff;\n}\n\n.btn-outline-primary:hover {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-outline-primary:focus, .btn-outline-primary.focus {\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);\n}\n\n.btn-outline-primary.disabled, .btn-outline-primary:disabled {\n color: #007bff;\n background-color: transparent;\n}\n\n.btn-outline-primary:not(:disabled):not(.disabled):active, .btn-outline-primary:not(:disabled):not(.disabled).active,\n.show > .btn-outline-primary.dropdown-toggle {\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.btn-outline-primary:not(:disabled):not(.disabled):active:focus, .btn-outline-primary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-primary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);\n}\n\n.btn-outline-secondary {\n color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-outline-secondary:hover {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-outline-secondary:focus, .btn-outline-secondary.focus {\n box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);\n}\n\n.btn-outline-secondary.disabled, .btn-outline-secondary:disabled {\n color: #6c757d;\n background-color: transparent;\n}\n\n.btn-outline-secondary:not(:disabled):not(.disabled):active, .btn-outline-secondary:not(:disabled):not(.disabled).active,\n.show > .btn-outline-secondary.dropdown-toggle {\n color: #fff;\n background-color: #6c757d;\n border-color: #6c757d;\n}\n\n.btn-outline-secondary:not(:disabled):not(.disabled):active:focus, .btn-outline-secondary:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-secondary.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);\n}\n\n.btn-outline-success {\n color: #28a745;\n border-color: #28a745;\n}\n\n.btn-outline-success:hover {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-outline-success:focus, .btn-outline-success.focus {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);\n}\n\n.btn-outline-success.disabled, .btn-outline-success:disabled {\n color: #28a745;\n background-color: transparent;\n}\n\n.btn-outline-success:not(:disabled):not(.disabled):active, .btn-outline-success:not(:disabled):not(.disabled).active,\n.show > .btn-outline-success.dropdown-toggle {\n color: #fff;\n background-color: #28a745;\n border-color: #28a745;\n}\n\n.btn-outline-success:not(:disabled):not(.disabled):active:focus, .btn-outline-success:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-success.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);\n}\n\n.btn-outline-info {\n color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-outline-info:hover {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-outline-info:focus, .btn-outline-info.focus {\n box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);\n}\n\n.btn-outline-info.disabled, .btn-outline-info:disabled {\n color: #17a2b8;\n background-color: transparent;\n}\n\n.btn-outline-info:not(:disabled):not(.disabled):active, .btn-outline-info:not(:disabled):not(.disabled).active,\n.show > .btn-outline-info.dropdown-toggle {\n color: #fff;\n background-color: #17a2b8;\n border-color: #17a2b8;\n}\n\n.btn-outline-info:not(:disabled):not(.disabled):active:focus, .btn-outline-info:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-info.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);\n}\n\n.btn-outline-warning {\n color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-outline-warning:hover {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-outline-warning:focus, .btn-outline-warning.focus {\n box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);\n}\n\n.btn-outline-warning.disabled, .btn-outline-warning:disabled {\n color: #ffc107;\n background-color: transparent;\n}\n\n.btn-outline-warning:not(:disabled):not(.disabled):active, .btn-outline-warning:not(:disabled):not(.disabled).active,\n.show > .btn-outline-warning.dropdown-toggle {\n color: #212529;\n background-color: #ffc107;\n border-color: #ffc107;\n}\n\n.btn-outline-warning:not(:disabled):not(.disabled):active:focus, .btn-outline-warning:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-warning.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);\n}\n\n.btn-outline-danger {\n color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-outline-danger:hover {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-outline-danger:focus, .btn-outline-danger.focus {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);\n}\n\n.btn-outline-danger.disabled, .btn-outline-danger:disabled {\n color: #dc3545;\n background-color: transparent;\n}\n\n.btn-outline-danger:not(:disabled):not(.disabled):active, .btn-outline-danger:not(:disabled):not(.disabled).active,\n.show > .btn-outline-danger.dropdown-toggle {\n color: #fff;\n background-color: #dc3545;\n border-color: #dc3545;\n}\n\n.btn-outline-danger:not(:disabled):not(.disabled):active:focus, .btn-outline-danger:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-danger.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);\n}\n\n.btn-outline-light {\n color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-outline-light:hover {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-outline-light:focus, .btn-outline-light.focus {\n box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);\n}\n\n.btn-outline-light.disabled, .btn-outline-light:disabled {\n color: #f8f9fa;\n background-color: transparent;\n}\n\n.btn-outline-light:not(:disabled):not(.disabled):active, .btn-outline-light:not(:disabled):not(.disabled).active,\n.show > .btn-outline-light.dropdown-toggle {\n color: #212529;\n background-color: #f8f9fa;\n border-color: #f8f9fa;\n}\n\n.btn-outline-light:not(:disabled):not(.disabled):active:focus, .btn-outline-light:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-light.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);\n}\n\n.btn-outline-dark {\n color: #343a40;\n border-color: #343a40;\n}\n\n.btn-outline-dark:hover {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-outline-dark:focus, .btn-outline-dark.focus {\n box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);\n}\n\n.btn-outline-dark.disabled, .btn-outline-dark:disabled {\n color: #343a40;\n background-color: transparent;\n}\n\n.btn-outline-dark:not(:disabled):not(.disabled):active, .btn-outline-dark:not(:disabled):not(.disabled).active,\n.show > .btn-outline-dark.dropdown-toggle {\n color: #fff;\n background-color: #343a40;\n border-color: #343a40;\n}\n\n.btn-outline-dark:not(:disabled):not(.disabled):active:focus, .btn-outline-dark:not(:disabled):not(.disabled).active:focus,\n.show > .btn-outline-dark.dropdown-toggle:focus {\n box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);\n}\n\n.btn-link {\n font-weight: 400;\n color: #007bff;\n text-decoration: none;\n}\n\n.btn-link:hover {\n color: #0056b3;\n text-decoration: underline;\n}\n\n.btn-link:focus, .btn-link.focus {\n text-decoration: underline;\n box-shadow: none;\n}\n\n.btn-link:disabled, .btn-link.disabled {\n color: #6c757d;\n pointer-events: none;\n}\n\n.btn-lg, .btn-group-lg > .btn {\n padding: 0.5rem 1rem;\n font-size: 1.25rem;\n line-height: 1.5;\n border-radius: 0.3rem;\n}\n\n.btn-sm, .btn-group-sm > .btn {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n border-radius: 0.2rem;\n}\n\n.btn-block {\n display: block;\n width: 100%;\n}\n\n.btn-block + .btn-block {\n margin-top: 0.5rem;\n}\n\ninput[type=\"submit\"].btn-block,\ninput[type=\"reset\"].btn-block,\ninput[type=\"button\"].btn-block {\n width: 100%;\n}\n\n.fade {\n transition: opacity 0.15s linear;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .fade {\n transition: none;\n }\n}\n\n.fade:not(.show) {\n opacity: 0;\n}\n\n.collapse:not(.show) {\n display: none;\n}\n\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n transition: height 0.35s ease;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .collapsing {\n transition: none;\n }\n}\n\n.dropup,\n.dropright,\n.dropdown,\n.dropleft {\n position: relative;\n}\n\n.dropdown-toggle {\n white-space: nowrap;\n}\n\n.dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0.3em solid;\n border-right: 0.3em solid transparent;\n border-bottom: 0;\n border-left: 0.3em solid transparent;\n}\n\n.dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: 1000;\n display: none;\n float: left;\n min-width: 10rem;\n padding: 0.5rem 0;\n margin: 0.125rem 0 0;\n font-size: 1rem;\n color: #212529;\n text-align: left;\n list-style: none;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-radius: 0.25rem;\n}\n\n.dropdown-menu-left {\n right: auto;\n left: 0;\n}\n\n.dropdown-menu-right {\n right: 0;\n left: auto;\n}\n\n@media (min-width: 576px) {\n .dropdown-menu-sm-left {\n right: auto;\n left: 0;\n }\n .dropdown-menu-sm-right {\n right: 0;\n left: auto;\n }\n}\n\n@media (min-width: 768px) {\n .dropdown-menu-md-left {\n right: auto;\n left: 0;\n }\n .dropdown-menu-md-right {\n right: 0;\n left: auto;\n }\n}\n\n@media (min-width: 992px) {\n .dropdown-menu-lg-left {\n right: auto;\n left: 0;\n }\n .dropdown-menu-lg-right {\n right: 0;\n left: auto;\n }\n}\n\n@media (min-width: 1200px) {\n .dropdown-menu-xl-left {\n right: auto;\n left: 0;\n }\n .dropdown-menu-xl-right {\n right: 0;\n left: auto;\n }\n}\n\n.dropup .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-top: 0;\n margin-bottom: 0.125rem;\n}\n\n.dropup .dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0;\n border-right: 0.3em solid transparent;\n border-bottom: 0.3em solid;\n border-left: 0.3em solid transparent;\n}\n\n.dropup .dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropright .dropdown-menu {\n top: 0;\n right: auto;\n left: 100%;\n margin-top: 0;\n margin-left: 0.125rem;\n}\n\n.dropright .dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0.3em solid transparent;\n border-right: 0;\n border-bottom: 0.3em solid transparent;\n border-left: 0.3em solid;\n}\n\n.dropright .dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropright .dropdown-toggle::after {\n vertical-align: 0;\n}\n\n.dropleft .dropdown-menu {\n top: 0;\n right: 100%;\n left: auto;\n margin-top: 0;\n margin-right: 0.125rem;\n}\n\n.dropleft .dropdown-toggle::after {\n display: inline-block;\n margin-left: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n}\n\n.dropleft .dropdown-toggle::after {\n display: none;\n}\n\n.dropleft .dropdown-toggle::before {\n display: inline-block;\n margin-right: 0.255em;\n vertical-align: 0.255em;\n content: \"\";\n border-top: 0.3em solid transparent;\n border-right: 0.3em solid;\n border-bottom: 0.3em solid transparent;\n}\n\n.dropleft .dropdown-toggle:empty::after {\n margin-left: 0;\n}\n\n.dropleft .dropdown-toggle::before {\n vertical-align: 0;\n}\n\n.dropdown-menu[x-placement^=\"top\"], .dropdown-menu[x-placement^=\"right\"], .dropdown-menu[x-placement^=\"bottom\"], .dropdown-menu[x-placement^=\"left\"] {\n right: auto;\n bottom: auto;\n}\n\n.dropdown-divider {\n height: 0;\n margin: 0.5rem 0;\n overflow: hidden;\n border-top: 1px solid #e9ecef;\n}\n\n.dropdown-item {\n display: block;\n width: 100%;\n padding: 0.25rem 1.5rem;\n clear: both;\n font-weight: 400;\n color: #212529;\n text-align: inherit;\n white-space: nowrap;\n background-color: transparent;\n border: 0;\n}\n\n.dropdown-item:hover, .dropdown-item:focus {\n color: #16181b;\n text-decoration: none;\n background-color: #f8f9fa;\n}\n\n.dropdown-item.active, .dropdown-item:active {\n color: #fff;\n text-decoration: none;\n background-color: #007bff;\n}\n\n.dropdown-item.disabled, .dropdown-item:disabled {\n color: #6c757d;\n pointer-events: none;\n background-color: transparent;\n}\n\n.dropdown-menu.show {\n display: block;\n}\n\n.dropdown-header {\n display: block;\n padding: 0.5rem 1.5rem;\n margin-bottom: 0;\n font-size: 0.875rem;\n color: #6c757d;\n white-space: nowrap;\n}\n\n.dropdown-item-text {\n display: block;\n padding: 0.25rem 1.5rem;\n color: #212529;\n}\n\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: inline-flex;\n vertical-align: middle;\n}\n\n.btn-group > .btn,\n.btn-group-vertical > .btn {\n position: relative;\n flex: 1 1 auto;\n}\n\n.btn-group > .btn:hover,\n.btn-group-vertical > .btn:hover {\n z-index: 1;\n}\n\n.btn-group > .btn:focus, .btn-group > .btn:active, .btn-group > .btn.active,\n.btn-group-vertical > .btn:focus,\n.btn-group-vertical > .btn:active,\n.btn-group-vertical > .btn.active {\n z-index: 1;\n}\n\n.btn-toolbar {\n display: flex;\n flex-wrap: wrap;\n justify-content: flex-start;\n}\n\n.btn-toolbar .input-group {\n width: auto;\n}\n\n.btn-group > .btn:not(:first-child),\n.btn-group > .btn-group:not(:first-child) {\n margin-left: -1px;\n}\n\n.btn-group > .btn:not(:last-child):not(.dropdown-toggle),\n.btn-group > .btn-group:not(:last-child) > .btn {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.btn-group > .btn:not(:first-child),\n.btn-group > .btn-group:not(:first-child) > .btn {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.dropdown-toggle-split {\n padding-right: 0.5625rem;\n padding-left: 0.5625rem;\n}\n\n.dropdown-toggle-split::after,\n.dropup .dropdown-toggle-split::after,\n.dropright .dropdown-toggle-split::after {\n margin-left: 0;\n}\n\n.dropleft .dropdown-toggle-split::before {\n margin-right: 0;\n}\n\n.btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split {\n padding-right: 0.375rem;\n padding-left: 0.375rem;\n}\n\n.btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split {\n padding-right: 0.75rem;\n padding-left: 0.75rem;\n}\n\n.btn-group-vertical {\n flex-direction: column;\n align-items: flex-start;\n justify-content: center;\n}\n\n.btn-group-vertical > .btn,\n.btn-group-vertical > .btn-group {\n width: 100%;\n}\n\n.btn-group-vertical > .btn:not(:first-child),\n.btn-group-vertical > .btn-group:not(:first-child) {\n margin-top: -1px;\n}\n\n.btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle),\n.btn-group-vertical > .btn-group:not(:last-child) > .btn {\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.btn-group-vertical > .btn:not(:first-child),\n.btn-group-vertical > .btn-group:not(:first-child) > .btn {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n.btn-group-toggle > .btn,\n.btn-group-toggle > .btn-group > .btn {\n margin-bottom: 0;\n}\n\n.btn-group-toggle > .btn input[type=\"radio\"],\n.btn-group-toggle > .btn input[type=\"checkbox\"],\n.btn-group-toggle > .btn-group > .btn input[type=\"radio\"],\n.btn-group-toggle > .btn-group > .btn input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0, 0, 0, 0);\n pointer-events: none;\n}\n\n.input-group {\n position: relative;\n display: flex;\n flex-wrap: wrap;\n align-items: stretch;\n width: 100%;\n}\n\n.input-group > .form-control,\n.input-group > .form-control-plaintext,\n.input-group > .custom-select,\n.input-group > .custom-file {\n position: relative;\n flex: 1 1 auto;\n width: 1%;\n margin-bottom: 0;\n}\n\n.input-group > .form-control + .form-control,\n.input-group > .form-control + .custom-select,\n.input-group > .form-control + .custom-file,\n.input-group > .form-control-plaintext + .form-control,\n.input-group > .form-control-plaintext + .custom-select,\n.input-group > .form-control-plaintext + .custom-file,\n.input-group > .custom-select + .form-control,\n.input-group > .custom-select + .custom-select,\n.input-group > .custom-select + .custom-file,\n.input-group > .custom-file + .form-control,\n.input-group > .custom-file + .custom-select,\n.input-group > .custom-file + .custom-file {\n margin-left: -1px;\n}\n\n.input-group > .form-control:focus,\n.input-group > .custom-select:focus,\n.input-group > .custom-file .custom-file-input:focus ~ .custom-file-label {\n z-index: 3;\n}\n\n.input-group > .custom-file .custom-file-input:focus {\n z-index: 4;\n}\n\n.input-group > .form-control:not(:last-child),\n.input-group > .custom-select:not(:last-child) {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.input-group > .form-control:not(:first-child),\n.input-group > .custom-select:not(:first-child) {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.input-group > .custom-file {\n display: flex;\n align-items: center;\n}\n\n.input-group > .custom-file:not(:last-child) .custom-file-label,\n.input-group > .custom-file:not(:last-child) .custom-file-label::after {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.input-group > .custom-file:not(:first-child) .custom-file-label {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.input-group-prepend,\n.input-group-append {\n display: flex;\n}\n\n.input-group-prepend .btn,\n.input-group-append .btn {\n position: relative;\n z-index: 2;\n}\n\n.input-group-prepend .btn:focus,\n.input-group-append .btn:focus {\n z-index: 3;\n}\n\n.input-group-prepend .btn + .btn,\n.input-group-prepend .btn + .input-group-text,\n.input-group-prepend .input-group-text + .input-group-text,\n.input-group-prepend .input-group-text + .btn,\n.input-group-append .btn + .btn,\n.input-group-append .btn + .input-group-text,\n.input-group-append .input-group-text + .input-group-text,\n.input-group-append .input-group-text + .btn {\n margin-left: -1px;\n}\n\n.input-group-prepend {\n margin-right: -1px;\n}\n\n.input-group-append {\n margin-left: -1px;\n}\n\n.input-group-text {\n display: flex;\n align-items: center;\n padding: 0.375rem 0.75rem;\n margin-bottom: 0;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: #495057;\n text-align: center;\n white-space: nowrap;\n background-color: #e9ecef;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n}\n\n.input-group-text input[type=\"radio\"],\n.input-group-text input[type=\"checkbox\"] {\n margin-top: 0;\n}\n\n.input-group-lg > .form-control:not(textarea),\n.input-group-lg > .custom-select {\n height: calc(1.5em + 1rem + 2px);\n}\n\n.input-group-lg > .form-control,\n.input-group-lg > .custom-select,\n.input-group-lg > .input-group-prepend > .input-group-text,\n.input-group-lg > .input-group-append > .input-group-text,\n.input-group-lg > .input-group-prepend > .btn,\n.input-group-lg > .input-group-append > .btn {\n padding: 0.5rem 1rem;\n font-size: 1.25rem;\n line-height: 1.5;\n border-radius: 0.3rem;\n}\n\n.input-group-sm > .form-control:not(textarea),\n.input-group-sm > .custom-select {\n height: calc(1.5em + 0.5rem + 2px);\n}\n\n.input-group-sm > .form-control,\n.input-group-sm > .custom-select,\n.input-group-sm > .input-group-prepend > .input-group-text,\n.input-group-sm > .input-group-append > .input-group-text,\n.input-group-sm > .input-group-prepend > .btn,\n.input-group-sm > .input-group-append > .btn {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n border-radius: 0.2rem;\n}\n\n.input-group-lg > .custom-select,\n.input-group-sm > .custom-select {\n padding-right: 1.75rem;\n}\n\n.input-group > .input-group-prepend > .btn,\n.input-group > .input-group-prepend > .input-group-text,\n.input-group > .input-group-append:not(:last-child) > .btn,\n.input-group > .input-group-append:not(:last-child) > .input-group-text,\n.input-group > .input-group-append:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group > .input-group-append:last-child > .input-group-text:not(:last-child) {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.input-group > .input-group-append > .btn,\n.input-group > .input-group-append > .input-group-text,\n.input-group > .input-group-prepend:not(:first-child) > .btn,\n.input-group > .input-group-prepend:not(:first-child) > .input-group-text,\n.input-group > .input-group-prepend:first-child > .btn:not(:first-child),\n.input-group > .input-group-prepend:first-child > .input-group-text:not(:first-child) {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.custom-control {\n position: relative;\n display: block;\n min-height: 1.5rem;\n padding-left: 1.5rem;\n}\n\n.custom-control-inline {\n display: inline-flex;\n margin-right: 1rem;\n}\n\n.custom-control-input {\n position: absolute;\n z-index: -1;\n opacity: 0;\n}\n\n.custom-control-input:checked ~ .custom-control-label::before {\n color: #fff;\n border-color: #007bff;\n background-color: #007bff;\n}\n\n.custom-control-input:focus ~ .custom-control-label::before {\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-control-input:focus:not(:checked) ~ .custom-control-label::before {\n border-color: #80bdff;\n}\n\n.custom-control-input:not(:disabled):active ~ .custom-control-label::before {\n color: #fff;\n background-color: #b3d7ff;\n border-color: #b3d7ff;\n}\n\n.custom-control-input:disabled ~ .custom-control-label {\n color: #6c757d;\n}\n\n.custom-control-input:disabled ~ .custom-control-label::before {\n background-color: #e9ecef;\n}\n\n.custom-control-label {\n position: relative;\n margin-bottom: 0;\n vertical-align: top;\n}\n\n.custom-control-label::before {\n position: absolute;\n top: 0.25rem;\n left: -1.5rem;\n display: block;\n width: 1rem;\n height: 1rem;\n pointer-events: none;\n content: \"\";\n background-color: #fff;\n border: #adb5bd solid 1px;\n}\n\n.custom-control-label::after {\n position: absolute;\n top: 0.25rem;\n left: -1.5rem;\n display: block;\n width: 1rem;\n height: 1rem;\n content: \"\";\n background: no-repeat 50% / 50% 50%;\n}\n\n.custom-checkbox .custom-control-label::before {\n border-radius: 0.25rem;\n}\n\n.custom-checkbox .custom-control-input:checked ~ .custom-control-label::after {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26 2.974 7.25 8 2.193z'/%3e%3c/svg%3e\");\n}\n\n.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::before {\n border-color: #007bff;\n background-color: #007bff;\n}\n\n.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::after {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e\");\n}\n\n.custom-checkbox .custom-control-input:disabled:checked ~ .custom-control-label::before {\n background-color: rgba(0, 123, 255, 0.5);\n}\n\n.custom-checkbox .custom-control-input:disabled:indeterminate ~ .custom-control-label::before {\n background-color: rgba(0, 123, 255, 0.5);\n}\n\n.custom-radio .custom-control-label::before {\n border-radius: 50%;\n}\n\n.custom-radio .custom-control-input:checked ~ .custom-control-label::after {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e\");\n}\n\n.custom-radio .custom-control-input:disabled:checked ~ .custom-control-label::before {\n background-color: rgba(0, 123, 255, 0.5);\n}\n\n.custom-switch {\n padding-left: 2.25rem;\n}\n\n.custom-switch .custom-control-label::before {\n left: -2.25rem;\n width: 1.75rem;\n pointer-events: all;\n border-radius: 0.5rem;\n}\n\n.custom-switch .custom-control-label::after {\n top: calc(0.25rem + 2px);\n left: calc(-2.25rem + 2px);\n width: calc(1rem - 4px);\n height: calc(1rem - 4px);\n background-color: #adb5bd;\n border-radius: 0.5rem;\n transition: transform 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .custom-switch .custom-control-label::after {\n transition: none;\n }\n}\n\n.custom-switch .custom-control-input:checked ~ .custom-control-label::after {\n background-color: #fff;\n transform: translateX(0.75rem);\n}\n\n.custom-switch .custom-control-input:disabled:checked ~ .custom-control-label::before {\n background-color: rgba(0, 123, 255, 0.5);\n}\n\n.custom-select {\n display: inline-block;\n width: 100%;\n height: calc(1.5em + 0.75rem + 2px);\n padding: 0.375rem 1.75rem 0.375rem 0.75rem;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n color: #495057;\n vertical-align: middle;\n background: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e\") no-repeat right 0.75rem center/8px 10px;\n background-color: #fff;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n appearance: none;\n}\n\n.custom-select:focus {\n border-color: #80bdff;\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-select:focus::-ms-value {\n color: #495057;\n background-color: #fff;\n}\n\n.custom-select[multiple], .custom-select[size]:not([size=\"1\"]) {\n height: auto;\n padding-right: 0.75rem;\n background-image: none;\n}\n\n.custom-select:disabled {\n color: #6c757d;\n background-color: #e9ecef;\n}\n\n.custom-select::-ms-expand {\n display: none;\n}\n\n.custom-select-sm {\n height: calc(1.5em + 0.5rem + 2px);\n padding-top: 0.25rem;\n padding-bottom: 0.25rem;\n padding-left: 0.5rem;\n font-size: 0.875rem;\n}\n\n.custom-select-lg {\n height: calc(1.5em + 1rem + 2px);\n padding-top: 0.5rem;\n padding-bottom: 0.5rem;\n padding-left: 1rem;\n font-size: 1.25rem;\n}\n\n.custom-file {\n position: relative;\n display: inline-block;\n width: 100%;\n height: calc(1.5em + 0.75rem + 2px);\n margin-bottom: 0;\n}\n\n.custom-file-input {\n position: relative;\n z-index: 2;\n width: 100%;\n height: calc(1.5em + 0.75rem + 2px);\n margin: 0;\n opacity: 0;\n}\n\n.custom-file-input:focus ~ .custom-file-label {\n border-color: #80bdff;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-file-input:disabled ~ .custom-file-label {\n background-color: #e9ecef;\n}\n\n.custom-file-input:lang(en) ~ .custom-file-label::after {\n content: \"Browse\";\n}\n\n.custom-file-input ~ .custom-file-label[data-browse]::after {\n content: attr(data-browse);\n}\n\n.custom-file-label {\n position: absolute;\n top: 0;\n right: 0;\n left: 0;\n z-index: 1;\n height: calc(1.5em + 0.75rem + 2px);\n padding: 0.375rem 0.75rem;\n font-weight: 400;\n line-height: 1.5;\n color: #495057;\n background-color: #fff;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n}\n\n.custom-file-label::after {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n z-index: 3;\n display: block;\n height: calc(1.5em + 0.75rem);\n padding: 0.375rem 0.75rem;\n line-height: 1.5;\n color: #495057;\n content: \"Browse\";\n background-color: #e9ecef;\n border-left: inherit;\n border-radius: 0 0.25rem 0.25rem 0;\n}\n\n.custom-range {\n width: 100%;\n height: calc(1rem + 0.4rem);\n padding: 0;\n background-color: transparent;\n appearance: none;\n}\n\n.custom-range:focus {\n outline: none;\n}\n\n.custom-range:focus::-webkit-slider-thumb {\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-range:focus::-moz-range-thumb {\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-range:focus::-ms-thumb {\n box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.custom-range::-moz-focus-outer {\n border: 0;\n}\n\n.custom-range::-webkit-slider-thumb {\n width: 1rem;\n height: 1rem;\n margin-top: -0.25rem;\n background-color: #007bff;\n border: 0;\n border-radius: 1rem;\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n appearance: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .custom-range::-webkit-slider-thumb {\n transition: none;\n }\n}\n\n.custom-range::-webkit-slider-thumb:active {\n background-color: #b3d7ff;\n}\n\n.custom-range::-webkit-slider-runnable-track {\n width: 100%;\n height: 0.5rem;\n color: transparent;\n cursor: pointer;\n background-color: #dee2e6;\n border-color: transparent;\n border-radius: 1rem;\n}\n\n.custom-range::-moz-range-thumb {\n width: 1rem;\n height: 1rem;\n background-color: #007bff;\n border: 0;\n border-radius: 1rem;\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n appearance: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .custom-range::-moz-range-thumb {\n transition: none;\n }\n}\n\n.custom-range::-moz-range-thumb:active {\n background-color: #b3d7ff;\n}\n\n.custom-range::-moz-range-track {\n width: 100%;\n height: 0.5rem;\n color: transparent;\n cursor: pointer;\n background-color: #dee2e6;\n border-color: transparent;\n border-radius: 1rem;\n}\n\n.custom-range::-ms-thumb {\n width: 1rem;\n height: 1rem;\n margin-top: 0;\n margin-right: 0.2rem;\n margin-left: 0.2rem;\n background-color: #007bff;\n border: 0;\n border-radius: 1rem;\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n appearance: none;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .custom-range::-ms-thumb {\n transition: none;\n }\n}\n\n.custom-range::-ms-thumb:active {\n background-color: #b3d7ff;\n}\n\n.custom-range::-ms-track {\n width: 100%;\n height: 0.5rem;\n color: transparent;\n cursor: pointer;\n background-color: transparent;\n border-color: transparent;\n border-width: 0.5rem;\n}\n\n.custom-range::-ms-fill-lower {\n background-color: #dee2e6;\n border-radius: 1rem;\n}\n\n.custom-range::-ms-fill-upper {\n margin-right: 15px;\n background-color: #dee2e6;\n border-radius: 1rem;\n}\n\n.custom-range:disabled::-webkit-slider-thumb {\n background-color: #adb5bd;\n}\n\n.custom-range:disabled::-webkit-slider-runnable-track {\n cursor: default;\n}\n\n.custom-range:disabled::-moz-range-thumb {\n background-color: #adb5bd;\n}\n\n.custom-range:disabled::-moz-range-track {\n cursor: default;\n}\n\n.custom-range:disabled::-ms-thumb {\n background-color: #adb5bd;\n}\n\n.custom-control-label::before,\n.custom-file-label,\n.custom-select {\n transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .custom-control-label::before,\n .custom-file-label,\n .custom-select {\n transition: none;\n }\n}\n\n.nav {\n display: flex;\n flex-wrap: wrap;\n padding-left: 0;\n margin-bottom: 0;\n list-style: none;\n}\n\n.nav-link {\n display: block;\n padding: 0.5rem 1rem;\n}\n\n.nav-link:hover, .nav-link:focus {\n text-decoration: none;\n}\n\n.nav-link.disabled {\n color: #6c757d;\n pointer-events: none;\n cursor: default;\n}\n\n.nav-tabs {\n border-bottom: 1px solid #dee2e6;\n}\n\n.nav-tabs .nav-item {\n margin-bottom: -1px;\n}\n\n.nav-tabs .nav-link {\n border: 1px solid transparent;\n border-top-left-radius: 0.25rem;\n border-top-right-radius: 0.25rem;\n}\n\n.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus {\n border-color: #e9ecef #e9ecef #dee2e6;\n}\n\n.nav-tabs .nav-link.disabled {\n color: #6c757d;\n background-color: transparent;\n border-color: transparent;\n}\n\n.nav-tabs .nav-link.active,\n.nav-tabs .nav-item.show .nav-link {\n color: #495057;\n background-color: #fff;\n border-color: #dee2e6 #dee2e6 #fff;\n}\n\n.nav-tabs .dropdown-menu {\n margin-top: -1px;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n.nav-pills .nav-link {\n border-radius: 0.25rem;\n}\n\n.nav-pills .nav-link.active,\n.nav-pills .show > .nav-link {\n color: #fff;\n background-color: #007bff;\n}\n\n.nav-fill .nav-item {\n flex: 1 1 auto;\n text-align: center;\n}\n\n.nav-justified .nav-item {\n flex-basis: 0;\n flex-grow: 1;\n text-align: center;\n}\n\n.tab-content > .tab-pane {\n display: none;\n}\n\n.tab-content > .active {\n display: block;\n}\n\n.navbar {\n position: relative;\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n padding: 0.5rem 1rem;\n}\n\n.navbar > .container,\n.navbar > .container-fluid {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: space-between;\n}\n\n.navbar-brand {\n display: inline-block;\n padding-top: 0.3125rem;\n padding-bottom: 0.3125rem;\n margin-right: 1rem;\n font-size: 1.25rem;\n line-height: inherit;\n white-space: nowrap;\n}\n\n.navbar-brand:hover, .navbar-brand:focus {\n text-decoration: none;\n}\n\n.navbar-nav {\n display: flex;\n flex-direction: column;\n padding-left: 0;\n margin-bottom: 0;\n list-style: none;\n}\n\n.navbar-nav .nav-link {\n padding-right: 0;\n padding-left: 0;\n}\n\n.navbar-nav .dropdown-menu {\n position: static;\n float: none;\n}\n\n.navbar-text {\n display: inline-block;\n padding-top: 0.5rem;\n padding-bottom: 0.5rem;\n}\n\n.navbar-collapse {\n flex-basis: 100%;\n flex-grow: 1;\n align-items: center;\n}\n\n.navbar-toggler {\n padding: 0.25rem 0.75rem;\n font-size: 1.25rem;\n line-height: 1;\n background-color: transparent;\n border: 1px solid transparent;\n border-radius: 0.25rem;\n}\n\n.navbar-toggler:hover, .navbar-toggler:focus {\n text-decoration: none;\n}\n\n.navbar-toggler-icon {\n display: inline-block;\n width: 1.5em;\n height: 1.5em;\n vertical-align: middle;\n content: \"\";\n background: no-repeat center center;\n background-size: 100% 100%;\n}\n\n@media (max-width: 575.98px) {\n .navbar-expand-sm > .container,\n .navbar-expand-sm > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 576px) {\n .navbar-expand-sm {\n flex-flow: row nowrap;\n justify-content: flex-start;\n }\n .navbar-expand-sm .navbar-nav {\n flex-direction: row;\n }\n .navbar-expand-sm .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-sm .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-sm > .container,\n .navbar-expand-sm > .container-fluid {\n flex-wrap: nowrap;\n }\n .navbar-expand-sm .navbar-collapse {\n display: flex !important;\n flex-basis: auto;\n }\n .navbar-expand-sm .navbar-toggler {\n display: none;\n }\n}\n\n@media (max-width: 767.98px) {\n .navbar-expand-md > .container,\n .navbar-expand-md > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 768px) {\n .navbar-expand-md {\n flex-flow: row nowrap;\n justify-content: flex-start;\n }\n .navbar-expand-md .navbar-nav {\n flex-direction: row;\n }\n .navbar-expand-md .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-md .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-md > .container,\n .navbar-expand-md > .container-fluid {\n flex-wrap: nowrap;\n }\n .navbar-expand-md .navbar-collapse {\n display: flex !important;\n flex-basis: auto;\n }\n .navbar-expand-md .navbar-toggler {\n display: none;\n }\n}\n\n@media (max-width: 991.98px) {\n .navbar-expand-lg > .container,\n .navbar-expand-lg > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 992px) {\n .navbar-expand-lg {\n flex-flow: row nowrap;\n justify-content: flex-start;\n }\n .navbar-expand-lg .navbar-nav {\n flex-direction: row;\n }\n .navbar-expand-lg .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-lg .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-lg > .container,\n .navbar-expand-lg > .container-fluid {\n flex-wrap: nowrap;\n }\n .navbar-expand-lg .navbar-collapse {\n display: flex !important;\n flex-basis: auto;\n }\n .navbar-expand-lg .navbar-toggler {\n display: none;\n }\n}\n\n@media (max-width: 1199.98px) {\n .navbar-expand-xl > .container,\n .navbar-expand-xl > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n }\n}\n\n@media (min-width: 1200px) {\n .navbar-expand-xl {\n flex-flow: row nowrap;\n justify-content: flex-start;\n }\n .navbar-expand-xl .navbar-nav {\n flex-direction: row;\n }\n .navbar-expand-xl .navbar-nav .dropdown-menu {\n position: absolute;\n }\n .navbar-expand-xl .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n }\n .navbar-expand-xl > .container,\n .navbar-expand-xl > .container-fluid {\n flex-wrap: nowrap;\n }\n .navbar-expand-xl .navbar-collapse {\n display: flex !important;\n flex-basis: auto;\n }\n .navbar-expand-xl .navbar-toggler {\n display: none;\n }\n}\n\n.navbar-expand {\n flex-flow: row nowrap;\n justify-content: flex-start;\n}\n\n.navbar-expand > .container,\n.navbar-expand > .container-fluid {\n padding-right: 0;\n padding-left: 0;\n}\n\n.navbar-expand .navbar-nav {\n flex-direction: row;\n}\n\n.navbar-expand .navbar-nav .dropdown-menu {\n position: absolute;\n}\n\n.navbar-expand .navbar-nav .nav-link {\n padding-right: 0.5rem;\n padding-left: 0.5rem;\n}\n\n.navbar-expand > .container,\n.navbar-expand > .container-fluid {\n flex-wrap: nowrap;\n}\n\n.navbar-expand .navbar-collapse {\n display: flex !important;\n flex-basis: auto;\n}\n\n.navbar-expand .navbar-toggler {\n display: none;\n}\n\n.navbar-light .navbar-brand {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-brand:hover, .navbar-light .navbar-brand:focus {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-nav .nav-link {\n color: rgba(0, 0, 0, 0.5);\n}\n\n.navbar-light .navbar-nav .nav-link:hover, .navbar-light .navbar-nav .nav-link:focus {\n color: rgba(0, 0, 0, 0.7);\n}\n\n.navbar-light .navbar-nav .nav-link.disabled {\n color: rgba(0, 0, 0, 0.3);\n}\n\n.navbar-light .navbar-nav .show > .nav-link,\n.navbar-light .navbar-nav .active > .nav-link,\n.navbar-light .navbar-nav .nav-link.show,\n.navbar-light .navbar-nav .nav-link.active {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-toggler {\n color: rgba(0, 0, 0, 0.5);\n border-color: rgba(0, 0, 0, 0.1);\n}\n\n.navbar-light .navbar-toggler-icon {\n background-image: url(\"data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(0, 0, 0, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e\");\n}\n\n.navbar-light .navbar-text {\n color: rgba(0, 0, 0, 0.5);\n}\n\n.navbar-light .navbar-text a {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-light .navbar-text a:hover, .navbar-light .navbar-text a:focus {\n color: rgba(0, 0, 0, 0.9);\n}\n\n.navbar-dark .navbar-brand {\n color: #fff;\n}\n\n.navbar-dark .navbar-brand:hover, .navbar-dark .navbar-brand:focus {\n color: #fff;\n}\n\n.navbar-dark .navbar-nav .nav-link {\n color: rgba(255, 255, 255, 0.5);\n}\n\n.navbar-dark .navbar-nav .nav-link:hover, .navbar-dark .navbar-nav .nav-link:focus {\n color: rgba(255, 255, 255, 0.75);\n}\n\n.navbar-dark .navbar-nav .nav-link.disabled {\n color: rgba(255, 255, 255, 0.25);\n}\n\n.navbar-dark .navbar-nav .show > .nav-link,\n.navbar-dark .navbar-nav .active > .nav-link,\n.navbar-dark .navbar-nav .nav-link.show,\n.navbar-dark .navbar-nav .nav-link.active {\n color: #fff;\n}\n\n.navbar-dark .navbar-toggler {\n color: rgba(255, 255, 255, 0.5);\n border-color: rgba(255, 255, 255, 0.1);\n}\n\n.navbar-dark .navbar-toggler-icon {\n background-image: url(\"data:image/svg+xml,%3csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3e%3cpath stroke='rgba(255, 255, 255, 0.5)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e\");\n}\n\n.navbar-dark .navbar-text {\n color: rgba(255, 255, 255, 0.5);\n}\n\n.navbar-dark .navbar-text a {\n color: #fff;\n}\n\n.navbar-dark .navbar-text a:hover, .navbar-dark .navbar-text a:focus {\n color: #fff;\n}\n\n.card {\n position: relative;\n display: flex;\n flex-direction: column;\n min-width: 0;\n word-wrap: break-word;\n background-color: #fff;\n background-clip: border-box;\n border: 1px solid rgba(0, 0, 0, 0.125);\n border-radius: 0.25rem;\n}\n\n.card > hr {\n margin-right: 0;\n margin-left: 0;\n}\n\n.card > .list-group:first-child .list-group-item:first-child {\n border-top-left-radius: 0.25rem;\n border-top-right-radius: 0.25rem;\n}\n\n.card > .list-group:last-child .list-group-item:last-child {\n border-bottom-right-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n}\n\n.card-body {\n flex: 1 1 auto;\n padding: 1.25rem;\n}\n\n.card-title {\n margin-bottom: 0.75rem;\n}\n\n.card-subtitle {\n margin-top: -0.375rem;\n margin-bottom: 0;\n}\n\n.card-text:last-child {\n margin-bottom: 0;\n}\n\n.card-link:hover {\n text-decoration: none;\n}\n\n.card-link + .card-link {\n margin-left: 1.25rem;\n}\n\n.card-header {\n padding: 0.75rem 1.25rem;\n margin-bottom: 0;\n background-color: rgba(0, 0, 0, 0.03);\n border-bottom: 1px solid rgba(0, 0, 0, 0.125);\n}\n\n.card-header:first-child {\n border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0;\n}\n\n.card-header + .list-group .list-group-item:first-child {\n border-top: 0;\n}\n\n.card-footer {\n padding: 0.75rem 1.25rem;\n background-color: rgba(0, 0, 0, 0.03);\n border-top: 1px solid rgba(0, 0, 0, 0.125);\n}\n\n.card-footer:last-child {\n border-radius: 0 0 calc(0.25rem - 1px) calc(0.25rem - 1px);\n}\n\n.card-header-tabs {\n margin-right: -0.625rem;\n margin-bottom: -0.75rem;\n margin-left: -0.625rem;\n border-bottom: 0;\n}\n\n.card-header-pills {\n margin-right: -0.625rem;\n margin-left: -0.625rem;\n}\n\n.card-img-overlay {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n padding: 1.25rem;\n}\n\n.card-img {\n width: 100%;\n border-radius: calc(0.25rem - 1px);\n}\n\n.card-img-top {\n width: 100%;\n border-top-left-radius: calc(0.25rem - 1px);\n border-top-right-radius: calc(0.25rem - 1px);\n}\n\n.card-img-bottom {\n width: 100%;\n border-bottom-right-radius: calc(0.25rem - 1px);\n border-bottom-left-radius: calc(0.25rem - 1px);\n}\n\n.card-deck {\n display: flex;\n flex-direction: column;\n}\n\n.card-deck .card {\n margin-bottom: 15px;\n}\n\n@media (min-width: 576px) {\n .card-deck {\n flex-flow: row wrap;\n margin-right: -15px;\n margin-left: -15px;\n }\n .card-deck .card {\n display: flex;\n flex: 1 0 0%;\n flex-direction: column;\n margin-right: 15px;\n margin-bottom: 0;\n margin-left: 15px;\n }\n}\n\n.card-group {\n display: flex;\n flex-direction: column;\n}\n\n.card-group > .card {\n margin-bottom: 15px;\n}\n\n@media (min-width: 576px) {\n .card-group {\n flex-flow: row wrap;\n }\n .card-group > .card {\n flex: 1 0 0%;\n margin-bottom: 0;\n }\n .card-group > .card + .card {\n margin-left: 0;\n border-left: 0;\n }\n .card-group > .card:not(:last-child) {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n }\n .card-group > .card:not(:last-child) .card-img-top,\n .card-group > .card:not(:last-child) .card-header {\n border-top-right-radius: 0;\n }\n .card-group > .card:not(:last-child) .card-img-bottom,\n .card-group > .card:not(:last-child) .card-footer {\n border-bottom-right-radius: 0;\n }\n .card-group > .card:not(:first-child) {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n }\n .card-group > .card:not(:first-child) .card-img-top,\n .card-group > .card:not(:first-child) .card-header {\n border-top-left-radius: 0;\n }\n .card-group > .card:not(:first-child) .card-img-bottom,\n .card-group > .card:not(:first-child) .card-footer {\n border-bottom-left-radius: 0;\n }\n}\n\n.card-columns .card {\n margin-bottom: 0.75rem;\n}\n\n@media (min-width: 576px) {\n .card-columns {\n column-count: 3;\n column-gap: 1.25rem;\n orphans: 1;\n widows: 1;\n }\n .card-columns .card {\n display: inline-block;\n width: 100%;\n }\n}\n\n.accordion > .card {\n overflow: hidden;\n}\n\n.accordion > .card:not(:first-of-type) .card-header:first-child {\n border-radius: 0;\n}\n\n.accordion > .card:not(:first-of-type):not(:last-of-type) {\n border-bottom: 0;\n border-radius: 0;\n}\n\n.accordion > .card:first-of-type {\n border-bottom: 0;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.accordion > .card:last-of-type {\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n\n.accordion > .card .card-header {\n margin-bottom: -1px;\n}\n\n.breadcrumb {\n display: flex;\n flex-wrap: wrap;\n padding: 0.75rem 1rem;\n margin-bottom: 1rem;\n list-style: none;\n background-color: #e9ecef;\n border-radius: 0.25rem;\n}\n\n.breadcrumb-item + .breadcrumb-item {\n padding-left: 0.5rem;\n}\n\n.breadcrumb-item + .breadcrumb-item::before {\n display: inline-block;\n padding-right: 0.5rem;\n color: #6c757d;\n content: \"/\";\n}\n\n.breadcrumb-item + .breadcrumb-item:hover::before {\n text-decoration: underline;\n}\n\n.breadcrumb-item + .breadcrumb-item:hover::before {\n text-decoration: none;\n}\n\n.breadcrumb-item.active {\n color: #6c757d;\n}\n\n.pagination {\n display: flex;\n padding-left: 0;\n list-style: none;\n border-radius: 0.25rem;\n}\n\n.page-link {\n position: relative;\n display: block;\n padding: 0.5rem 0.75rem;\n margin-left: -1px;\n line-height: 1.25;\n color: #007bff;\n background-color: #fff;\n border: 1px solid #dee2e6;\n}\n\n.page-link:hover {\n z-index: 2;\n color: #0056b3;\n text-decoration: none;\n background-color: #e9ecef;\n border-color: #dee2e6;\n}\n\n.page-link:focus {\n z-index: 2;\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25);\n}\n\n.page-item:first-child .page-link {\n margin-left: 0;\n border-top-left-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n}\n\n.page-item:last-child .page-link {\n border-top-right-radius: 0.25rem;\n border-bottom-right-radius: 0.25rem;\n}\n\n.page-item.active .page-link {\n z-index: 1;\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.page-item.disabled .page-link {\n color: #6c757d;\n pointer-events: none;\n cursor: auto;\n background-color: #fff;\n border-color: #dee2e6;\n}\n\n.pagination-lg .page-link {\n padding: 0.75rem 1.5rem;\n font-size: 1.25rem;\n line-height: 1.5;\n}\n\n.pagination-lg .page-item:first-child .page-link {\n border-top-left-radius: 0.3rem;\n border-bottom-left-radius: 0.3rem;\n}\n\n.pagination-lg .page-item:last-child .page-link {\n border-top-right-radius: 0.3rem;\n border-bottom-right-radius: 0.3rem;\n}\n\n.pagination-sm .page-link {\n padding: 0.25rem 0.5rem;\n font-size: 0.875rem;\n line-height: 1.5;\n}\n\n.pagination-sm .page-item:first-child .page-link {\n border-top-left-radius: 0.2rem;\n border-bottom-left-radius: 0.2rem;\n}\n\n.pagination-sm .page-item:last-child .page-link {\n border-top-right-radius: 0.2rem;\n border-bottom-right-radius: 0.2rem;\n}\n\n.badge {\n display: inline-block;\n padding: 0.25em 0.4em;\n font-size: 75%;\n font-weight: 700;\n line-height: 1;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: 0.25rem;\n transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .badge {\n transition: none;\n }\n}\n\na.badge:hover, a.badge:focus {\n text-decoration: none;\n}\n\n.badge:empty {\n display: none;\n}\n\n.btn .badge {\n position: relative;\n top: -1px;\n}\n\n.badge-pill {\n padding-right: 0.6em;\n padding-left: 0.6em;\n border-radius: 10rem;\n}\n\n.badge-primary {\n color: #fff;\n background-color: #007bff;\n}\n\na.badge-primary:hover, a.badge-primary:focus {\n color: #fff;\n background-color: #0062cc;\n}\n\na.badge-primary:focus, a.badge-primary.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5);\n}\n\n.badge-secondary {\n color: #fff;\n background-color: #6c757d;\n}\n\na.badge-secondary:hover, a.badge-secondary:focus {\n color: #fff;\n background-color: #545b62;\n}\n\na.badge-secondary:focus, a.badge-secondary.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5);\n}\n\n.badge-success {\n color: #fff;\n background-color: #28a745;\n}\n\na.badge-success:hover, a.badge-success:focus {\n color: #fff;\n background-color: #1e7e34;\n}\n\na.badge-success:focus, a.badge-success.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5);\n}\n\n.badge-info {\n color: #fff;\n background-color: #17a2b8;\n}\n\na.badge-info:hover, a.badge-info:focus {\n color: #fff;\n background-color: #117a8b;\n}\n\na.badge-info:focus, a.badge-info.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5);\n}\n\n.badge-warning {\n color: #212529;\n background-color: #ffc107;\n}\n\na.badge-warning:hover, a.badge-warning:focus {\n color: #212529;\n background-color: #d39e00;\n}\n\na.badge-warning:focus, a.badge-warning.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5);\n}\n\n.badge-danger {\n color: #fff;\n background-color: #dc3545;\n}\n\na.badge-danger:hover, a.badge-danger:focus {\n color: #fff;\n background-color: #bd2130;\n}\n\na.badge-danger:focus, a.badge-danger.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5);\n}\n\n.badge-light {\n color: #212529;\n background-color: #f8f9fa;\n}\n\na.badge-light:hover, a.badge-light:focus {\n color: #212529;\n background-color: #dae0e5;\n}\n\na.badge-light:focus, a.badge-light.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5);\n}\n\n.badge-dark {\n color: #fff;\n background-color: #343a40;\n}\n\na.badge-dark:hover, a.badge-dark:focus {\n color: #fff;\n background-color: #1d2124;\n}\n\na.badge-dark:focus, a.badge-dark.focus {\n outline: 0;\n box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5);\n}\n\n.jumbotron {\n padding: 2rem 1rem;\n margin-bottom: 2rem;\n background-color: #e9ecef;\n border-radius: 0.3rem;\n}\n\n@media (min-width: 576px) {\n .jumbotron {\n padding: 4rem 2rem;\n }\n}\n\n.jumbotron-fluid {\n padding-right: 0;\n padding-left: 0;\n border-radius: 0;\n}\n\n.alert {\n position: relative;\n padding: 0.75rem 1.25rem;\n margin-bottom: 1rem;\n border: 1px solid transparent;\n border-radius: 0.25rem;\n}\n\n.alert-heading {\n color: inherit;\n}\n\n.alert-link {\n font-weight: 700;\n}\n\n.alert-dismissible {\n padding-right: 4rem;\n}\n\n.alert-dismissible .close {\n position: absolute;\n top: 0;\n right: 0;\n padding: 0.75rem 1.25rem;\n color: inherit;\n}\n\n.alert-primary {\n color: #004085;\n background-color: #cce5ff;\n border-color: #b8daff;\n}\n\n.alert-primary hr {\n border-top-color: #9fcdff;\n}\n\n.alert-primary .alert-link {\n color: #002752;\n}\n\n.alert-secondary {\n color: #383d41;\n background-color: #e2e3e5;\n border-color: #d6d8db;\n}\n\n.alert-secondary hr {\n border-top-color: #c8cbcf;\n}\n\n.alert-secondary .alert-link {\n color: #202326;\n}\n\n.alert-success {\n color: #155724;\n background-color: #d4edda;\n border-color: #c3e6cb;\n}\n\n.alert-success hr {\n border-top-color: #b1dfbb;\n}\n\n.alert-success .alert-link {\n color: #0b2e13;\n}\n\n.alert-info {\n color: #0c5460;\n background-color: #d1ecf1;\n border-color: #bee5eb;\n}\n\n.alert-info hr {\n border-top-color: #abdde5;\n}\n\n.alert-info .alert-link {\n color: #062c33;\n}\n\n.alert-warning {\n color: #856404;\n background-color: #fff3cd;\n border-color: #ffeeba;\n}\n\n.alert-warning hr {\n border-top-color: #ffe8a1;\n}\n\n.alert-warning .alert-link {\n color: #533f03;\n}\n\n.alert-danger {\n color: #721c24;\n background-color: #f8d7da;\n border-color: #f5c6cb;\n}\n\n.alert-danger hr {\n border-top-color: #f1b0b7;\n}\n\n.alert-danger .alert-link {\n color: #491217;\n}\n\n.alert-light {\n color: #818182;\n background-color: #fefefe;\n border-color: #fdfdfe;\n}\n\n.alert-light hr {\n border-top-color: #ececf6;\n}\n\n.alert-light .alert-link {\n color: #686868;\n}\n\n.alert-dark {\n color: #1b1e21;\n background-color: #d6d8d9;\n border-color: #c6c8ca;\n}\n\n.alert-dark hr {\n border-top-color: #b9bbbe;\n}\n\n.alert-dark .alert-link {\n color: #040505;\n}\n\n@keyframes progress-bar-stripes {\n from {\n background-position: 1rem 0;\n }\n to {\n background-position: 0 0;\n }\n}\n\n.progress {\n display: flex;\n height: 1rem;\n overflow: hidden;\n font-size: 0.75rem;\n background-color: #e9ecef;\n border-radius: 0.25rem;\n}\n\n.progress-bar {\n display: flex;\n flex-direction: column;\n justify-content: center;\n color: #fff;\n text-align: center;\n white-space: nowrap;\n background-color: #007bff;\n transition: width 0.6s ease;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .progress-bar {\n transition: none;\n }\n}\n\n.progress-bar-striped {\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-size: 1rem 1rem;\n}\n\n.progress-bar-animated {\n animation: progress-bar-stripes 1s linear infinite;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .progress-bar-animated {\n animation: none;\n }\n}\n\n.media {\n display: flex;\n align-items: flex-start;\n}\n\n.media-body {\n flex: 1;\n}\n\n.list-group {\n display: flex;\n flex-direction: column;\n padding-left: 0;\n margin-bottom: 0;\n}\n\n.list-group-item-action {\n width: 100%;\n color: #495057;\n text-align: inherit;\n}\n\n.list-group-item-action:hover, .list-group-item-action:focus {\n z-index: 1;\n color: #495057;\n text-decoration: none;\n background-color: #f8f9fa;\n}\n\n.list-group-item-action:active {\n color: #212529;\n background-color: #e9ecef;\n}\n\n.list-group-item {\n position: relative;\n display: block;\n padding: 0.75rem 1.25rem;\n margin-bottom: -1px;\n background-color: #fff;\n border: 1px solid rgba(0, 0, 0, 0.125);\n}\n\n.list-group-item:first-child {\n border-top-left-radius: 0.25rem;\n border-top-right-radius: 0.25rem;\n}\n\n.list-group-item:last-child {\n margin-bottom: 0;\n border-bottom-right-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n}\n\n.list-group-item.disabled, .list-group-item:disabled {\n color: #6c757d;\n pointer-events: none;\n background-color: #fff;\n}\n\n.list-group-item.active {\n z-index: 2;\n color: #fff;\n background-color: #007bff;\n border-color: #007bff;\n}\n\n.list-group-horizontal {\n flex-direction: row;\n}\n\n.list-group-horizontal .list-group-item {\n margin-right: -1px;\n margin-bottom: 0;\n}\n\n.list-group-horizontal .list-group-item:first-child {\n border-top-left-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n border-top-right-radius: 0;\n}\n\n.list-group-horizontal .list-group-item:last-child {\n margin-right: 0;\n border-top-right-radius: 0.25rem;\n border-bottom-right-radius: 0.25rem;\n border-bottom-left-radius: 0;\n}\n\n@media (min-width: 576px) {\n .list-group-horizontal-sm {\n flex-direction: row;\n }\n .list-group-horizontal-sm .list-group-item {\n margin-right: -1px;\n margin-bottom: 0;\n }\n .list-group-horizontal-sm .list-group-item:first-child {\n border-top-left-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n border-top-right-radius: 0;\n }\n .list-group-horizontal-sm .list-group-item:last-child {\n margin-right: 0;\n border-top-right-radius: 0.25rem;\n border-bottom-right-radius: 0.25rem;\n border-bottom-left-radius: 0;\n }\n}\n\n@media (min-width: 768px) {\n .list-group-horizontal-md {\n flex-direction: row;\n }\n .list-group-horizontal-md .list-group-item {\n margin-right: -1px;\n margin-bottom: 0;\n }\n .list-group-horizontal-md .list-group-item:first-child {\n border-top-left-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n border-top-right-radius: 0;\n }\n .list-group-horizontal-md .list-group-item:last-child {\n margin-right: 0;\n border-top-right-radius: 0.25rem;\n border-bottom-right-radius: 0.25rem;\n border-bottom-left-radius: 0;\n }\n}\n\n@media (min-width: 992px) {\n .list-group-horizontal-lg {\n flex-direction: row;\n }\n .list-group-horizontal-lg .list-group-item {\n margin-right: -1px;\n margin-bottom: 0;\n }\n .list-group-horizontal-lg .list-group-item:first-child {\n border-top-left-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n border-top-right-radius: 0;\n }\n .list-group-horizontal-lg .list-group-item:last-child {\n margin-right: 0;\n border-top-right-radius: 0.25rem;\n border-bottom-right-radius: 0.25rem;\n border-bottom-left-radius: 0;\n }\n}\n\n@media (min-width: 1200px) {\n .list-group-horizontal-xl {\n flex-direction: row;\n }\n .list-group-horizontal-xl .list-group-item {\n margin-right: -1px;\n margin-bottom: 0;\n }\n .list-group-horizontal-xl .list-group-item:first-child {\n border-top-left-radius: 0.25rem;\n border-bottom-left-radius: 0.25rem;\n border-top-right-radius: 0;\n }\n .list-group-horizontal-xl .list-group-item:last-child {\n margin-right: 0;\n border-top-right-radius: 0.25rem;\n border-bottom-right-radius: 0.25rem;\n border-bottom-left-radius: 0;\n }\n}\n\n.list-group-flush .list-group-item {\n border-right: 0;\n border-left: 0;\n border-radius: 0;\n}\n\n.list-group-flush .list-group-item:last-child {\n margin-bottom: -1px;\n}\n\n.list-group-flush:first-child .list-group-item:first-child {\n border-top: 0;\n}\n\n.list-group-flush:last-child .list-group-item:last-child {\n margin-bottom: 0;\n border-bottom: 0;\n}\n\n.list-group-item-primary {\n color: #004085;\n background-color: #b8daff;\n}\n\n.list-group-item-primary.list-group-item-action:hover, .list-group-item-primary.list-group-item-action:focus {\n color: #004085;\n background-color: #9fcdff;\n}\n\n.list-group-item-primary.list-group-item-action.active {\n color: #fff;\n background-color: #004085;\n border-color: #004085;\n}\n\n.list-group-item-secondary {\n color: #383d41;\n background-color: #d6d8db;\n}\n\n.list-group-item-secondary.list-group-item-action:hover, .list-group-item-secondary.list-group-item-action:focus {\n color: #383d41;\n background-color: #c8cbcf;\n}\n\n.list-group-item-secondary.list-group-item-action.active {\n color: #fff;\n background-color: #383d41;\n border-color: #383d41;\n}\n\n.list-group-item-success {\n color: #155724;\n background-color: #c3e6cb;\n}\n\n.list-group-item-success.list-group-item-action:hover, .list-group-item-success.list-group-item-action:focus {\n color: #155724;\n background-color: #b1dfbb;\n}\n\n.list-group-item-success.list-group-item-action.active {\n color: #fff;\n background-color: #155724;\n border-color: #155724;\n}\n\n.list-group-item-info {\n color: #0c5460;\n background-color: #bee5eb;\n}\n\n.list-group-item-info.list-group-item-action:hover, .list-group-item-info.list-group-item-action:focus {\n color: #0c5460;\n background-color: #abdde5;\n}\n\n.list-group-item-info.list-group-item-action.active {\n color: #fff;\n background-color: #0c5460;\n border-color: #0c5460;\n}\n\n.list-group-item-warning {\n color: #856404;\n background-color: #ffeeba;\n}\n\n.list-group-item-warning.list-group-item-action:hover, .list-group-item-warning.list-group-item-action:focus {\n color: #856404;\n background-color: #ffe8a1;\n}\n\n.list-group-item-warning.list-group-item-action.active {\n color: #fff;\n background-color: #856404;\n border-color: #856404;\n}\n\n.list-group-item-danger {\n color: #721c24;\n background-color: #f5c6cb;\n}\n\n.list-group-item-danger.list-group-item-action:hover, .list-group-item-danger.list-group-item-action:focus {\n color: #721c24;\n background-color: #f1b0b7;\n}\n\n.list-group-item-danger.list-group-item-action.active {\n color: #fff;\n background-color: #721c24;\n border-color: #721c24;\n}\n\n.list-group-item-light {\n color: #818182;\n background-color: #fdfdfe;\n}\n\n.list-group-item-light.list-group-item-action:hover, .list-group-item-light.list-group-item-action:focus {\n color: #818182;\n background-color: #ececf6;\n}\n\n.list-group-item-light.list-group-item-action.active {\n color: #fff;\n background-color: #818182;\n border-color: #818182;\n}\n\n.list-group-item-dark {\n color: #1b1e21;\n background-color: #c6c8ca;\n}\n\n.list-group-item-dark.list-group-item-action:hover, .list-group-item-dark.list-group-item-action:focus {\n color: #1b1e21;\n background-color: #b9bbbe;\n}\n\n.list-group-item-dark.list-group-item-action.active {\n color: #fff;\n background-color: #1b1e21;\n border-color: #1b1e21;\n}\n\n.close {\n float: right;\n font-size: 1.5rem;\n font-weight: 700;\n line-height: 1;\n color: #000;\n text-shadow: 0 1px 0 #fff;\n opacity: .5;\n}\n\n.close:hover {\n color: #000;\n text-decoration: none;\n}\n\n.close:not(:disabled):not(.disabled):hover, .close:not(:disabled):not(.disabled):focus {\n opacity: .75;\n}\n\nbutton.close {\n padding: 0;\n background-color: transparent;\n border: 0;\n appearance: none;\n}\n\na.close.disabled {\n pointer-events: none;\n}\n\n.toast {\n max-width: 350px;\n overflow: hidden;\n font-size: 0.875rem;\n background-color: rgba(255, 255, 255, 0.85);\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.1);\n box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.1);\n backdrop-filter: blur(10px);\n opacity: 0;\n border-radius: 0.25rem;\n}\n\n.toast:not(:last-child) {\n margin-bottom: 0.75rem;\n}\n\n.toast.showing {\n opacity: 1;\n}\n\n.toast.show {\n display: block;\n opacity: 1;\n}\n\n.toast.hide {\n display: none;\n}\n\n.toast-header {\n display: flex;\n align-items: center;\n padding: 0.25rem 0.75rem;\n color: #6c757d;\n background-color: rgba(255, 255, 255, 0.85);\n background-clip: padding-box;\n border-bottom: 1px solid rgba(0, 0, 0, 0.05);\n}\n\n.toast-body {\n padding: 0.75rem;\n}\n\n.modal-open {\n overflow: hidden;\n}\n\n.modal-open .modal {\n overflow-x: hidden;\n overflow-y: auto;\n}\n\n.modal {\n position: fixed;\n top: 0;\n left: 0;\n z-index: 1050;\n display: none;\n width: 100%;\n height: 100%;\n overflow: hidden;\n outline: 0;\n}\n\n.modal-dialog {\n position: relative;\n width: auto;\n margin: 0.5rem;\n pointer-events: none;\n}\n\n.modal.fade .modal-dialog {\n transition: transform 0.3s ease-out;\n transform: translate(0, -50px);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .modal.fade .modal-dialog {\n transition: none;\n }\n}\n\n.modal.show .modal-dialog {\n transform: none;\n}\n\n.modal-dialog-scrollable {\n display: flex;\n max-height: calc(100% - 1rem);\n}\n\n.modal-dialog-scrollable .modal-content {\n max-height: calc(100vh - 1rem);\n overflow: hidden;\n}\n\n.modal-dialog-scrollable .modal-header,\n.modal-dialog-scrollable .modal-footer {\n flex-shrink: 0;\n}\n\n.modal-dialog-scrollable .modal-body {\n overflow-y: auto;\n}\n\n.modal-dialog-centered {\n display: flex;\n align-items: center;\n min-height: calc(100% - 1rem);\n}\n\n.modal-dialog-centered::before {\n display: block;\n height: calc(100vh - 1rem);\n content: \"\";\n}\n\n.modal-dialog-centered.modal-dialog-scrollable {\n flex-direction: column;\n justify-content: center;\n height: 100%;\n}\n\n.modal-dialog-centered.modal-dialog-scrollable .modal-content {\n max-height: none;\n}\n\n.modal-dialog-centered.modal-dialog-scrollable::before {\n content: none;\n}\n\n.modal-content {\n position: relative;\n display: flex;\n flex-direction: column;\n width: 100%;\n pointer-events: auto;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 0.3rem;\n outline: 0;\n}\n\n.modal-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n z-index: 1040;\n width: 100vw;\n height: 100vh;\n background-color: #000;\n}\n\n.modal-backdrop.fade {\n opacity: 0;\n}\n\n.modal-backdrop.show {\n opacity: 0.5;\n}\n\n.modal-header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n padding: 1rem 1rem;\n border-bottom: 1px solid #dee2e6;\n border-top-left-radius: 0.3rem;\n border-top-right-radius: 0.3rem;\n}\n\n.modal-header .close {\n padding: 1rem 1rem;\n margin: -1rem -1rem -1rem auto;\n}\n\n.modal-title {\n margin-bottom: 0;\n line-height: 1.5;\n}\n\n.modal-body {\n position: relative;\n flex: 1 1 auto;\n padding: 1rem;\n}\n\n.modal-footer {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n padding: 1rem;\n border-top: 1px solid #dee2e6;\n border-bottom-right-radius: 0.3rem;\n border-bottom-left-radius: 0.3rem;\n}\n\n.modal-footer > :not(:first-child) {\n margin-left: .25rem;\n}\n\n.modal-footer > :not(:last-child) {\n margin-right: .25rem;\n}\n\n.modal-scrollbar-measure {\n position: absolute;\n top: -9999px;\n width: 50px;\n height: 50px;\n overflow: scroll;\n}\n\n@media (min-width: 576px) {\n .modal-dialog {\n max-width: 500px;\n margin: 1.75rem auto;\n }\n .modal-dialog-scrollable {\n max-height: calc(100% - 3.5rem);\n }\n .modal-dialog-scrollable .modal-content {\n max-height: calc(100vh - 3.5rem);\n }\n .modal-dialog-centered {\n min-height: calc(100% - 3.5rem);\n }\n .modal-dialog-centered::before {\n height: calc(100vh - 3.5rem);\n }\n .modal-sm {\n max-width: 300px;\n }\n}\n\n@media (min-width: 992px) {\n .modal-lg,\n .modal-xl {\n max-width: 800px;\n }\n}\n\n@media (min-width: 1200px) {\n .modal-xl {\n max-width: 1140px;\n }\n}\n\n.tooltip {\n position: absolute;\n z-index: 1070;\n display: block;\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n font-style: normal;\n font-weight: 400;\n line-height: 1.5;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n letter-spacing: normal;\n word-break: normal;\n word-spacing: normal;\n white-space: normal;\n line-break: auto;\n font-size: 0.875rem;\n word-wrap: break-word;\n opacity: 0;\n}\n\n.tooltip.show {\n opacity: 0.9;\n}\n\n.tooltip .arrow {\n position: absolute;\n display: block;\n width: 0.8rem;\n height: 0.4rem;\n}\n\n.tooltip .arrow::before {\n position: absolute;\n content: \"\";\n border-color: transparent;\n border-style: solid;\n}\n\n.bs-tooltip-top, .bs-tooltip-auto[x-placement^=\"top\"] {\n padding: 0.4rem 0;\n}\n\n.bs-tooltip-top .arrow, .bs-tooltip-auto[x-placement^=\"top\"] .arrow {\n bottom: 0;\n}\n\n.bs-tooltip-top .arrow::before, .bs-tooltip-auto[x-placement^=\"top\"] .arrow::before {\n top: 0;\n border-width: 0.4rem 0.4rem 0;\n border-top-color: #000;\n}\n\n.bs-tooltip-right, .bs-tooltip-auto[x-placement^=\"right\"] {\n padding: 0 0.4rem;\n}\n\n.bs-tooltip-right .arrow, .bs-tooltip-auto[x-placement^=\"right\"] .arrow {\n left: 0;\n width: 0.4rem;\n height: 0.8rem;\n}\n\n.bs-tooltip-right .arrow::before, .bs-tooltip-auto[x-placement^=\"right\"] .arrow::before {\n right: 0;\n border-width: 0.4rem 0.4rem 0.4rem 0;\n border-right-color: #000;\n}\n\n.bs-tooltip-bottom, .bs-tooltip-auto[x-placement^=\"bottom\"] {\n padding: 0.4rem 0;\n}\n\n.bs-tooltip-bottom .arrow, .bs-tooltip-auto[x-placement^=\"bottom\"] .arrow {\n top: 0;\n}\n\n.bs-tooltip-bottom .arrow::before, .bs-tooltip-auto[x-placement^=\"bottom\"] .arrow::before {\n bottom: 0;\n border-width: 0 0.4rem 0.4rem;\n border-bottom-color: #000;\n}\n\n.bs-tooltip-left, .bs-tooltip-auto[x-placement^=\"left\"] {\n padding: 0 0.4rem;\n}\n\n.bs-tooltip-left .arrow, .bs-tooltip-auto[x-placement^=\"left\"] .arrow {\n right: 0;\n width: 0.4rem;\n height: 0.8rem;\n}\n\n.bs-tooltip-left .arrow::before, .bs-tooltip-auto[x-placement^=\"left\"] .arrow::before {\n left: 0;\n border-width: 0.4rem 0 0.4rem 0.4rem;\n border-left-color: #000;\n}\n\n.tooltip-inner {\n max-width: 200px;\n padding: 0.25rem 0.5rem;\n color: #fff;\n text-align: center;\n background-color: #000;\n border-radius: 0.25rem;\n}\n\n.popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 1060;\n display: block;\n max-width: 276px;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, \"Noto Sans\", sans-serif, \"Apple Color Emoji\", \"Segoe UI Emoji\", \"Segoe UI Symbol\", \"Noto Color Emoji\";\n font-style: normal;\n font-weight: 400;\n line-height: 1.5;\n text-align: left;\n text-align: start;\n text-decoration: none;\n text-shadow: none;\n text-transform: none;\n letter-spacing: normal;\n word-break: normal;\n word-spacing: normal;\n white-space: normal;\n line-break: auto;\n font-size: 0.875rem;\n word-wrap: break-word;\n background-color: #fff;\n background-clip: padding-box;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 0.3rem;\n}\n\n.popover .arrow {\n position: absolute;\n display: block;\n width: 1rem;\n height: 0.5rem;\n margin: 0 0.3rem;\n}\n\n.popover .arrow::before, .popover .arrow::after {\n position: absolute;\n display: block;\n content: \"\";\n border-color: transparent;\n border-style: solid;\n}\n\n.bs-popover-top, .bs-popover-auto[x-placement^=\"top\"] {\n margin-bottom: 0.5rem;\n}\n\n.bs-popover-top > .arrow, .bs-popover-auto[x-placement^=\"top\"] > .arrow {\n bottom: calc((0.5rem + 1px) * -1);\n}\n\n.bs-popover-top > .arrow::before, .bs-popover-auto[x-placement^=\"top\"] > .arrow::before {\n bottom: 0;\n border-width: 0.5rem 0.5rem 0;\n border-top-color: rgba(0, 0, 0, 0.25);\n}\n\n.bs-popover-top > .arrow::after, .bs-popover-auto[x-placement^=\"top\"] > .arrow::after {\n bottom: 1px;\n border-width: 0.5rem 0.5rem 0;\n border-top-color: #fff;\n}\n\n.bs-popover-right, .bs-popover-auto[x-placement^=\"right\"] {\n margin-left: 0.5rem;\n}\n\n.bs-popover-right > .arrow, .bs-popover-auto[x-placement^=\"right\"] > .arrow {\n left: calc((0.5rem + 1px) * -1);\n width: 0.5rem;\n height: 1rem;\n margin: 0.3rem 0;\n}\n\n.bs-popover-right > .arrow::before, .bs-popover-auto[x-placement^=\"right\"] > .arrow::before {\n left: 0;\n border-width: 0.5rem 0.5rem 0.5rem 0;\n border-right-color: rgba(0, 0, 0, 0.25);\n}\n\n.bs-popover-right > .arrow::after, .bs-popover-auto[x-placement^=\"right\"] > .arrow::after {\n left: 1px;\n border-width: 0.5rem 0.5rem 0.5rem 0;\n border-right-color: #fff;\n}\n\n.bs-popover-bottom, .bs-popover-auto[x-placement^=\"bottom\"] {\n margin-top: 0.5rem;\n}\n\n.bs-popover-bottom > .arrow, .bs-popover-auto[x-placement^=\"bottom\"] > .arrow {\n top: calc((0.5rem + 1px) * -1);\n}\n\n.bs-popover-bottom > .arrow::before, .bs-popover-auto[x-placement^=\"bottom\"] > .arrow::before {\n top: 0;\n border-width: 0 0.5rem 0.5rem 0.5rem;\n border-bottom-color: rgba(0, 0, 0, 0.25);\n}\n\n.bs-popover-bottom > .arrow::after, .bs-popover-auto[x-placement^=\"bottom\"] > .arrow::after {\n top: 1px;\n border-width: 0 0.5rem 0.5rem 0.5rem;\n border-bottom-color: #fff;\n}\n\n.bs-popover-bottom .popover-header::before, .bs-popover-auto[x-placement^=\"bottom\"] .popover-header::before {\n position: absolute;\n top: 0;\n left: 50%;\n display: block;\n width: 1rem;\n margin-left: -0.5rem;\n content: \"\";\n border-bottom: 1px solid #f7f7f7;\n}\n\n.bs-popover-left, .bs-popover-auto[x-placement^=\"left\"] {\n margin-right: 0.5rem;\n}\n\n.bs-popover-left > .arrow, .bs-popover-auto[x-placement^=\"left\"] > .arrow {\n right: calc((0.5rem + 1px) * -1);\n width: 0.5rem;\n height: 1rem;\n margin: 0.3rem 0;\n}\n\n.bs-popover-left > .arrow::before, .bs-popover-auto[x-placement^=\"left\"] > .arrow::before {\n right: 0;\n border-width: 0.5rem 0 0.5rem 0.5rem;\n border-left-color: rgba(0, 0, 0, 0.25);\n}\n\n.bs-popover-left > .arrow::after, .bs-popover-auto[x-placement^=\"left\"] > .arrow::after {\n right: 1px;\n border-width: 0.5rem 0 0.5rem 0.5rem;\n border-left-color: #fff;\n}\n\n.popover-header {\n padding: 0.5rem 0.75rem;\n margin-bottom: 0;\n font-size: 1rem;\n background-color: #f7f7f7;\n border-bottom: 1px solid #ebebeb;\n border-top-left-radius: calc(0.3rem - 1px);\n border-top-right-radius: calc(0.3rem - 1px);\n}\n\n.popover-header:empty {\n display: none;\n}\n\n.popover-body {\n padding: 0.5rem 0.75rem;\n color: #212529;\n}\n\n.carousel {\n position: relative;\n}\n\n.carousel.pointer-event {\n touch-action: pan-y;\n}\n\n.carousel-inner {\n position: relative;\n width: 100%;\n overflow: hidden;\n}\n\n.carousel-inner::after {\n display: block;\n clear: both;\n content: \"\";\n}\n\n.carousel-item {\n position: relative;\n display: none;\n float: left;\n width: 100%;\n margin-right: -100%;\n backface-visibility: hidden;\n transition: transform 0.6s ease-in-out;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .carousel-item {\n transition: none;\n }\n}\n\n.carousel-item.active,\n.carousel-item-next,\n.carousel-item-prev {\n display: block;\n}\n\n.carousel-item-next:not(.carousel-item-left),\n.active.carousel-item-right {\n transform: translateX(100%);\n}\n\n.carousel-item-prev:not(.carousel-item-right),\n.active.carousel-item-left {\n transform: translateX(-100%);\n}\n\n.carousel-fade .carousel-item {\n opacity: 0;\n transition-property: opacity;\n transform: none;\n}\n\n.carousel-fade .carousel-item.active,\n.carousel-fade .carousel-item-next.carousel-item-left,\n.carousel-fade .carousel-item-prev.carousel-item-right {\n z-index: 1;\n opacity: 1;\n}\n\n.carousel-fade .active.carousel-item-left,\n.carousel-fade .active.carousel-item-right {\n z-index: 0;\n opacity: 0;\n transition: 0s 0.6s opacity;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .carousel-fade .active.carousel-item-left,\n .carousel-fade .active.carousel-item-right {\n transition: none;\n }\n}\n\n.carousel-control-prev,\n.carousel-control-next {\n position: absolute;\n top: 0;\n bottom: 0;\n z-index: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 15%;\n color: #fff;\n text-align: center;\n opacity: 0.5;\n transition: opacity 0.15s ease;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .carousel-control-prev,\n .carousel-control-next {\n transition: none;\n }\n}\n\n.carousel-control-prev:hover, .carousel-control-prev:focus,\n.carousel-control-next:hover,\n.carousel-control-next:focus {\n color: #fff;\n text-decoration: none;\n outline: 0;\n opacity: 0.9;\n}\n\n.carousel-control-prev {\n left: 0;\n}\n\n.carousel-control-next {\n right: 0;\n}\n\n.carousel-control-prev-icon,\n.carousel-control-next-icon {\n display: inline-block;\n width: 20px;\n height: 20px;\n background: no-repeat 50% / 100% 100%;\n}\n\n.carousel-control-prev-icon {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5-2.5-2.5 2.5-2.5-1.5-1.5z'/%3e%3c/svg%3e\");\n}\n\n.carousel-control-next-icon {\n background-image: url(\"data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5 2.5 2.5-2.5 2.5 1.5 1.5 4-4-4-4z'/%3e%3c/svg%3e\");\n}\n\n.carousel-indicators {\n position: absolute;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 15;\n display: flex;\n justify-content: center;\n padding-left: 0;\n margin-right: 15%;\n margin-left: 15%;\n list-style: none;\n}\n\n.carousel-indicators li {\n box-sizing: content-box;\n flex: 0 1 auto;\n width: 30px;\n height: 3px;\n margin-right: 3px;\n margin-left: 3px;\n text-indent: -999px;\n cursor: pointer;\n background-color: #fff;\n background-clip: padding-box;\n border-top: 10px solid transparent;\n border-bottom: 10px solid transparent;\n opacity: .5;\n transition: opacity 0.6s ease;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .carousel-indicators li {\n transition: none;\n }\n}\n\n.carousel-indicators .active {\n opacity: 1;\n}\n\n.carousel-caption {\n position: absolute;\n right: 15%;\n bottom: 20px;\n left: 15%;\n z-index: 10;\n padding-top: 20px;\n padding-bottom: 20px;\n color: #fff;\n text-align: center;\n}\n\n@keyframes spinner-border {\n to {\n transform: rotate(360deg);\n }\n}\n\n.spinner-border {\n display: inline-block;\n width: 2rem;\n height: 2rem;\n vertical-align: text-bottom;\n border: 0.25em solid currentColor;\n border-right-color: transparent;\n border-radius: 50%;\n animation: spinner-border .75s linear infinite;\n}\n\n.spinner-border-sm {\n width: 1rem;\n height: 1rem;\n border-width: 0.2em;\n}\n\n@keyframes spinner-grow {\n 0% {\n transform: scale(0);\n }\n 50% {\n opacity: 1;\n }\n}\n\n.spinner-grow {\n display: inline-block;\n width: 2rem;\n height: 2rem;\n vertical-align: text-bottom;\n background-color: currentColor;\n border-radius: 50%;\n opacity: 0;\n animation: spinner-grow .75s linear infinite;\n}\n\n.spinner-grow-sm {\n width: 1rem;\n height: 1rem;\n}\n\n.align-baseline {\n vertical-align: baseline !important;\n}\n\n.align-top {\n vertical-align: top !important;\n}\n\n.align-middle {\n vertical-align: middle !important;\n}\n\n.align-bottom {\n vertical-align: bottom !important;\n}\n\n.align-text-bottom {\n vertical-align: text-bottom !important;\n}\n\n.align-text-top {\n vertical-align: text-top !important;\n}\n\n.bg-primary {\n background-color: #007bff !important;\n}\n\na.bg-primary:hover, a.bg-primary:focus,\nbutton.bg-primary:hover,\nbutton.bg-primary:focus {\n background-color: #0062cc !important;\n}\n\n.bg-secondary {\n background-color: #6c757d !important;\n}\n\na.bg-secondary:hover, a.bg-secondary:focus,\nbutton.bg-secondary:hover,\nbutton.bg-secondary:focus {\n background-color: #545b62 !important;\n}\n\n.bg-success {\n background-color: #28a745 !important;\n}\n\na.bg-success:hover, a.bg-success:focus,\nbutton.bg-success:hover,\nbutton.bg-success:focus {\n background-color: #1e7e34 !important;\n}\n\n.bg-info {\n background-color: #17a2b8 !important;\n}\n\na.bg-info:hover, a.bg-info:focus,\nbutton.bg-info:hover,\nbutton.bg-info:focus {\n background-color: #117a8b !important;\n}\n\n.bg-warning {\n background-color: #ffc107 !important;\n}\n\na.bg-warning:hover, a.bg-warning:focus,\nbutton.bg-warning:hover,\nbutton.bg-warning:focus {\n background-color: #d39e00 !important;\n}\n\n.bg-danger {\n background-color: #dc3545 !important;\n}\n\na.bg-danger:hover, a.bg-danger:focus,\nbutton.bg-danger:hover,\nbutton.bg-danger:focus {\n background-color: #bd2130 !important;\n}\n\n.bg-light {\n background-color: #f8f9fa !important;\n}\n\na.bg-light:hover, a.bg-light:focus,\nbutton.bg-light:hover,\nbutton.bg-light:focus {\n background-color: #dae0e5 !important;\n}\n\n.bg-dark {\n background-color: #343a40 !important;\n}\n\na.bg-dark:hover, a.bg-dark:focus,\nbutton.bg-dark:hover,\nbutton.bg-dark:focus {\n background-color: #1d2124 !important;\n}\n\n.bg-white {\n background-color: #fff !important;\n}\n\n.bg-transparent {\n background-color: transparent !important;\n}\n\n.border {\n border: 1px solid #dee2e6 !important;\n}\n\n.border-top {\n border-top: 1px solid #dee2e6 !important;\n}\n\n.border-right {\n border-right: 1px solid #dee2e6 !important;\n}\n\n.border-bottom {\n border-bottom: 1px solid #dee2e6 !important;\n}\n\n.border-left {\n border-left: 1px solid #dee2e6 !important;\n}\n\n.border-0 {\n border: 0 !important;\n}\n\n.border-top-0 {\n border-top: 0 !important;\n}\n\n.border-right-0 {\n border-right: 0 !important;\n}\n\n.border-bottom-0 {\n border-bottom: 0 !important;\n}\n\n.border-left-0 {\n border-left: 0 !important;\n}\n\n.border-primary {\n border-color: #007bff !important;\n}\n\n.border-secondary {\n border-color: #6c757d !important;\n}\n\n.border-success {\n border-color: #28a745 !important;\n}\n\n.border-info {\n border-color: #17a2b8 !important;\n}\n\n.border-warning {\n border-color: #ffc107 !important;\n}\n\n.border-danger {\n border-color: #dc3545 !important;\n}\n\n.border-light {\n border-color: #f8f9fa !important;\n}\n\n.border-dark {\n border-color: #343a40 !important;\n}\n\n.border-white {\n border-color: #fff !important;\n}\n\n.rounded-sm {\n border-radius: 0.2rem !important;\n}\n\n.rounded {\n border-radius: 0.25rem !important;\n}\n\n.rounded-top {\n border-top-left-radius: 0.25rem !important;\n border-top-right-radius: 0.25rem !important;\n}\n\n.rounded-right {\n border-top-right-radius: 0.25rem !important;\n border-bottom-right-radius: 0.25rem !important;\n}\n\n.rounded-bottom {\n border-bottom-right-radius: 0.25rem !important;\n border-bottom-left-radius: 0.25rem !important;\n}\n\n.rounded-left {\n border-top-left-radius: 0.25rem !important;\n border-bottom-left-radius: 0.25rem !important;\n}\n\n.rounded-lg {\n border-radius: 0.3rem !important;\n}\n\n.rounded-circle {\n border-radius: 50% !important;\n}\n\n.rounded-pill {\n border-radius: 50rem !important;\n}\n\n.rounded-0 {\n border-radius: 0 !important;\n}\n\n.clearfix::after {\n display: block;\n clear: both;\n content: \"\";\n}\n\n.d-none {\n display: none !important;\n}\n\n.d-inline {\n display: inline !important;\n}\n\n.d-inline-block {\n display: inline-block !important;\n}\n\n.d-block {\n display: block !important;\n}\n\n.d-table {\n display: table !important;\n}\n\n.d-table-row {\n display: table-row !important;\n}\n\n.d-table-cell {\n display: table-cell !important;\n}\n\n.d-flex {\n display: flex !important;\n}\n\n.d-inline-flex {\n display: inline-flex !important;\n}\n\n@media (min-width: 576px) {\n .d-sm-none {\n display: none !important;\n }\n .d-sm-inline {\n display: inline !important;\n }\n .d-sm-inline-block {\n display: inline-block !important;\n }\n .d-sm-block {\n display: block !important;\n }\n .d-sm-table {\n display: table !important;\n }\n .d-sm-table-row {\n display: table-row !important;\n }\n .d-sm-table-cell {\n display: table-cell !important;\n }\n .d-sm-flex {\n display: flex !important;\n }\n .d-sm-inline-flex {\n display: inline-flex !important;\n }\n}\n\n@media (min-width: 768px) {\n .d-md-none {\n display: none !important;\n }\n .d-md-inline {\n display: inline !important;\n }\n .d-md-inline-block {\n display: inline-block !important;\n }\n .d-md-block {\n display: block !important;\n }\n .d-md-table {\n display: table !important;\n }\n .d-md-table-row {\n display: table-row !important;\n }\n .d-md-table-cell {\n display: table-cell !important;\n }\n .d-md-flex {\n display: flex !important;\n }\n .d-md-inline-flex {\n display: inline-flex !important;\n }\n}\n\n@media (min-width: 992px) {\n .d-lg-none {\n display: none !important;\n }\n .d-lg-inline {\n display: inline !important;\n }\n .d-lg-inline-block {\n display: inline-block !important;\n }\n .d-lg-block {\n display: block !important;\n }\n .d-lg-table {\n display: table !important;\n }\n .d-lg-table-row {\n display: table-row !important;\n }\n .d-lg-table-cell {\n display: table-cell !important;\n }\n .d-lg-flex {\n display: flex !important;\n }\n .d-lg-inline-flex {\n display: inline-flex !important;\n }\n}\n\n@media (min-width: 1200px) {\n .d-xl-none {\n display: none !important;\n }\n .d-xl-inline {\n display: inline !important;\n }\n .d-xl-inline-block {\n display: inline-block !important;\n }\n .d-xl-block {\n display: block !important;\n }\n .d-xl-table {\n display: table !important;\n }\n .d-xl-table-row {\n display: table-row !important;\n }\n .d-xl-table-cell {\n display: table-cell !important;\n }\n .d-xl-flex {\n display: flex !important;\n }\n .d-xl-inline-flex {\n display: inline-flex !important;\n }\n}\n\n@media print {\n .d-print-none {\n display: none !important;\n }\n .d-print-inline {\n display: inline !important;\n }\n .d-print-inline-block {\n display: inline-block !important;\n }\n .d-print-block {\n display: block !important;\n }\n .d-print-table {\n display: table !important;\n }\n .d-print-table-row {\n display: table-row !important;\n }\n .d-print-table-cell {\n display: table-cell !important;\n }\n .d-print-flex {\n display: flex !important;\n }\n .d-print-inline-flex {\n display: inline-flex !important;\n }\n}\n\n.embed-responsive {\n position: relative;\n display: block;\n width: 100%;\n padding: 0;\n overflow: hidden;\n}\n\n.embed-responsive::before {\n display: block;\n content: \"\";\n}\n\n.embed-responsive .embed-responsive-item,\n.embed-responsive iframe,\n.embed-responsive embed,\n.embed-responsive object,\n.embed-responsive video {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 100%;\n height: 100%;\n border: 0;\n}\n\n.embed-responsive-21by9::before {\n padding-top: 42.857143%;\n}\n\n.embed-responsive-16by9::before {\n padding-top: 56.25%;\n}\n\n.embed-responsive-4by3::before {\n padding-top: 75%;\n}\n\n.embed-responsive-1by1::before {\n padding-top: 100%;\n}\n\n.flex-row {\n flex-direction: row !important;\n}\n\n.flex-column {\n flex-direction: column !important;\n}\n\n.flex-row-reverse {\n flex-direction: row-reverse !important;\n}\n\n.flex-column-reverse {\n flex-direction: column-reverse !important;\n}\n\n.flex-wrap {\n flex-wrap: wrap !important;\n}\n\n.flex-nowrap {\n flex-wrap: nowrap !important;\n}\n\n.flex-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n}\n\n.flex-fill {\n flex: 1 1 auto !important;\n}\n\n.flex-grow-0 {\n flex-grow: 0 !important;\n}\n\n.flex-grow-1 {\n flex-grow: 1 !important;\n}\n\n.flex-shrink-0 {\n flex-shrink: 0 !important;\n}\n\n.flex-shrink-1 {\n flex-shrink: 1 !important;\n}\n\n.justify-content-start {\n justify-content: flex-start !important;\n}\n\n.justify-content-end {\n justify-content: flex-end !important;\n}\n\n.justify-content-center {\n justify-content: center !important;\n}\n\n.justify-content-between {\n justify-content: space-between !important;\n}\n\n.justify-content-around {\n justify-content: space-around !important;\n}\n\n.align-items-start {\n align-items: flex-start !important;\n}\n\n.align-items-end {\n align-items: flex-end !important;\n}\n\n.align-items-center {\n align-items: center !important;\n}\n\n.align-items-baseline {\n align-items: baseline !important;\n}\n\n.align-items-stretch {\n align-items: stretch !important;\n}\n\n.align-content-start {\n align-content: flex-start !important;\n}\n\n.align-content-end {\n align-content: flex-end !important;\n}\n\n.align-content-center {\n align-content: center !important;\n}\n\n.align-content-between {\n align-content: space-between !important;\n}\n\n.align-content-around {\n align-content: space-around !important;\n}\n\n.align-content-stretch {\n align-content: stretch !important;\n}\n\n.align-self-auto {\n align-self: auto !important;\n}\n\n.align-self-start {\n align-self: flex-start !important;\n}\n\n.align-self-end {\n align-self: flex-end !important;\n}\n\n.align-self-center {\n align-self: center !important;\n}\n\n.align-self-baseline {\n align-self: baseline !important;\n}\n\n.align-self-stretch {\n align-self: stretch !important;\n}\n\n@media (min-width: 576px) {\n .flex-sm-row {\n flex-direction: row !important;\n }\n .flex-sm-column {\n flex-direction: column !important;\n }\n .flex-sm-row-reverse {\n flex-direction: row-reverse !important;\n }\n .flex-sm-column-reverse {\n flex-direction: column-reverse !important;\n }\n .flex-sm-wrap {\n flex-wrap: wrap !important;\n }\n .flex-sm-nowrap {\n flex-wrap: nowrap !important;\n }\n .flex-sm-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n }\n .flex-sm-fill {\n flex: 1 1 auto !important;\n }\n .flex-sm-grow-0 {\n flex-grow: 0 !important;\n }\n .flex-sm-grow-1 {\n flex-grow: 1 !important;\n }\n .flex-sm-shrink-0 {\n flex-shrink: 0 !important;\n }\n .flex-sm-shrink-1 {\n flex-shrink: 1 !important;\n }\n .justify-content-sm-start {\n justify-content: flex-start !important;\n }\n .justify-content-sm-end {\n justify-content: flex-end !important;\n }\n .justify-content-sm-center {\n justify-content: center !important;\n }\n .justify-content-sm-between {\n justify-content: space-between !important;\n }\n .justify-content-sm-around {\n justify-content: space-around !important;\n }\n .align-items-sm-start {\n align-items: flex-start !important;\n }\n .align-items-sm-end {\n align-items: flex-end !important;\n }\n .align-items-sm-center {\n align-items: center !important;\n }\n .align-items-sm-baseline {\n align-items: baseline !important;\n }\n .align-items-sm-stretch {\n align-items: stretch !important;\n }\n .align-content-sm-start {\n align-content: flex-start !important;\n }\n .align-content-sm-end {\n align-content: flex-end !important;\n }\n .align-content-sm-center {\n align-content: center !important;\n }\n .align-content-sm-between {\n align-content: space-between !important;\n }\n .align-content-sm-around {\n align-content: space-around !important;\n }\n .align-content-sm-stretch {\n align-content: stretch !important;\n }\n .align-self-sm-auto {\n align-self: auto !important;\n }\n .align-self-sm-start {\n align-self: flex-start !important;\n }\n .align-self-sm-end {\n align-self: flex-end !important;\n }\n .align-self-sm-center {\n align-self: center !important;\n }\n .align-self-sm-baseline {\n align-self: baseline !important;\n }\n .align-self-sm-stretch {\n align-self: stretch !important;\n }\n}\n\n@media (min-width: 768px) {\n .flex-md-row {\n flex-direction: row !important;\n }\n .flex-md-column {\n flex-direction: column !important;\n }\n .flex-md-row-reverse {\n flex-direction: row-reverse !important;\n }\n .flex-md-column-reverse {\n flex-direction: column-reverse !important;\n }\n .flex-md-wrap {\n flex-wrap: wrap !important;\n }\n .flex-md-nowrap {\n flex-wrap: nowrap !important;\n }\n .flex-md-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n }\n .flex-md-fill {\n flex: 1 1 auto !important;\n }\n .flex-md-grow-0 {\n flex-grow: 0 !important;\n }\n .flex-md-grow-1 {\n flex-grow: 1 !important;\n }\n .flex-md-shrink-0 {\n flex-shrink: 0 !important;\n }\n .flex-md-shrink-1 {\n flex-shrink: 1 !important;\n }\n .justify-content-md-start {\n justify-content: flex-start !important;\n }\n .justify-content-md-end {\n justify-content: flex-end !important;\n }\n .justify-content-md-center {\n justify-content: center !important;\n }\n .justify-content-md-between {\n justify-content: space-between !important;\n }\n .justify-content-md-around {\n justify-content: space-around !important;\n }\n .align-items-md-start {\n align-items: flex-start !important;\n }\n .align-items-md-end {\n align-items: flex-end !important;\n }\n .align-items-md-center {\n align-items: center !important;\n }\n .align-items-md-baseline {\n align-items: baseline !important;\n }\n .align-items-md-stretch {\n align-items: stretch !important;\n }\n .align-content-md-start {\n align-content: flex-start !important;\n }\n .align-content-md-end {\n align-content: flex-end !important;\n }\n .align-content-md-center {\n align-content: center !important;\n }\n .align-content-md-between {\n align-content: space-between !important;\n }\n .align-content-md-around {\n align-content: space-around !important;\n }\n .align-content-md-stretch {\n align-content: stretch !important;\n }\n .align-self-md-auto {\n align-self: auto !important;\n }\n .align-self-md-start {\n align-self: flex-start !important;\n }\n .align-self-md-end {\n align-self: flex-end !important;\n }\n .align-self-md-center {\n align-self: center !important;\n }\n .align-self-md-baseline {\n align-self: baseline !important;\n }\n .align-self-md-stretch {\n align-self: stretch !important;\n }\n}\n\n@media (min-width: 992px) {\n .flex-lg-row {\n flex-direction: row !important;\n }\n .flex-lg-column {\n flex-direction: column !important;\n }\n .flex-lg-row-reverse {\n flex-direction: row-reverse !important;\n }\n .flex-lg-column-reverse {\n flex-direction: column-reverse !important;\n }\n .flex-lg-wrap {\n flex-wrap: wrap !important;\n }\n .flex-lg-nowrap {\n flex-wrap: nowrap !important;\n }\n .flex-lg-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n }\n .flex-lg-fill {\n flex: 1 1 auto !important;\n }\n .flex-lg-grow-0 {\n flex-grow: 0 !important;\n }\n .flex-lg-grow-1 {\n flex-grow: 1 !important;\n }\n .flex-lg-shrink-0 {\n flex-shrink: 0 !important;\n }\n .flex-lg-shrink-1 {\n flex-shrink: 1 !important;\n }\n .justify-content-lg-start {\n justify-content: flex-start !important;\n }\n .justify-content-lg-end {\n justify-content: flex-end !important;\n }\n .justify-content-lg-center {\n justify-content: center !important;\n }\n .justify-content-lg-between {\n justify-content: space-between !important;\n }\n .justify-content-lg-around {\n justify-content: space-around !important;\n }\n .align-items-lg-start {\n align-items: flex-start !important;\n }\n .align-items-lg-end {\n align-items: flex-end !important;\n }\n .align-items-lg-center {\n align-items: center !important;\n }\n .align-items-lg-baseline {\n align-items: baseline !important;\n }\n .align-items-lg-stretch {\n align-items: stretch !important;\n }\n .align-content-lg-start {\n align-content: flex-start !important;\n }\n .align-content-lg-end {\n align-content: flex-end !important;\n }\n .align-content-lg-center {\n align-content: center !important;\n }\n .align-content-lg-between {\n align-content: space-between !important;\n }\n .align-content-lg-around {\n align-content: space-around !important;\n }\n .align-content-lg-stretch {\n align-content: stretch !important;\n }\n .align-self-lg-auto {\n align-self: auto !important;\n }\n .align-self-lg-start {\n align-self: flex-start !important;\n }\n .align-self-lg-end {\n align-self: flex-end !important;\n }\n .align-self-lg-center {\n align-self: center !important;\n }\n .align-self-lg-baseline {\n align-self: baseline !important;\n }\n .align-self-lg-stretch {\n align-self: stretch !important;\n }\n}\n\n@media (min-width: 1200px) {\n .flex-xl-row {\n flex-direction: row !important;\n }\n .flex-xl-column {\n flex-direction: column !important;\n }\n .flex-xl-row-reverse {\n flex-direction: row-reverse !important;\n }\n .flex-xl-column-reverse {\n flex-direction: column-reverse !important;\n }\n .flex-xl-wrap {\n flex-wrap: wrap !important;\n }\n .flex-xl-nowrap {\n flex-wrap: nowrap !important;\n }\n .flex-xl-wrap-reverse {\n flex-wrap: wrap-reverse !important;\n }\n .flex-xl-fill {\n flex: 1 1 auto !important;\n }\n .flex-xl-grow-0 {\n flex-grow: 0 !important;\n }\n .flex-xl-grow-1 {\n flex-grow: 1 !important;\n }\n .flex-xl-shrink-0 {\n flex-shrink: 0 !important;\n }\n .flex-xl-shrink-1 {\n flex-shrink: 1 !important;\n }\n .justify-content-xl-start {\n justify-content: flex-start !important;\n }\n .justify-content-xl-end {\n justify-content: flex-end !important;\n }\n .justify-content-xl-center {\n justify-content: center !important;\n }\n .justify-content-xl-between {\n justify-content: space-between !important;\n }\n .justify-content-xl-around {\n justify-content: space-around !important;\n }\n .align-items-xl-start {\n align-items: flex-start !important;\n }\n .align-items-xl-end {\n align-items: flex-end !important;\n }\n .align-items-xl-center {\n align-items: center !important;\n }\n .align-items-xl-baseline {\n align-items: baseline !important;\n }\n .align-items-xl-stretch {\n align-items: stretch !important;\n }\n .align-content-xl-start {\n align-content: flex-start !important;\n }\n .align-content-xl-end {\n align-content: flex-end !important;\n }\n .align-content-xl-center {\n align-content: center !important;\n }\n .align-content-xl-between {\n align-content: space-between !important;\n }\n .align-content-xl-around {\n align-content: space-around !important;\n }\n .align-content-xl-stretch {\n align-content: stretch !important;\n }\n .align-self-xl-auto {\n align-self: auto !important;\n }\n .align-self-xl-start {\n align-self: flex-start !important;\n }\n .align-self-xl-end {\n align-self: flex-end !important;\n }\n .align-self-xl-center {\n align-self: center !important;\n }\n .align-self-xl-baseline {\n align-self: baseline !important;\n }\n .align-self-xl-stretch {\n align-self: stretch !important;\n }\n}\n\n.float-left {\n float: left !important;\n}\n\n.float-right {\n float: right !important;\n}\n\n.float-none {\n float: none !important;\n}\n\n@media (min-width: 576px) {\n .float-sm-left {\n float: left !important;\n }\n .float-sm-right {\n float: right !important;\n }\n .float-sm-none {\n float: none !important;\n }\n}\n\n@media (min-width: 768px) {\n .float-md-left {\n float: left !important;\n }\n .float-md-right {\n float: right !important;\n }\n .float-md-none {\n float: none !important;\n }\n}\n\n@media (min-width: 992px) {\n .float-lg-left {\n float: left !important;\n }\n .float-lg-right {\n float: right !important;\n }\n .float-lg-none {\n float: none !important;\n }\n}\n\n@media (min-width: 1200px) {\n .float-xl-left {\n float: left !important;\n }\n .float-xl-right {\n float: right !important;\n }\n .float-xl-none {\n float: none !important;\n }\n}\n\n.overflow-auto {\n overflow: auto !important;\n}\n\n.overflow-hidden {\n overflow: hidden !important;\n}\n\n.position-static {\n position: static !important;\n}\n\n.position-relative {\n position: relative !important;\n}\n\n.position-absolute {\n position: absolute !important;\n}\n\n.position-fixed {\n position: fixed !important;\n}\n\n.position-sticky {\n position: sticky !important;\n}\n\n.fixed-top {\n position: fixed;\n top: 0;\n right: 0;\n left: 0;\n z-index: 1030;\n}\n\n.fixed-bottom {\n position: fixed;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1030;\n}\n\n@supports (position: sticky) {\n .sticky-top {\n position: sticky;\n top: 0;\n z-index: 1020;\n }\n}\n\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n white-space: nowrap;\n border: 0;\n}\n\n.sr-only-focusable:active, .sr-only-focusable:focus {\n position: static;\n width: auto;\n height: auto;\n overflow: visible;\n clip: auto;\n white-space: normal;\n}\n\n.shadow-sm {\n box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important;\n}\n\n.shadow {\n box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important;\n}\n\n.shadow-lg {\n box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important;\n}\n\n.shadow-none {\n box-shadow: none !important;\n}\n\n.w-25 {\n width: 25% !important;\n}\n\n.w-50 {\n width: 50% !important;\n}\n\n.w-75 {\n width: 75% !important;\n}\n\n.w-100 {\n width: 100% !important;\n}\n\n.w-auto {\n width: auto !important;\n}\n\n.h-25 {\n height: 25% !important;\n}\n\n.h-50 {\n height: 50% !important;\n}\n\n.h-75 {\n height: 75% !important;\n}\n\n.h-100 {\n height: 100% !important;\n}\n\n.h-auto {\n height: auto !important;\n}\n\n.mw-100 {\n max-width: 100% !important;\n}\n\n.mh-100 {\n max-height: 100% !important;\n}\n\n.min-vw-100 {\n min-width: 100vw !important;\n}\n\n.min-vh-100 {\n min-height: 100vh !important;\n}\n\n.vw-100 {\n width: 100vw !important;\n}\n\n.vh-100 {\n height: 100vh !important;\n}\n\n.stretched-link::after {\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1;\n pointer-events: auto;\n content: \"\";\n background-color: rgba(0, 0, 0, 0);\n}\n\n.m-0 {\n margin: 0 !important;\n}\n\n.mt-0,\n.my-0 {\n margin-top: 0 !important;\n}\n\n.mr-0,\n.mx-0 {\n margin-right: 0 !important;\n}\n\n.mb-0,\n.my-0 {\n margin-bottom: 0 !important;\n}\n\n.ml-0,\n.mx-0 {\n margin-left: 0 !important;\n}\n\n.m-1 {\n margin: 0.25rem !important;\n}\n\n.mt-1,\n.my-1 {\n margin-top: 0.25rem !important;\n}\n\n.mr-1,\n.mx-1 {\n margin-right: 0.25rem !important;\n}\n\n.mb-1,\n.my-1 {\n margin-bottom: 0.25rem !important;\n}\n\n.ml-1,\n.mx-1 {\n margin-left: 0.25rem !important;\n}\n\n.m-2 {\n margin: 0.5rem !important;\n}\n\n.mt-2,\n.my-2 {\n margin-top: 0.5rem !important;\n}\n\n.mr-2,\n.mx-2 {\n margin-right: 0.5rem !important;\n}\n\n.mb-2,\n.my-2 {\n margin-bottom: 0.5rem !important;\n}\n\n.ml-2,\n.mx-2 {\n margin-left: 0.5rem !important;\n}\n\n.m-3 {\n margin: 1rem !important;\n}\n\n.mt-3,\n.my-3 {\n margin-top: 1rem !important;\n}\n\n.mr-3,\n.mx-3 {\n margin-right: 1rem !important;\n}\n\n.mb-3,\n.my-3 {\n margin-bottom: 1rem !important;\n}\n\n.ml-3,\n.mx-3 {\n margin-left: 1rem !important;\n}\n\n.m-4 {\n margin: 1.5rem !important;\n}\n\n.mt-4,\n.my-4 {\n margin-top: 1.5rem !important;\n}\n\n.mr-4,\n.mx-4 {\n margin-right: 1.5rem !important;\n}\n\n.mb-4,\n.my-4 {\n margin-bottom: 1.5rem !important;\n}\n\n.ml-4,\n.mx-4 {\n margin-left: 1.5rem !important;\n}\n\n.m-5 {\n margin: 3rem !important;\n}\n\n.mt-5,\n.my-5 {\n margin-top: 3rem !important;\n}\n\n.mr-5,\n.mx-5 {\n margin-right: 3rem !important;\n}\n\n.mb-5,\n.my-5 {\n margin-bottom: 3rem !important;\n}\n\n.ml-5,\n.mx-5 {\n margin-left: 3rem !important;\n}\n\n.p-0 {\n padding: 0 !important;\n}\n\n.pt-0,\n.py-0 {\n padding-top: 0 !important;\n}\n\n.pr-0,\n.px-0 {\n padding-right: 0 !important;\n}\n\n.pb-0,\n.py-0 {\n padding-bottom: 0 !important;\n}\n\n.pl-0,\n.px-0 {\n padding-left: 0 !important;\n}\n\n.p-1 {\n padding: 0.25rem !important;\n}\n\n.pt-1,\n.py-1 {\n padding-top: 0.25rem !important;\n}\n\n.pr-1,\n.px-1 {\n padding-right: 0.25rem !important;\n}\n\n.pb-1,\n.py-1 {\n padding-bottom: 0.25rem !important;\n}\n\n.pl-1,\n.px-1 {\n padding-left: 0.25rem !important;\n}\n\n.p-2 {\n padding: 0.5rem !important;\n}\n\n.pt-2,\n.py-2 {\n padding-top: 0.5rem !important;\n}\n\n.pr-2,\n.px-2 {\n padding-right: 0.5rem !important;\n}\n\n.pb-2,\n.py-2 {\n padding-bottom: 0.5rem !important;\n}\n\n.pl-2,\n.px-2 {\n padding-left: 0.5rem !important;\n}\n\n.p-3 {\n padding: 1rem !important;\n}\n\n.pt-3,\n.py-3 {\n padding-top: 1rem !important;\n}\n\n.pr-3,\n.px-3 {\n padding-right: 1rem !important;\n}\n\n.pb-3,\n.py-3 {\n padding-bottom: 1rem !important;\n}\n\n.pl-3,\n.px-3 {\n padding-left: 1rem !important;\n}\n\n.p-4 {\n padding: 1.5rem !important;\n}\n\n.pt-4,\n.py-4 {\n padding-top: 1.5rem !important;\n}\n\n.pr-4,\n.px-4 {\n padding-right: 1.5rem !important;\n}\n\n.pb-4,\n.py-4 {\n padding-bottom: 1.5rem !important;\n}\n\n.pl-4,\n.px-4 {\n padding-left: 1.5rem !important;\n}\n\n.p-5 {\n padding: 3rem !important;\n}\n\n.pt-5,\n.py-5 {\n padding-top: 3rem !important;\n}\n\n.pr-5,\n.px-5 {\n padding-right: 3rem !important;\n}\n\n.pb-5,\n.py-5 {\n padding-bottom: 3rem !important;\n}\n\n.pl-5,\n.px-5 {\n padding-left: 3rem !important;\n}\n\n.m-n1 {\n margin: -0.25rem !important;\n}\n\n.mt-n1,\n.my-n1 {\n margin-top: -0.25rem !important;\n}\n\n.mr-n1,\n.mx-n1 {\n margin-right: -0.25rem !important;\n}\n\n.mb-n1,\n.my-n1 {\n margin-bottom: -0.25rem !important;\n}\n\n.ml-n1,\n.mx-n1 {\n margin-left: -0.25rem !important;\n}\n\n.m-n2 {\n margin: -0.5rem !important;\n}\n\n.mt-n2,\n.my-n2 {\n margin-top: -0.5rem !important;\n}\n\n.mr-n2,\n.mx-n2 {\n margin-right: -0.5rem !important;\n}\n\n.mb-n2,\n.my-n2 {\n margin-bottom: -0.5rem !important;\n}\n\n.ml-n2,\n.mx-n2 {\n margin-left: -0.5rem !important;\n}\n\n.m-n3 {\n margin: -1rem !important;\n}\n\n.mt-n3,\n.my-n3 {\n margin-top: -1rem !important;\n}\n\n.mr-n3,\n.mx-n3 {\n margin-right: -1rem !important;\n}\n\n.mb-n3,\n.my-n3 {\n margin-bottom: -1rem !important;\n}\n\n.ml-n3,\n.mx-n3 {\n margin-left: -1rem !important;\n}\n\n.m-n4 {\n margin: -1.5rem !important;\n}\n\n.mt-n4,\n.my-n4 {\n margin-top: -1.5rem !important;\n}\n\n.mr-n4,\n.mx-n4 {\n margin-right: -1.5rem !important;\n}\n\n.mb-n4,\n.my-n4 {\n margin-bottom: -1.5rem !important;\n}\n\n.ml-n4,\n.mx-n4 {\n margin-left: -1.5rem !important;\n}\n\n.m-n5 {\n margin: -3rem !important;\n}\n\n.mt-n5,\n.my-n5 {\n margin-top: -3rem !important;\n}\n\n.mr-n5,\n.mx-n5 {\n margin-right: -3rem !important;\n}\n\n.mb-n5,\n.my-n5 {\n margin-bottom: -3rem !important;\n}\n\n.ml-n5,\n.mx-n5 {\n margin-left: -3rem !important;\n}\n\n.m-auto {\n margin: auto !important;\n}\n\n.mt-auto,\n.my-auto {\n margin-top: auto !important;\n}\n\n.mr-auto,\n.mx-auto {\n margin-right: auto !important;\n}\n\n.mb-auto,\n.my-auto {\n margin-bottom: auto !important;\n}\n\n.ml-auto,\n.mx-auto {\n margin-left: auto !important;\n}\n\n@media (min-width: 576px) {\n .m-sm-0 {\n margin: 0 !important;\n }\n .mt-sm-0,\n .my-sm-0 {\n margin-top: 0 !important;\n }\n .mr-sm-0,\n .mx-sm-0 {\n margin-right: 0 !important;\n }\n .mb-sm-0,\n .my-sm-0 {\n margin-bottom: 0 !important;\n }\n .ml-sm-0,\n .mx-sm-0 {\n margin-left: 0 !important;\n }\n .m-sm-1 {\n margin: 0.25rem !important;\n }\n .mt-sm-1,\n .my-sm-1 {\n margin-top: 0.25rem !important;\n }\n .mr-sm-1,\n .mx-sm-1 {\n margin-right: 0.25rem !important;\n }\n .mb-sm-1,\n .my-sm-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-sm-1,\n .mx-sm-1 {\n margin-left: 0.25rem !important;\n }\n .m-sm-2 {\n margin: 0.5rem !important;\n }\n .mt-sm-2,\n .my-sm-2 {\n margin-top: 0.5rem !important;\n }\n .mr-sm-2,\n .mx-sm-2 {\n margin-right: 0.5rem !important;\n }\n .mb-sm-2,\n .my-sm-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-sm-2,\n .mx-sm-2 {\n margin-left: 0.5rem !important;\n }\n .m-sm-3 {\n margin: 1rem !important;\n }\n .mt-sm-3,\n .my-sm-3 {\n margin-top: 1rem !important;\n }\n .mr-sm-3,\n .mx-sm-3 {\n margin-right: 1rem !important;\n }\n .mb-sm-3,\n .my-sm-3 {\n margin-bottom: 1rem !important;\n }\n .ml-sm-3,\n .mx-sm-3 {\n margin-left: 1rem !important;\n }\n .m-sm-4 {\n margin: 1.5rem !important;\n }\n .mt-sm-4,\n .my-sm-4 {\n margin-top: 1.5rem !important;\n }\n .mr-sm-4,\n .mx-sm-4 {\n margin-right: 1.5rem !important;\n }\n .mb-sm-4,\n .my-sm-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-sm-4,\n .mx-sm-4 {\n margin-left: 1.5rem !important;\n }\n .m-sm-5 {\n margin: 3rem !important;\n }\n .mt-sm-5,\n .my-sm-5 {\n margin-top: 3rem !important;\n }\n .mr-sm-5,\n .mx-sm-5 {\n margin-right: 3rem !important;\n }\n .mb-sm-5,\n .my-sm-5 {\n margin-bottom: 3rem !important;\n }\n .ml-sm-5,\n .mx-sm-5 {\n margin-left: 3rem !important;\n }\n .p-sm-0 {\n padding: 0 !important;\n }\n .pt-sm-0,\n .py-sm-0 {\n padding-top: 0 !important;\n }\n .pr-sm-0,\n .px-sm-0 {\n padding-right: 0 !important;\n }\n .pb-sm-0,\n .py-sm-0 {\n padding-bottom: 0 !important;\n }\n .pl-sm-0,\n .px-sm-0 {\n padding-left: 0 !important;\n }\n .p-sm-1 {\n padding: 0.25rem !important;\n }\n .pt-sm-1,\n .py-sm-1 {\n padding-top: 0.25rem !important;\n }\n .pr-sm-1,\n .px-sm-1 {\n padding-right: 0.25rem !important;\n }\n .pb-sm-1,\n .py-sm-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-sm-1,\n .px-sm-1 {\n padding-left: 0.25rem !important;\n }\n .p-sm-2 {\n padding: 0.5rem !important;\n }\n .pt-sm-2,\n .py-sm-2 {\n padding-top: 0.5rem !important;\n }\n .pr-sm-2,\n .px-sm-2 {\n padding-right: 0.5rem !important;\n }\n .pb-sm-2,\n .py-sm-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-sm-2,\n .px-sm-2 {\n padding-left: 0.5rem !important;\n }\n .p-sm-3 {\n padding: 1rem !important;\n }\n .pt-sm-3,\n .py-sm-3 {\n padding-top: 1rem !important;\n }\n .pr-sm-3,\n .px-sm-3 {\n padding-right: 1rem !important;\n }\n .pb-sm-3,\n .py-sm-3 {\n padding-bottom: 1rem !important;\n }\n .pl-sm-3,\n .px-sm-3 {\n padding-left: 1rem !important;\n }\n .p-sm-4 {\n padding: 1.5rem !important;\n }\n .pt-sm-4,\n .py-sm-4 {\n padding-top: 1.5rem !important;\n }\n .pr-sm-4,\n .px-sm-4 {\n padding-right: 1.5rem !important;\n }\n .pb-sm-4,\n .py-sm-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-sm-4,\n .px-sm-4 {\n padding-left: 1.5rem !important;\n }\n .p-sm-5 {\n padding: 3rem !important;\n }\n .pt-sm-5,\n .py-sm-5 {\n padding-top: 3rem !important;\n }\n .pr-sm-5,\n .px-sm-5 {\n padding-right: 3rem !important;\n }\n .pb-sm-5,\n .py-sm-5 {\n padding-bottom: 3rem !important;\n }\n .pl-sm-5,\n .px-sm-5 {\n padding-left: 3rem !important;\n }\n .m-sm-n1 {\n margin: -0.25rem !important;\n }\n .mt-sm-n1,\n .my-sm-n1 {\n margin-top: -0.25rem !important;\n }\n .mr-sm-n1,\n .mx-sm-n1 {\n margin-right: -0.25rem !important;\n }\n .mb-sm-n1,\n .my-sm-n1 {\n margin-bottom: -0.25rem !important;\n }\n .ml-sm-n1,\n .mx-sm-n1 {\n margin-left: -0.25rem !important;\n }\n .m-sm-n2 {\n margin: -0.5rem !important;\n }\n .mt-sm-n2,\n .my-sm-n2 {\n margin-top: -0.5rem !important;\n }\n .mr-sm-n2,\n .mx-sm-n2 {\n margin-right: -0.5rem !important;\n }\n .mb-sm-n2,\n .my-sm-n2 {\n margin-bottom: -0.5rem !important;\n }\n .ml-sm-n2,\n .mx-sm-n2 {\n margin-left: -0.5rem !important;\n }\n .m-sm-n3 {\n margin: -1rem !important;\n }\n .mt-sm-n3,\n .my-sm-n3 {\n margin-top: -1rem !important;\n }\n .mr-sm-n3,\n .mx-sm-n3 {\n margin-right: -1rem !important;\n }\n .mb-sm-n3,\n .my-sm-n3 {\n margin-bottom: -1rem !important;\n }\n .ml-sm-n3,\n .mx-sm-n3 {\n margin-left: -1rem !important;\n }\n .m-sm-n4 {\n margin: -1.5rem !important;\n }\n .mt-sm-n4,\n .my-sm-n4 {\n margin-top: -1.5rem !important;\n }\n .mr-sm-n4,\n .mx-sm-n4 {\n margin-right: -1.5rem !important;\n }\n .mb-sm-n4,\n .my-sm-n4 {\n margin-bottom: -1.5rem !important;\n }\n .ml-sm-n4,\n .mx-sm-n4 {\n margin-left: -1.5rem !important;\n }\n .m-sm-n5 {\n margin: -3rem !important;\n }\n .mt-sm-n5,\n .my-sm-n5 {\n margin-top: -3rem !important;\n }\n .mr-sm-n5,\n .mx-sm-n5 {\n margin-right: -3rem !important;\n }\n .mb-sm-n5,\n .my-sm-n5 {\n margin-bottom: -3rem !important;\n }\n .ml-sm-n5,\n .mx-sm-n5 {\n margin-left: -3rem !important;\n }\n .m-sm-auto {\n margin: auto !important;\n }\n .mt-sm-auto,\n .my-sm-auto {\n margin-top: auto !important;\n }\n .mr-sm-auto,\n .mx-sm-auto {\n margin-right: auto !important;\n }\n .mb-sm-auto,\n .my-sm-auto {\n margin-bottom: auto !important;\n }\n .ml-sm-auto,\n .mx-sm-auto {\n margin-left: auto !important;\n }\n}\n\n@media (min-width: 768px) {\n .m-md-0 {\n margin: 0 !important;\n }\n .mt-md-0,\n .my-md-0 {\n margin-top: 0 !important;\n }\n .mr-md-0,\n .mx-md-0 {\n margin-right: 0 !important;\n }\n .mb-md-0,\n .my-md-0 {\n margin-bottom: 0 !important;\n }\n .ml-md-0,\n .mx-md-0 {\n margin-left: 0 !important;\n }\n .m-md-1 {\n margin: 0.25rem !important;\n }\n .mt-md-1,\n .my-md-1 {\n margin-top: 0.25rem !important;\n }\n .mr-md-1,\n .mx-md-1 {\n margin-right: 0.25rem !important;\n }\n .mb-md-1,\n .my-md-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-md-1,\n .mx-md-1 {\n margin-left: 0.25rem !important;\n }\n .m-md-2 {\n margin: 0.5rem !important;\n }\n .mt-md-2,\n .my-md-2 {\n margin-top: 0.5rem !important;\n }\n .mr-md-2,\n .mx-md-2 {\n margin-right: 0.5rem !important;\n }\n .mb-md-2,\n .my-md-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-md-2,\n .mx-md-2 {\n margin-left: 0.5rem !important;\n }\n .m-md-3 {\n margin: 1rem !important;\n }\n .mt-md-3,\n .my-md-3 {\n margin-top: 1rem !important;\n }\n .mr-md-3,\n .mx-md-3 {\n margin-right: 1rem !important;\n }\n .mb-md-3,\n .my-md-3 {\n margin-bottom: 1rem !important;\n }\n .ml-md-3,\n .mx-md-3 {\n margin-left: 1rem !important;\n }\n .m-md-4 {\n margin: 1.5rem !important;\n }\n .mt-md-4,\n .my-md-4 {\n margin-top: 1.5rem !important;\n }\n .mr-md-4,\n .mx-md-4 {\n margin-right: 1.5rem !important;\n }\n .mb-md-4,\n .my-md-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-md-4,\n .mx-md-4 {\n margin-left: 1.5rem !important;\n }\n .m-md-5 {\n margin: 3rem !important;\n }\n .mt-md-5,\n .my-md-5 {\n margin-top: 3rem !important;\n }\n .mr-md-5,\n .mx-md-5 {\n margin-right: 3rem !important;\n }\n .mb-md-5,\n .my-md-5 {\n margin-bottom: 3rem !important;\n }\n .ml-md-5,\n .mx-md-5 {\n margin-left: 3rem !important;\n }\n .p-md-0 {\n padding: 0 !important;\n }\n .pt-md-0,\n .py-md-0 {\n padding-top: 0 !important;\n }\n .pr-md-0,\n .px-md-0 {\n padding-right: 0 !important;\n }\n .pb-md-0,\n .py-md-0 {\n padding-bottom: 0 !important;\n }\n .pl-md-0,\n .px-md-0 {\n padding-left: 0 !important;\n }\n .p-md-1 {\n padding: 0.25rem !important;\n }\n .pt-md-1,\n .py-md-1 {\n padding-top: 0.25rem !important;\n }\n .pr-md-1,\n .px-md-1 {\n padding-right: 0.25rem !important;\n }\n .pb-md-1,\n .py-md-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-md-1,\n .px-md-1 {\n padding-left: 0.25rem !important;\n }\n .p-md-2 {\n padding: 0.5rem !important;\n }\n .pt-md-2,\n .py-md-2 {\n padding-top: 0.5rem !important;\n }\n .pr-md-2,\n .px-md-2 {\n padding-right: 0.5rem !important;\n }\n .pb-md-2,\n .py-md-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-md-2,\n .px-md-2 {\n padding-left: 0.5rem !important;\n }\n .p-md-3 {\n padding: 1rem !important;\n }\n .pt-md-3,\n .py-md-3 {\n padding-top: 1rem !important;\n }\n .pr-md-3,\n .px-md-3 {\n padding-right: 1rem !important;\n }\n .pb-md-3,\n .py-md-3 {\n padding-bottom: 1rem !important;\n }\n .pl-md-3,\n .px-md-3 {\n padding-left: 1rem !important;\n }\n .p-md-4 {\n padding: 1.5rem !important;\n }\n .pt-md-4,\n .py-md-4 {\n padding-top: 1.5rem !important;\n }\n .pr-md-4,\n .px-md-4 {\n padding-right: 1.5rem !important;\n }\n .pb-md-4,\n .py-md-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-md-4,\n .px-md-4 {\n padding-left: 1.5rem !important;\n }\n .p-md-5 {\n padding: 3rem !important;\n }\n .pt-md-5,\n .py-md-5 {\n padding-top: 3rem !important;\n }\n .pr-md-5,\n .px-md-5 {\n padding-right: 3rem !important;\n }\n .pb-md-5,\n .py-md-5 {\n padding-bottom: 3rem !important;\n }\n .pl-md-5,\n .px-md-5 {\n padding-left: 3rem !important;\n }\n .m-md-n1 {\n margin: -0.25rem !important;\n }\n .mt-md-n1,\n .my-md-n1 {\n margin-top: -0.25rem !important;\n }\n .mr-md-n1,\n .mx-md-n1 {\n margin-right: -0.25rem !important;\n }\n .mb-md-n1,\n .my-md-n1 {\n margin-bottom: -0.25rem !important;\n }\n .ml-md-n1,\n .mx-md-n1 {\n margin-left: -0.25rem !important;\n }\n .m-md-n2 {\n margin: -0.5rem !important;\n }\n .mt-md-n2,\n .my-md-n2 {\n margin-top: -0.5rem !important;\n }\n .mr-md-n2,\n .mx-md-n2 {\n margin-right: -0.5rem !important;\n }\n .mb-md-n2,\n .my-md-n2 {\n margin-bottom: -0.5rem !important;\n }\n .ml-md-n2,\n .mx-md-n2 {\n margin-left: -0.5rem !important;\n }\n .m-md-n3 {\n margin: -1rem !important;\n }\n .mt-md-n3,\n .my-md-n3 {\n margin-top: -1rem !important;\n }\n .mr-md-n3,\n .mx-md-n3 {\n margin-right: -1rem !important;\n }\n .mb-md-n3,\n .my-md-n3 {\n margin-bottom: -1rem !important;\n }\n .ml-md-n3,\n .mx-md-n3 {\n margin-left: -1rem !important;\n }\n .m-md-n4 {\n margin: -1.5rem !important;\n }\n .mt-md-n4,\n .my-md-n4 {\n margin-top: -1.5rem !important;\n }\n .mr-md-n4,\n .mx-md-n4 {\n margin-right: -1.5rem !important;\n }\n .mb-md-n4,\n .my-md-n4 {\n margin-bottom: -1.5rem !important;\n }\n .ml-md-n4,\n .mx-md-n4 {\n margin-left: -1.5rem !important;\n }\n .m-md-n5 {\n margin: -3rem !important;\n }\n .mt-md-n5,\n .my-md-n5 {\n margin-top: -3rem !important;\n }\n .mr-md-n5,\n .mx-md-n5 {\n margin-right: -3rem !important;\n }\n .mb-md-n5,\n .my-md-n5 {\n margin-bottom: -3rem !important;\n }\n .ml-md-n5,\n .mx-md-n5 {\n margin-left: -3rem !important;\n }\n .m-md-auto {\n margin: auto !important;\n }\n .mt-md-auto,\n .my-md-auto {\n margin-top: auto !important;\n }\n .mr-md-auto,\n .mx-md-auto {\n margin-right: auto !important;\n }\n .mb-md-auto,\n .my-md-auto {\n margin-bottom: auto !important;\n }\n .ml-md-auto,\n .mx-md-auto {\n margin-left: auto !important;\n }\n}\n\n@media (min-width: 992px) {\n .m-lg-0 {\n margin: 0 !important;\n }\n .mt-lg-0,\n .my-lg-0 {\n margin-top: 0 !important;\n }\n .mr-lg-0,\n .mx-lg-0 {\n margin-right: 0 !important;\n }\n .mb-lg-0,\n .my-lg-0 {\n margin-bottom: 0 !important;\n }\n .ml-lg-0,\n .mx-lg-0 {\n margin-left: 0 !important;\n }\n .m-lg-1 {\n margin: 0.25rem !important;\n }\n .mt-lg-1,\n .my-lg-1 {\n margin-top: 0.25rem !important;\n }\n .mr-lg-1,\n .mx-lg-1 {\n margin-right: 0.25rem !important;\n }\n .mb-lg-1,\n .my-lg-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-lg-1,\n .mx-lg-1 {\n margin-left: 0.25rem !important;\n }\n .m-lg-2 {\n margin: 0.5rem !important;\n }\n .mt-lg-2,\n .my-lg-2 {\n margin-top: 0.5rem !important;\n }\n .mr-lg-2,\n .mx-lg-2 {\n margin-right: 0.5rem !important;\n }\n .mb-lg-2,\n .my-lg-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-lg-2,\n .mx-lg-2 {\n margin-left: 0.5rem !important;\n }\n .m-lg-3 {\n margin: 1rem !important;\n }\n .mt-lg-3,\n .my-lg-3 {\n margin-top: 1rem !important;\n }\n .mr-lg-3,\n .mx-lg-3 {\n margin-right: 1rem !important;\n }\n .mb-lg-3,\n .my-lg-3 {\n margin-bottom: 1rem !important;\n }\n .ml-lg-3,\n .mx-lg-3 {\n margin-left: 1rem !important;\n }\n .m-lg-4 {\n margin: 1.5rem !important;\n }\n .mt-lg-4,\n .my-lg-4 {\n margin-top: 1.5rem !important;\n }\n .mr-lg-4,\n .mx-lg-4 {\n margin-right: 1.5rem !important;\n }\n .mb-lg-4,\n .my-lg-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-lg-4,\n .mx-lg-4 {\n margin-left: 1.5rem !important;\n }\n .m-lg-5 {\n margin: 3rem !important;\n }\n .mt-lg-5,\n .my-lg-5 {\n margin-top: 3rem !important;\n }\n .mr-lg-5,\n .mx-lg-5 {\n margin-right: 3rem !important;\n }\n .mb-lg-5,\n .my-lg-5 {\n margin-bottom: 3rem !important;\n }\n .ml-lg-5,\n .mx-lg-5 {\n margin-left: 3rem !important;\n }\n .p-lg-0 {\n padding: 0 !important;\n }\n .pt-lg-0,\n .py-lg-0 {\n padding-top: 0 !important;\n }\n .pr-lg-0,\n .px-lg-0 {\n padding-right: 0 !important;\n }\n .pb-lg-0,\n .py-lg-0 {\n padding-bottom: 0 !important;\n }\n .pl-lg-0,\n .px-lg-0 {\n padding-left: 0 !important;\n }\n .p-lg-1 {\n padding: 0.25rem !important;\n }\n .pt-lg-1,\n .py-lg-1 {\n padding-top: 0.25rem !important;\n }\n .pr-lg-1,\n .px-lg-1 {\n padding-right: 0.25rem !important;\n }\n .pb-lg-1,\n .py-lg-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-lg-1,\n .px-lg-1 {\n padding-left: 0.25rem !important;\n }\n .p-lg-2 {\n padding: 0.5rem !important;\n }\n .pt-lg-2,\n .py-lg-2 {\n padding-top: 0.5rem !important;\n }\n .pr-lg-2,\n .px-lg-2 {\n padding-right: 0.5rem !important;\n }\n .pb-lg-2,\n .py-lg-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-lg-2,\n .px-lg-2 {\n padding-left: 0.5rem !important;\n }\n .p-lg-3 {\n padding: 1rem !important;\n }\n .pt-lg-3,\n .py-lg-3 {\n padding-top: 1rem !important;\n }\n .pr-lg-3,\n .px-lg-3 {\n padding-right: 1rem !important;\n }\n .pb-lg-3,\n .py-lg-3 {\n padding-bottom: 1rem !important;\n }\n .pl-lg-3,\n .px-lg-3 {\n padding-left: 1rem !important;\n }\n .p-lg-4 {\n padding: 1.5rem !important;\n }\n .pt-lg-4,\n .py-lg-4 {\n padding-top: 1.5rem !important;\n }\n .pr-lg-4,\n .px-lg-4 {\n padding-right: 1.5rem !important;\n }\n .pb-lg-4,\n .py-lg-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-lg-4,\n .px-lg-4 {\n padding-left: 1.5rem !important;\n }\n .p-lg-5 {\n padding: 3rem !important;\n }\n .pt-lg-5,\n .py-lg-5 {\n padding-top: 3rem !important;\n }\n .pr-lg-5,\n .px-lg-5 {\n padding-right: 3rem !important;\n }\n .pb-lg-5,\n .py-lg-5 {\n padding-bottom: 3rem !important;\n }\n .pl-lg-5,\n .px-lg-5 {\n padding-left: 3rem !important;\n }\n .m-lg-n1 {\n margin: -0.25rem !important;\n }\n .mt-lg-n1,\n .my-lg-n1 {\n margin-top: -0.25rem !important;\n }\n .mr-lg-n1,\n .mx-lg-n1 {\n margin-right: -0.25rem !important;\n }\n .mb-lg-n1,\n .my-lg-n1 {\n margin-bottom: -0.25rem !important;\n }\n .ml-lg-n1,\n .mx-lg-n1 {\n margin-left: -0.25rem !important;\n }\n .m-lg-n2 {\n margin: -0.5rem !important;\n }\n .mt-lg-n2,\n .my-lg-n2 {\n margin-top: -0.5rem !important;\n }\n .mr-lg-n2,\n .mx-lg-n2 {\n margin-right: -0.5rem !important;\n }\n .mb-lg-n2,\n .my-lg-n2 {\n margin-bottom: -0.5rem !important;\n }\n .ml-lg-n2,\n .mx-lg-n2 {\n margin-left: -0.5rem !important;\n }\n .m-lg-n3 {\n margin: -1rem !important;\n }\n .mt-lg-n3,\n .my-lg-n3 {\n margin-top: -1rem !important;\n }\n .mr-lg-n3,\n .mx-lg-n3 {\n margin-right: -1rem !important;\n }\n .mb-lg-n3,\n .my-lg-n3 {\n margin-bottom: -1rem !important;\n }\n .ml-lg-n3,\n .mx-lg-n3 {\n margin-left: -1rem !important;\n }\n .m-lg-n4 {\n margin: -1.5rem !important;\n }\n .mt-lg-n4,\n .my-lg-n4 {\n margin-top: -1.5rem !important;\n }\n .mr-lg-n4,\n .mx-lg-n4 {\n margin-right: -1.5rem !important;\n }\n .mb-lg-n4,\n .my-lg-n4 {\n margin-bottom: -1.5rem !important;\n }\n .ml-lg-n4,\n .mx-lg-n4 {\n margin-left: -1.5rem !important;\n }\n .m-lg-n5 {\n margin: -3rem !important;\n }\n .mt-lg-n5,\n .my-lg-n5 {\n margin-top: -3rem !important;\n }\n .mr-lg-n5,\n .mx-lg-n5 {\n margin-right: -3rem !important;\n }\n .mb-lg-n5,\n .my-lg-n5 {\n margin-bottom: -3rem !important;\n }\n .ml-lg-n5,\n .mx-lg-n5 {\n margin-left: -3rem !important;\n }\n .m-lg-auto {\n margin: auto !important;\n }\n .mt-lg-auto,\n .my-lg-auto {\n margin-top: auto !important;\n }\n .mr-lg-auto,\n .mx-lg-auto {\n margin-right: auto !important;\n }\n .mb-lg-auto,\n .my-lg-auto {\n margin-bottom: auto !important;\n }\n .ml-lg-auto,\n .mx-lg-auto {\n margin-left: auto !important;\n }\n}\n\n@media (min-width: 1200px) {\n .m-xl-0 {\n margin: 0 !important;\n }\n .mt-xl-0,\n .my-xl-0 {\n margin-top: 0 !important;\n }\n .mr-xl-0,\n .mx-xl-0 {\n margin-right: 0 !important;\n }\n .mb-xl-0,\n .my-xl-0 {\n margin-bottom: 0 !important;\n }\n .ml-xl-0,\n .mx-xl-0 {\n margin-left: 0 !important;\n }\n .m-xl-1 {\n margin: 0.25rem !important;\n }\n .mt-xl-1,\n .my-xl-1 {\n margin-top: 0.25rem !important;\n }\n .mr-xl-1,\n .mx-xl-1 {\n margin-right: 0.25rem !important;\n }\n .mb-xl-1,\n .my-xl-1 {\n margin-bottom: 0.25rem !important;\n }\n .ml-xl-1,\n .mx-xl-1 {\n margin-left: 0.25rem !important;\n }\n .m-xl-2 {\n margin: 0.5rem !important;\n }\n .mt-xl-2,\n .my-xl-2 {\n margin-top: 0.5rem !important;\n }\n .mr-xl-2,\n .mx-xl-2 {\n margin-right: 0.5rem !important;\n }\n .mb-xl-2,\n .my-xl-2 {\n margin-bottom: 0.5rem !important;\n }\n .ml-xl-2,\n .mx-xl-2 {\n margin-left: 0.5rem !important;\n }\n .m-xl-3 {\n margin: 1rem !important;\n }\n .mt-xl-3,\n .my-xl-3 {\n margin-top: 1rem !important;\n }\n .mr-xl-3,\n .mx-xl-3 {\n margin-right: 1rem !important;\n }\n .mb-xl-3,\n .my-xl-3 {\n margin-bottom: 1rem !important;\n }\n .ml-xl-3,\n .mx-xl-3 {\n margin-left: 1rem !important;\n }\n .m-xl-4 {\n margin: 1.5rem !important;\n }\n .mt-xl-4,\n .my-xl-4 {\n margin-top: 1.5rem !important;\n }\n .mr-xl-4,\n .mx-xl-4 {\n margin-right: 1.5rem !important;\n }\n .mb-xl-4,\n .my-xl-4 {\n margin-bottom: 1.5rem !important;\n }\n .ml-xl-4,\n .mx-xl-4 {\n margin-left: 1.5rem !important;\n }\n .m-xl-5 {\n margin: 3rem !important;\n }\n .mt-xl-5,\n .my-xl-5 {\n margin-top: 3rem !important;\n }\n .mr-xl-5,\n .mx-xl-5 {\n margin-right: 3rem !important;\n }\n .mb-xl-5,\n .my-xl-5 {\n margin-bottom: 3rem !important;\n }\n .ml-xl-5,\n .mx-xl-5 {\n margin-left: 3rem !important;\n }\n .p-xl-0 {\n padding: 0 !important;\n }\n .pt-xl-0,\n .py-xl-0 {\n padding-top: 0 !important;\n }\n .pr-xl-0,\n .px-xl-0 {\n padding-right: 0 !important;\n }\n .pb-xl-0,\n .py-xl-0 {\n padding-bottom: 0 !important;\n }\n .pl-xl-0,\n .px-xl-0 {\n padding-left: 0 !important;\n }\n .p-xl-1 {\n padding: 0.25rem !important;\n }\n .pt-xl-1,\n .py-xl-1 {\n padding-top: 0.25rem !important;\n }\n .pr-xl-1,\n .px-xl-1 {\n padding-right: 0.25rem !important;\n }\n .pb-xl-1,\n .py-xl-1 {\n padding-bottom: 0.25rem !important;\n }\n .pl-xl-1,\n .px-xl-1 {\n padding-left: 0.25rem !important;\n }\n .p-xl-2 {\n padding: 0.5rem !important;\n }\n .pt-xl-2,\n .py-xl-2 {\n padding-top: 0.5rem !important;\n }\n .pr-xl-2,\n .px-xl-2 {\n padding-right: 0.5rem !important;\n }\n .pb-xl-2,\n .py-xl-2 {\n padding-bottom: 0.5rem !important;\n }\n .pl-xl-2,\n .px-xl-2 {\n padding-left: 0.5rem !important;\n }\n .p-xl-3 {\n padding: 1rem !important;\n }\n .pt-xl-3,\n .py-xl-3 {\n padding-top: 1rem !important;\n }\n .pr-xl-3,\n .px-xl-3 {\n padding-right: 1rem !important;\n }\n .pb-xl-3,\n .py-xl-3 {\n padding-bottom: 1rem !important;\n }\n .pl-xl-3,\n .px-xl-3 {\n padding-left: 1rem !important;\n }\n .p-xl-4 {\n padding: 1.5rem !important;\n }\n .pt-xl-4,\n .py-xl-4 {\n padding-top: 1.5rem !important;\n }\n .pr-xl-4,\n .px-xl-4 {\n padding-right: 1.5rem !important;\n }\n .pb-xl-4,\n .py-xl-4 {\n padding-bottom: 1.5rem !important;\n }\n .pl-xl-4,\n .px-xl-4 {\n padding-left: 1.5rem !important;\n }\n .p-xl-5 {\n padding: 3rem !important;\n }\n .pt-xl-5,\n .py-xl-5 {\n padding-top: 3rem !important;\n }\n .pr-xl-5,\n .px-xl-5 {\n padding-right: 3rem !important;\n }\n .pb-xl-5,\n .py-xl-5 {\n padding-bottom: 3rem !important;\n }\n .pl-xl-5,\n .px-xl-5 {\n padding-left: 3rem !important;\n }\n .m-xl-n1 {\n margin: -0.25rem !important;\n }\n .mt-xl-n1,\n .my-xl-n1 {\n margin-top: -0.25rem !important;\n }\n .mr-xl-n1,\n .mx-xl-n1 {\n margin-right: -0.25rem !important;\n }\n .mb-xl-n1,\n .my-xl-n1 {\n margin-bottom: -0.25rem !important;\n }\n .ml-xl-n1,\n .mx-xl-n1 {\n margin-left: -0.25rem !important;\n }\n .m-xl-n2 {\n margin: -0.5rem !important;\n }\n .mt-xl-n2,\n .my-xl-n2 {\n margin-top: -0.5rem !important;\n }\n .mr-xl-n2,\n .mx-xl-n2 {\n margin-right: -0.5rem !important;\n }\n .mb-xl-n2,\n .my-xl-n2 {\n margin-bottom: -0.5rem !important;\n }\n .ml-xl-n2,\n .mx-xl-n2 {\n margin-left: -0.5rem !important;\n }\n .m-xl-n3 {\n margin: -1rem !important;\n }\n .mt-xl-n3,\n .my-xl-n3 {\n margin-top: -1rem !important;\n }\n .mr-xl-n3,\n .mx-xl-n3 {\n margin-right: -1rem !important;\n }\n .mb-xl-n3,\n .my-xl-n3 {\n margin-bottom: -1rem !important;\n }\n .ml-xl-n3,\n .mx-xl-n3 {\n margin-left: -1rem !important;\n }\n .m-xl-n4 {\n margin: -1.5rem !important;\n }\n .mt-xl-n4,\n .my-xl-n4 {\n margin-top: -1.5rem !important;\n }\n .mr-xl-n4,\n .mx-xl-n4 {\n margin-right: -1.5rem !important;\n }\n .mb-xl-n4,\n .my-xl-n4 {\n margin-bottom: -1.5rem !important;\n }\n .ml-xl-n4,\n .mx-xl-n4 {\n margin-left: -1.5rem !important;\n }\n .m-xl-n5 {\n margin: -3rem !important;\n }\n .mt-xl-n5,\n .my-xl-n5 {\n margin-top: -3rem !important;\n }\n .mr-xl-n5,\n .mx-xl-n5 {\n margin-right: -3rem !important;\n }\n .mb-xl-n5,\n .my-xl-n5 {\n margin-bottom: -3rem !important;\n }\n .ml-xl-n5,\n .mx-xl-n5 {\n margin-left: -3rem !important;\n }\n .m-xl-auto {\n margin: auto !important;\n }\n .mt-xl-auto,\n .my-xl-auto {\n margin-top: auto !important;\n }\n .mr-xl-auto,\n .mx-xl-auto {\n margin-right: auto !important;\n }\n .mb-xl-auto,\n .my-xl-auto {\n margin-bottom: auto !important;\n }\n .ml-xl-auto,\n .mx-xl-auto {\n margin-left: auto !important;\n }\n}\n\n.text-monospace {\n font-family: SFMono-Regular, Menlo, Monaco, Consolas, \"Liberation Mono\", \"Courier New\", monospace !important;\n}\n\n.text-justify {\n text-align: justify !important;\n}\n\n.text-wrap {\n white-space: normal !important;\n}\n\n.text-nowrap {\n white-space: nowrap !important;\n}\n\n.text-truncate {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.text-left {\n text-align: left !important;\n}\n\n.text-right {\n text-align: right !important;\n}\n\n.text-center {\n text-align: center !important;\n}\n\n@media (min-width: 576px) {\n .text-sm-left {\n text-align: left !important;\n }\n .text-sm-right {\n text-align: right !important;\n }\n .text-sm-center {\n text-align: center !important;\n }\n}\n\n@media (min-width: 768px) {\n .text-md-left {\n text-align: left !important;\n }\n .text-md-right {\n text-align: right !important;\n }\n .text-md-center {\n text-align: center !important;\n }\n}\n\n@media (min-width: 992px) {\n .text-lg-left {\n text-align: left !important;\n }\n .text-lg-right {\n text-align: right !important;\n }\n .text-lg-center {\n text-align: center !important;\n }\n}\n\n@media (min-width: 1200px) {\n .text-xl-left {\n text-align: left !important;\n }\n .text-xl-right {\n text-align: right !important;\n }\n .text-xl-center {\n text-align: center !important;\n }\n}\n\n.text-lowercase {\n text-transform: lowercase !important;\n}\n\n.text-uppercase {\n text-transform: uppercase !important;\n}\n\n.text-capitalize {\n text-transform: capitalize !important;\n}\n\n.font-weight-light {\n font-weight: 300 !important;\n}\n\n.font-weight-lighter {\n font-weight: lighter !important;\n}\n\n.font-weight-normal {\n font-weight: 400 !important;\n}\n\n.font-weight-bold {\n font-weight: 700 !important;\n}\n\n.font-weight-bolder {\n font-weight: bolder !important;\n}\n\n.font-italic {\n font-style: italic !important;\n}\n\n.text-white {\n color: #fff !important;\n}\n\n.text-primary {\n color: #007bff !important;\n}\n\na.text-primary:hover, a.text-primary:focus {\n color: #0056b3 !important;\n}\n\n.text-secondary {\n color: #6c757d !important;\n}\n\na.text-secondary:hover, a.text-secondary:focus {\n color: #494f54 !important;\n}\n\n.text-success {\n color: #28a745 !important;\n}\n\na.text-success:hover, a.text-success:focus {\n color: #19692c !important;\n}\n\n.text-info {\n color: #17a2b8 !important;\n}\n\na.text-info:hover, a.text-info:focus {\n color: #0f6674 !important;\n}\n\n.text-warning {\n color: #ffc107 !important;\n}\n\na.text-warning:hover, a.text-warning:focus {\n color: #ba8b00 !important;\n}\n\n.text-danger {\n color: #dc3545 !important;\n}\n\na.text-danger:hover, a.text-danger:focus {\n color: #a71d2a !important;\n}\n\n.text-light {\n color: #f8f9fa !important;\n}\n\na.text-light:hover, a.text-light:focus {\n color: #cbd3da !important;\n}\n\n.text-dark {\n color: #343a40 !important;\n}\n\na.text-dark:hover, a.text-dark:focus {\n color: #121416 !important;\n}\n\n.text-body {\n color: #212529 !important;\n}\n\n.text-muted {\n color: #6c757d !important;\n}\n\n.text-black-50 {\n color: rgba(0, 0, 0, 0.5) !important;\n}\n\n.text-white-50 {\n color: rgba(255, 255, 255, 0.5) !important;\n}\n\n.text-hide {\n font: 0/0 a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n\n.text-decoration-none {\n text-decoration: none !important;\n}\n\n.text-break {\n word-break: break-word !important;\n overflow-wrap: break-word !important;\n}\n\n.text-reset {\n color: inherit !important;\n}\n\n.visible {\n visibility: visible !important;\n}\n\n.invisible {\n visibility: hidden !important;\n}\n\n@media print {\n *,\n *::before,\n *::after {\n text-shadow: none !important;\n box-shadow: none !important;\n }\n a:not(.btn) {\n text-decoration: underline;\n }\n abbr[title]::after {\n content: \" (\" attr(title) \")\";\n }\n pre {\n white-space: pre-wrap !important;\n }\n pre,\n blockquote {\n border: 1px solid #adb5bd;\n page-break-inside: avoid;\n }\n thead {\n display: table-header-group;\n }\n tr,\n img {\n page-break-inside: avoid;\n }\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n h2,\n h3 {\n page-break-after: avoid;\n }\n @page {\n size: a3;\n }\n body {\n min-width: 992px !important;\n }\n .container {\n min-width: 992px !important;\n }\n .navbar {\n display: none;\n }\n .badge {\n border: 1px solid #000;\n }\n .table {\n border-collapse: collapse !important;\n }\n .table td,\n .table th {\n background-color: #fff !important;\n }\n .table-bordered th,\n .table-bordered td {\n border: 1px solid #dee2e6 !important;\n }\n .table-dark {\n color: inherit;\n }\n .table-dark th,\n .table-dark td,\n .table-dark thead th,\n .table-dark tbody + tbody {\n border-color: #dee2e6;\n }\n .table .thead-dark th {\n color: inherit;\n border-color: #dee2e6;\n }\n}\n\n/*# sourceMappingURL=bootstrap.css.map */","// Hover mixin and `$enable-hover-media-query` are deprecated.\n//\n// Originally added during our alphas and maintained during betas, this mixin was\n// designed to prevent `:hover` stickiness on iOS-an issue where hover styles\n// would persist after initial touch.\n//\n// For backward compatibility, we've kept these mixins and updated them to\n// always return their regular pseudo-classes instead of a shimmed media query.\n//\n// Issue: https://github.com/twbs/bootstrap/issues/25195\n\n@mixin hover {\n &:hover { @content; }\n}\n\n@mixin hover-focus {\n &:hover,\n &:focus {\n @content;\n }\n}\n\n@mixin plain-hover-focus {\n &,\n &:hover,\n &:focus {\n @content;\n }\n}\n\n@mixin hover-focus-active {\n &:hover,\n &:focus,\n &:active {\n @content;\n }\n}\n","// stylelint-disable declaration-no-important, selector-list-comma-newline-after\n\n//\n// Headings\n//\n\nh1, h2, h3, h4, h5, h6,\n.h1, .h2, .h3, .h4, .h5, .h6 {\n margin-bottom: $headings-margin-bottom;\n font-family: $headings-font-family;\n font-weight: $headings-font-weight;\n line-height: $headings-line-height;\n color: $headings-color;\n}\n\nh1, .h1 { @include font-size($h1-font-size); }\nh2, .h2 { @include font-size($h2-font-size); }\nh3, .h3 { @include font-size($h3-font-size); }\nh4, .h4 { @include font-size($h4-font-size); }\nh5, .h5 { @include font-size($h5-font-size); }\nh6, .h6 { @include font-size($h6-font-size); }\n\n.lead {\n @include font-size($lead-font-size);\n font-weight: $lead-font-weight;\n}\n\n// Type display classes\n.display-1 {\n @include font-size($display1-size);\n font-weight: $display1-weight;\n line-height: $display-line-height;\n}\n.display-2 {\n @include font-size($display2-size);\n font-weight: $display2-weight;\n line-height: $display-line-height;\n}\n.display-3 {\n @include font-size($display3-size);\n font-weight: $display3-weight;\n line-height: $display-line-height;\n}\n.display-4 {\n @include font-size($display4-size);\n font-weight: $display4-weight;\n line-height: $display-line-height;\n}\n\n\n//\n// Horizontal rules\n//\n\nhr {\n margin-top: $hr-margin-y;\n margin-bottom: $hr-margin-y;\n border: 0;\n border-top: $hr-border-width solid $hr-border-color;\n}\n\n\n//\n// Emphasis\n//\n\nsmall,\n.small {\n @include font-size($small-font-size);\n font-weight: $font-weight-normal;\n}\n\nmark,\n.mark {\n padding: $mark-padding;\n background-color: $mark-bg;\n}\n\n\n//\n// Lists\n//\n\n.list-unstyled {\n @include list-unstyled;\n}\n\n// Inline turns list items into inline-block\n.list-inline {\n @include list-unstyled;\n}\n.list-inline-item {\n display: inline-block;\n\n &:not(:last-child) {\n margin-right: $list-inline-padding;\n }\n}\n\n\n//\n// Misc\n//\n\n// Builds on `abbr`\n.initialism {\n @include font-size(90%);\n text-transform: uppercase;\n}\n\n// Blockquotes\n.blockquote {\n margin-bottom: $spacer;\n @include font-size($blockquote-font-size);\n}\n\n.blockquote-footer {\n display: block;\n @include font-size($blockquote-small-font-size);\n color: $blockquote-small-color;\n\n &::before {\n content: \"\\2014\\00A0\"; // em dash, nbsp\n }\n}\n","// Lists\n\n// Unstyled keeps list items block level, just removes default browser padding and list-style\n@mixin list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n","// Responsive images (ensure images don't scale beyond their parents)\n//\n// This is purposefully opt-in via an explicit class rather than being the default for all ``s.\n// We previously tried the \"images are responsive by default\" approach in Bootstrap v2,\n// and abandoned it in Bootstrap v3 because it breaks lots of third-party widgets (including Google Maps)\n// which weren't expecting the images within themselves to be involuntarily resized.\n// See also https://github.com/twbs/bootstrap/issues/18178\n.img-fluid {\n @include img-fluid;\n}\n\n\n// Image thumbnails\n.img-thumbnail {\n padding: $thumbnail-padding;\n background-color: $thumbnail-bg;\n border: $thumbnail-border-width solid $thumbnail-border-color;\n @include border-radius($thumbnail-border-radius);\n @include box-shadow($thumbnail-box-shadow);\n\n // Keep them at most 100% wide\n @include img-fluid;\n}\n\n//\n// Figures\n//\n\n.figure {\n // Ensures the caption's text aligns with the image.\n display: inline-block;\n}\n\n.figure-img {\n margin-bottom: $spacer / 2;\n line-height: 1;\n}\n\n.figure-caption {\n @include font-size($figure-caption-font-size);\n color: $figure-caption-color;\n}\n","// Image Mixins\n// - Responsive image\n// - Retina image\n\n\n// Responsive image\n//\n// Keep images from scaling beyond the width of their parents.\n\n@mixin img-fluid {\n // Part 1: Set a maximum relative to the parent\n max-width: 100%;\n // Part 2: Override the height to auto, otherwise images will be stretched\n // when setting a width and height attribute on the img element.\n height: auto;\n}\n\n\n// Retina image\n//\n// Short retina mixin for setting background-image and -size.\n\n@mixin img-retina($file-1x, $file-2x, $width-1x, $height-1x) {\n background-image: url($file-1x);\n\n // Autoprefixer takes care of adding -webkit-min-device-pixel-ratio and -o-min-device-pixel-ratio,\n // but doesn't convert dppx=>dpi.\n // There's no such thing as unprefixed min-device-pixel-ratio since it's nonstandard.\n // Compatibility info: https://caniuse.com/#feat=css-media-resolution\n @media only screen and (min-resolution: 192dpi), // IE9-11 don't support dppx\n only screen and (min-resolution: 2dppx) { // Standardized\n background-image: url($file-2x);\n background-size: $width-1x $height-1x;\n }\n @include deprecate(\"`img-retina()`\", \"v4.3.0\", \"v5\");\n}\n","// stylelint-disable property-blacklist\n// Single side border-radius\n\n@mixin border-radius($radius: $border-radius, $fallback-border-radius: false) {\n @if $enable-rounded {\n border-radius: $radius;\n }\n @else if $fallback-border-radius != false {\n border-radius: $fallback-border-radius;\n }\n}\n\n@mixin border-top-radius($radius) {\n @if $enable-rounded {\n border-top-left-radius: $radius;\n border-top-right-radius: $radius;\n }\n}\n\n@mixin border-right-radius($radius) {\n @if $enable-rounded {\n border-top-right-radius: $radius;\n border-bottom-right-radius: $radius;\n }\n}\n\n@mixin border-bottom-radius($radius) {\n @if $enable-rounded {\n border-bottom-right-radius: $radius;\n border-bottom-left-radius: $radius;\n }\n}\n\n@mixin border-left-radius($radius) {\n @if $enable-rounded {\n border-top-left-radius: $radius;\n border-bottom-left-radius: $radius;\n }\n}\n\n@mixin border-top-left-radius($radius) {\n @if $enable-rounded {\n border-top-left-radius: $radius;\n }\n}\n\n@mixin border-top-right-radius($radius) {\n @if $enable-rounded {\n border-top-right-radius: $radius;\n }\n}\n\n@mixin border-bottom-right-radius($radius) {\n @if $enable-rounded {\n border-bottom-right-radius: $radius;\n }\n}\n\n@mixin border-bottom-left-radius($radius) {\n @if $enable-rounded {\n border-bottom-left-radius: $radius;\n }\n}\n","// Inline code\ncode {\n @include font-size($code-font-size);\n color: $code-color;\n word-break: break-word;\n\n // Streamline the style when inside anchors to avoid broken underline and more\n a > & {\n color: inherit;\n }\n}\n\n// User input typically entered via keyboard\nkbd {\n padding: $kbd-padding-y $kbd-padding-x;\n @include font-size($kbd-font-size);\n color: $kbd-color;\n background-color: $kbd-bg;\n @include border-radius($border-radius-sm);\n @include box-shadow($kbd-box-shadow);\n\n kbd {\n padding: 0;\n @include font-size(100%);\n font-weight: $nested-kbd-font-weight;\n @include box-shadow(none);\n }\n}\n\n// Blocks of code\npre {\n display: block;\n @include font-size($code-font-size);\n color: $pre-color;\n\n // Account for some code outputs that place code tags in pre tags\n code {\n @include font-size(inherit);\n color: inherit;\n word-break: normal;\n }\n}\n\n// Enable scrollable blocks of code\n.pre-scrollable {\n max-height: $pre-scrollable-max-height;\n overflow-y: scroll;\n}\n","// Container widths\n//\n// Set the container width, and override it for fixed navbars in media queries.\n\n@if $enable-grid-classes {\n .container {\n @include make-container();\n @include make-container-max-widths();\n }\n}\n\n// Fluid container\n//\n// Utilizes the mixin meant for fixed width containers, but with 100% width for\n// fluid, full width layouts.\n\n@if $enable-grid-classes {\n .container-fluid {\n @include make-container();\n }\n}\n\n// Row\n//\n// Rows contain and clear the floats of your columns.\n\n@if $enable-grid-classes {\n .row {\n @include make-row();\n }\n\n // Remove the negative margin from default .row, then the horizontal padding\n // from all immediate children columns (to prevent runaway style inheritance).\n .no-gutters {\n margin-right: 0;\n margin-left: 0;\n\n > .col,\n > [class*=\"col-\"] {\n padding-right: 0;\n padding-left: 0;\n }\n }\n}\n\n// Columns\n//\n// Common styles for small and large grid columns\n\n@if $enable-grid-classes {\n @include make-grid-columns();\n}\n","/// Grid system\n//\n// Generate semantic grid columns with these mixins.\n\n@mixin make-container($gutter: $grid-gutter-width) {\n width: 100%;\n padding-right: $gutter / 2;\n padding-left: $gutter / 2;\n margin-right: auto;\n margin-left: auto;\n}\n\n\n// For each breakpoint, define the maximum width of the container in a media query\n@mixin make-container-max-widths($max-widths: $container-max-widths, $breakpoints: $grid-breakpoints) {\n @each $breakpoint, $container-max-width in $max-widths {\n @include media-breakpoint-up($breakpoint, $breakpoints) {\n max-width: $container-max-width;\n }\n }\n}\n\n@mixin make-row($gutter: $grid-gutter-width) {\n display: flex;\n flex-wrap: wrap;\n margin-right: -$gutter / 2;\n margin-left: -$gutter / 2;\n}\n\n@mixin make-col-ready($gutter: $grid-gutter-width) {\n position: relative;\n // Prevent columns from becoming too narrow when at smaller grid tiers by\n // always setting `width: 100%;`. This works because we use `flex` values\n // later on to override this initial width.\n width: 100%;\n padding-right: $gutter / 2;\n padding-left: $gutter / 2;\n}\n\n@mixin make-col($size, $columns: $grid-columns) {\n flex: 0 0 percentage($size / $columns);\n // Add a `max-width` to ensure content within each column does not blow out\n // the width of the column. Applies to IE10+ and Firefox. Chrome and Safari\n // do not appear to require this.\n max-width: percentage($size / $columns);\n}\n\n@mixin make-col-offset($size, $columns: $grid-columns) {\n $num: $size / $columns;\n margin-left: if($num == 0, 0, percentage($num));\n}\n","// Breakpoint viewport sizes and media queries.\n//\n// Breakpoints are defined as a map of (name: minimum width), order from small to large:\n//\n// (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px)\n//\n// The map defined in the `$grid-breakpoints` global variable is used as the `$breakpoints` argument by default.\n\n// Name of the next breakpoint, or null for the last breakpoint.\n//\n// >> breakpoint-next(sm)\n// md\n// >> breakpoint-next(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n// md\n// >> breakpoint-next(sm, $breakpoint-names: (xs sm md lg xl))\n// md\n@function breakpoint-next($name, $breakpoints: $grid-breakpoints, $breakpoint-names: map-keys($breakpoints)) {\n $n: index($breakpoint-names, $name);\n @return if($n != null and $n < length($breakpoint-names), nth($breakpoint-names, $n + 1), null);\n}\n\n// Minimum breakpoint width. Null for the smallest (first) breakpoint.\n//\n// >> breakpoint-min(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n// 576px\n@function breakpoint-min($name, $breakpoints: $grid-breakpoints) {\n $min: map-get($breakpoints, $name);\n @return if($min != 0, $min, null);\n}\n\n// Maximum breakpoint width. Null for the largest (last) breakpoint.\n// The maximum value is calculated as the minimum of the next one less 0.02px\n// to work around the limitations of `min-` and `max-` prefixes and viewports with fractional widths.\n// See https://www.w3.org/TR/mediaqueries-4/#mq-min-max\n// Uses 0.02px rather than 0.01px to work around a current rounding bug in Safari.\n// See https://bugs.webkit.org/show_bug.cgi?id=178261\n//\n// >> breakpoint-max(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n// 767.98px\n@function breakpoint-max($name, $breakpoints: $grid-breakpoints) {\n $next: breakpoint-next($name, $breakpoints);\n @return if($next, breakpoint-min($next, $breakpoints) - .02, null);\n}\n\n// Returns a blank string if smallest breakpoint, otherwise returns the name with a dash in front.\n// Useful for making responsive utilities.\n//\n// >> breakpoint-infix(xs, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n// \"\" (Returns a blank string)\n// >> breakpoint-infix(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px))\n// \"-sm\"\n@function breakpoint-infix($name, $breakpoints: $grid-breakpoints) {\n @return if(breakpoint-min($name, $breakpoints) == null, \"\", \"-#{$name}\");\n}\n\n// Media of at least the minimum breakpoint width. No query for the smallest breakpoint.\n// Makes the @content apply to the given breakpoint and wider.\n@mixin media-breakpoint-up($name, $breakpoints: $grid-breakpoints) {\n $min: breakpoint-min($name, $breakpoints);\n @if $min {\n @media (min-width: $min) {\n @content;\n }\n } @else {\n @content;\n }\n}\n\n// Media of at most the maximum breakpoint width. No query for the largest breakpoint.\n// Makes the @content apply to the given breakpoint and narrower.\n@mixin media-breakpoint-down($name, $breakpoints: $grid-breakpoints) {\n $max: breakpoint-max($name, $breakpoints);\n @if $max {\n @media (max-width: $max) {\n @content;\n }\n } @else {\n @content;\n }\n}\n\n// Media that spans multiple breakpoint widths.\n// Makes the @content apply between the min and max breakpoints\n@mixin media-breakpoint-between($lower, $upper, $breakpoints: $grid-breakpoints) {\n $min: breakpoint-min($lower, $breakpoints);\n $max: breakpoint-max($upper, $breakpoints);\n\n @if $min != null and $max != null {\n @media (min-width: $min) and (max-width: $max) {\n @content;\n }\n } @else if $max == null {\n @include media-breakpoint-up($lower, $breakpoints) {\n @content;\n }\n } @else if $min == null {\n @include media-breakpoint-down($upper, $breakpoints) {\n @content;\n }\n }\n}\n\n// Media between the breakpoint's minimum and maximum widths.\n// No minimum for the smallest breakpoint, and no maximum for the largest one.\n// Makes the @content apply only to the given breakpoint, not viewports any wider or narrower.\n@mixin media-breakpoint-only($name, $breakpoints: $grid-breakpoints) {\n $min: breakpoint-min($name, $breakpoints);\n $max: breakpoint-max($name, $breakpoints);\n\n @if $min != null and $max != null {\n @media (min-width: $min) and (max-width: $max) {\n @content;\n }\n } @else if $max == null {\n @include media-breakpoint-up($name, $breakpoints) {\n @content;\n }\n } @else if $min == null {\n @include media-breakpoint-down($name, $breakpoints) {\n @content;\n }\n }\n}\n","// Framework grid generation\n//\n// Used only by Bootstrap to generate the correct number of grid classes given\n// any value of `$grid-columns`.\n\n@mixin make-grid-columns($columns: $grid-columns, $gutter: $grid-gutter-width, $breakpoints: $grid-breakpoints) {\n // Common properties for all breakpoints\n %grid-column {\n position: relative;\n width: 100%;\n padding-right: $gutter / 2;\n padding-left: $gutter / 2;\n }\n\n @each $breakpoint in map-keys($breakpoints) {\n $infix: breakpoint-infix($breakpoint, $breakpoints);\n\n // Allow columns to stretch full width below their breakpoints\n @for $i from 1 through $columns {\n .col#{$infix}-#{$i} {\n @extend %grid-column;\n }\n }\n .col#{$infix},\n .col#{$infix}-auto {\n @extend %grid-column;\n }\n\n @include media-breakpoint-up($breakpoint, $breakpoints) {\n // Provide basic `.col-{bp}` classes for equal-width flexbox columns\n .col#{$infix} {\n flex-basis: 0;\n flex-grow: 1;\n max-width: 100%;\n }\n .col#{$infix}-auto {\n flex: 0 0 auto;\n width: auto;\n max-width: 100%; // Reset earlier grid tiers\n }\n\n @for $i from 1 through $columns {\n .col#{$infix}-#{$i} {\n @include make-col($i, $columns);\n }\n }\n\n .order#{$infix}-first { order: -1; }\n\n .order#{$infix}-last { order: $columns + 1; }\n\n @for $i from 0 through $columns {\n .order#{$infix}-#{$i} { order: $i; }\n }\n\n // `$columns - 1` because offsetting by the width of an entire row isn't possible\n @for $i from 0 through ($columns - 1) {\n @if not ($infix == \"\" and $i == 0) { // Avoid emitting useless .offset-0\n .offset#{$infix}-#{$i} {\n @include make-col-offset($i, $columns);\n }\n }\n }\n }\n }\n}\n","//\n// Basic Bootstrap table\n//\n\n.table {\n width: 100%;\n margin-bottom: $spacer;\n color: $table-color;\n background-color: $table-bg; // Reset for nesting within parents with `background-color`.\n\n th,\n td {\n padding: $table-cell-padding;\n vertical-align: top;\n border-top: $table-border-width solid $table-border-color;\n }\n\n thead th {\n vertical-align: bottom;\n border-bottom: (2 * $table-border-width) solid $table-border-color;\n }\n\n tbody + tbody {\n border-top: (2 * $table-border-width) solid $table-border-color;\n }\n}\n\n\n//\n// Condensed table w/ half padding\n//\n\n.table-sm {\n th,\n td {\n padding: $table-cell-padding-sm;\n }\n}\n\n\n// Border versions\n//\n// Add or remove borders all around the table and between all the columns.\n\n.table-bordered {\n border: $table-border-width solid $table-border-color;\n\n th,\n td {\n border: $table-border-width solid $table-border-color;\n }\n\n thead {\n th,\n td {\n border-bottom-width: 2 * $table-border-width;\n }\n }\n}\n\n.table-borderless {\n th,\n td,\n thead th,\n tbody + tbody {\n border: 0;\n }\n}\n\n// Zebra-striping\n//\n// Default zebra-stripe styles (alternating gray and transparent backgrounds)\n\n.table-striped {\n tbody tr:nth-of-type(#{$table-striped-order}) {\n background-color: $table-accent-bg;\n }\n}\n\n\n// Hover effect\n//\n// Placed here since it has to come after the potential zebra striping\n\n.table-hover {\n tbody tr {\n @include hover {\n color: $table-hover-color;\n background-color: $table-hover-bg;\n }\n }\n}\n\n\n// Table backgrounds\n//\n// Exact selectors below required to override `.table-striped` and prevent\n// inheritance to nested tables.\n\n@each $color, $value in $theme-colors {\n @include table-row-variant($color, theme-color-level($color, $table-bg-level), theme-color-level($color, $table-border-level));\n}\n\n@include table-row-variant(active, $table-active-bg);\n\n\n// Dark styles\n//\n// Same table markup, but inverted color scheme: dark background and light text.\n\n// stylelint-disable-next-line no-duplicate-selectors\n.table {\n .thead-dark {\n th {\n color: $table-dark-color;\n background-color: $table-dark-bg;\n border-color: $table-dark-border-color;\n }\n }\n\n .thead-light {\n th {\n color: $table-head-color;\n background-color: $table-head-bg;\n border-color: $table-border-color;\n }\n }\n}\n\n.table-dark {\n color: $table-dark-color;\n background-color: $table-dark-bg;\n\n th,\n td,\n thead th {\n border-color: $table-dark-border-color;\n }\n\n &.table-bordered {\n border: 0;\n }\n\n &.table-striped {\n tbody tr:nth-of-type(odd) {\n background-color: $table-dark-accent-bg;\n }\n }\n\n &.table-hover {\n tbody tr {\n @include hover {\n color: $table-dark-hover-color;\n background-color: $table-dark-hover-bg;\n }\n }\n }\n}\n\n\n// Responsive tables\n//\n// Generate series of `.table-responsive-*` classes for configuring the screen\n// size of where your table will overflow.\n\n.table-responsive {\n @each $breakpoint in map-keys($grid-breakpoints) {\n $next: breakpoint-next($breakpoint, $grid-breakpoints);\n $infix: breakpoint-infix($next, $grid-breakpoints);\n\n &#{$infix} {\n @include media-breakpoint-down($breakpoint) {\n display: block;\n width: 100%;\n overflow-x: auto;\n -webkit-overflow-scrolling: touch;\n\n // Prevent double border on horizontal scroll due to use of `display: block;`\n > .table-bordered {\n border: 0;\n }\n }\n }\n }\n}\n","// Tables\n\n@mixin table-row-variant($state, $background, $border: null) {\n // Exact selectors below required to override `.table-striped` and prevent\n // inheritance to nested tables.\n .table-#{$state} {\n &,\n > th,\n > td {\n background-color: $background;\n }\n\n @if $border != null {\n th,\n td,\n thead th,\n tbody + tbody {\n border-color: $border;\n }\n }\n }\n\n // Hover states for `.table-hover`\n // Note: this is not available for cells or rows within `thead` or `tfoot`.\n .table-hover {\n $hover-background: darken($background, 5%);\n\n .table-#{$state} {\n @include hover {\n background-color: $hover-background;\n\n > td,\n > th {\n background-color: $hover-background;\n }\n }\n }\n }\n}\n","// stylelint-disable selector-no-qualifying-type\n\n//\n// Textual form controls\n//\n\n.form-control {\n display: block;\n width: 100%;\n height: $input-height;\n padding: $input-padding-y $input-padding-x;\n font-family: $input-font-family;\n @include font-size($input-font-size);\n font-weight: $input-font-weight;\n line-height: $input-line-height;\n color: $input-color;\n background-color: $input-bg;\n background-clip: padding-box;\n border: $input-border-width solid $input-border-color;\n\n // Note: This has no effect on `s in CSS.\n @include border-radius($input-border-radius, 0);\n\n @include box-shadow($input-box-shadow);\n @include transition($input-transition);\n\n // Unstyle the caret on ` receives focus\n // in IE and (under certain conditions) Edge, as it looks bad and cannot be made to\n // match the appearance of the native widget.\n // See https://github.com/twbs/bootstrap/issues/19398.\n color: $input-color;\n background-color: $input-bg;\n }\n}\n\n// Make file inputs better match text inputs by forcing them to new lines.\n.form-control-file,\n.form-control-range {\n display: block;\n width: 100%;\n}\n\n\n//\n// Labels\n//\n\n// For use with horizontal and inline forms, when you need the label (or legend)\n// text to align with the form controls.\n.col-form-label {\n padding-top: calc(#{$input-padding-y} + #{$input-border-width});\n padding-bottom: calc(#{$input-padding-y} + #{$input-border-width});\n margin-bottom: 0; // Override the `
+ @if (showSearchConfiguration) + { +
+

Configuration

+
+
+
+
Shop Quantity
+
How many results from each store?
+

This is the maximum number of results we gather for each store we have access to. The larger the result, the longer it takes to load search queries.

+
+ + +
+
+
+
+
+
Currency
+
What currency would you like results in?
+

The currency displayed may either be from the online store directly, or through currency conversion (we'll put a little tag beside the coonverted ones).

+
+
+ +
+ +
+
+
+
+
+
Minimum Rating
+
We'll crop out the lower rated stuff.
+

We'll only show products that have a rating greater than or equal to the set minimum rating. Optionally, we can also show those that don't have rating.

+
+ + +
+
+ + +
+
+
+
+
+
Price Range
+
Whats your budget?
+

Results will be pruned of budgets that fall outside of the designated range. The checkbox can enable or disable the upper bound. These bounds do include the shipping price if it's known.

+
+
+
+ +
+ Upper limit +
+ +
+ .00 +
+
+
+
+ Lower limit +
+ +
+ .00 +
+
+
+
+
+
+
Shops Searched
+
What's your preference?
+

We'll only look through shops that are enabled in this list. Of course, at least one shop has to be enabled.

+ @foreach (string shop in Shops.Keys) + { +
+ + +
+ } +
+
+
+
+
Minimum Purchases
+
If they're purchasing, I am too!
+

Only products that have enough purchases are shown. Optionally, we can also show results that don't have a purchase tally.

+
+
+ Minimum purchases +
+ +
+
+ + +
+
+
+
+
+
Minimum Reviews
+
Well if this many people say it's good...
+

Only products with enough reviews/ratings are shown. Optionally, we can also show the results that don't have this information.

+
+
+ Minimum reviews +
+ +
+
+ + +
+
+
+
+
+
Shipping
+
Free shipping?
+

Show results with shipping rates less than a certain value, and choose whether or not to display listings without shipping information.

+
+
+ + + + Max shipping +
+ +
+ .00 +
+
+
+ + +
+
+
+
+
+ } + + +
+
+
+ @if (searching) + { + @if (listings.Count != 0) + { +
+ Loading... +
+ Looked through @resultsChecked listings and found @listings.Count viable results. We're still looking! + } + else + { +
+ Loading... +
+ Hold tight! We're looking through the stores for viable results... + } + } + else if (listings.Count != 0) + { + @if (organizing) + { +
+ Loading... +
+ Organizing the data to your spec... + } + else + { + Looked through @resultsChecked listings and found @listings.Count viable results. + } + } + else if (searched) + { + We've found @resultsChecked listings and unfortunately none matched your search. + } + else + { + Search for something to see the results! + } +
+ + + + + + + + @(item.FriendlyName()) + + + + + +
+
+ +
+
+ + +@code { + [CascadingParameter(Name = "Shops")] + public Dictionary Shops { get; set; } + + [Parameter] + public string Query { get; set; } + + private SearchProfile activeProfile = new SearchProfile(); + private ResultsProfile activeResultsProfile = new ResultsProfile(); + + private bool showSearchConfiguration = false; + private string ToggleSearchConfigButtonCss + { + get => "btn btn-outline-secondary" + (showSearchConfiguration ? " active" : ""); + } + + private bool searched = false; + private bool searching = false; + private bool organizing = false; + + private int resultsChecked = 0; + private List listings = new List(); + + protected override void OnInitialized() + { + foreach (string shop in Shops.Keys) + { + activeProfile.shopStates[shop] = true; + } + base.OnInitialized(); + } + + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + } + + protected override async Task OnParametersSetAsync() + { + if (!string.IsNullOrEmpty(Query)) + { + await PerformSearch(Query); + } + await base.OnParametersSetAsync(); + } + + private async Task PerformSearch(string query) + { + if (string.IsNullOrWhiteSpace(query)) return; + if (searching) return; + searching = true; + Logger.Log($"Received search request for \"{query}\".", LogLevel.Debug); + resultsChecked = 0; + listings.Clear(); + Dictionary> greatest = new Dictionary>(); + foreach (string shopName in Shops.Keys) + { + if (activeProfile.shopStates[shopName]) + { + Logger.Log($"Querying \"{shopName}\" for products."); + Shops[shopName].SetupSession(query, activeProfile.currency); + int shopViableResults = 0; + await foreach (ProductListing listing in Shops[shopName]) + { + resultsChecked += 1; + if (resultsChecked % 50 == 0) { + StateHasChanged(); + await Task.Yield(); + } + + + if (listing.Shipping == null && !activeProfile.keepUnknownShipping || (activeProfile.enableMaxShippingFee && listing.Shipping > activeProfile.MaxShippingFee)) continue; + float shippingDifference = listing.Shipping != null ? listing.Shipping.Value : 0; + if (!(listing.LowerPrice + shippingDifference >= activeProfile.lowerPrice && (!activeProfile.enableUpperPrice || listing.UpperPrice + shippingDifference <= activeProfile.UpperPrice))) continue; + if ((listing.Rating == null && !activeProfile.keepUnrated) || activeProfile.minRating > (listing.Rating == null ? 0 : listing.Rating)) continue; + if ((listing.PurchaseCount == null && !activeProfile.keepUnknownPurchaseCount) || activeProfile.minPurchases > (listing.PurchaseCount == null ? 0 : listing.PurchaseCount)) continue; + if ((listing.ReviewCount == null && !activeProfile.keepUnknownRatingCount) || activeProfile.minReviews > (listing.ReviewCount == null ? 0 : listing.ReviewCount)) continue; + + ProductListingInfo info = new ProductListingInfo(listing, shopName); + listings.Add(info); + await Task.Yield(); + foreach (ResultsProfile.Category c in Enum.GetValues()) + { + if (!greatest.ContainsKey(c)) greatest[c] = new List(); + if (greatest[c].Count > 0) + { + int? compResult = c.CompareListings(info, greatest[c][0]); + if (compResult.HasValue) + { + if (compResult > 0) greatest[c].Clear(); + if (compResult >= 0) greatest[c].Add(info); + } + } + else + { + if (c.CompareListings(info, info).HasValue) + { + greatest[c].Add(info); + } + } + } + + shopViableResults += 1; + if (shopViableResults >= activeProfile.maxResults) break; + } + Logger.Log($"\"{shopName}\" has completed. There are {listings.Count} results in total.", LogLevel.Debug); + } + else + { + Logger.Log($"Skipping {shopName} since it's disabled."); + } + } + searching = false; + searched = true; + + foreach (ResultsProfile.Category c in greatest.Keys) + { + foreach (ProductListingInfo info in greatest[c]) + { + info.Tops.Add(c); + } + } + + await Organize(activeResultsProfile.Order); + } + + private async Task Organize(List order) + { + if (searching) return; + organizing = true; + StateHasChanged(); + + List sortedResults = await Task.Run>(() => { + List sorted = new List(listings); + sorted.Sort((a, b) => + { + foreach (ResultsProfile.Category category in activeResultsProfile.Order) + { + int? compareResult = category.CompareListings(a, b); + if (compareResult.HasValue && compareResult.Value != 0) + { + return -compareResult.Value; + } + } + return 0; + }); + return sorted; + }); + listings.Clear(); + listings.AddRange(sortedResults); + organizing = false; + StateHasChanged(); + } +} diff --git a/src/MultiShop/Program.cs b/src/MultiShop/Program.cs index 8a1ee24..24caf48 100644 --- a/src/MultiShop/Program.cs +++ b/src/MultiShop/Program.cs @@ -1,12 +1,8 @@ using System; using System.Net.Http; -using System.Collections.Generic; using System.Threading.Tasks; -using System.Text; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; using SimpleLogger; namespace MultiShop @@ -15,12 +11,11 @@ namespace MultiShop { public static async Task Main(string[] args) { - Logger.AddLogListener(new ConsoleLogReceiver()); + + Logger.AddLogListener(new ConsoleLogReceiver() {Level = LogLevel.Debug}); var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add("#app"); - builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); - await builder.Build().RunAsync(); } } diff --git a/src/MultiShop/SearchStructures/ProductListingInfo.cs b/src/MultiShop/SearchStructures/ProductListingInfo.cs new file mode 100644 index 0000000..3fdb0dc --- /dev/null +++ b/src/MultiShop/SearchStructures/ProductListingInfo.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using MultiShop.ShopFramework; + +namespace MultiShop.SearchStructures +{ + public class ProductListingInfo + { + public ProductListing Listing { get; private set; } + public string ShopName { get; private set; } + public float RatingToPriceRatio { + get { + int reviewFactor = Listing.ReviewCount.HasValue ? Listing.ReviewCount.Value : 1; + int purchaseFactor = Listing.PurchaseCount.HasValue ? Listing.PurchaseCount.Value : 1; + float ratingFactor = 1 + (Listing.Rating.HasValue ? Listing.Rating.Value : 0); + return (ratingFactor * (reviewFactor > purchaseFactor ? reviewFactor : purchaseFactor))/(Listing.LowerPrice * Listing.UpperPrice); + } + } + public ISet Tops { get; private set; } = new HashSet(); + + public ProductListingInfo(ProductListing listing, string shopName) + { + this.Listing = listing; + this.ShopName = shopName; + } + } +} \ No newline at end of file diff --git a/src/MultiShop/SearchStructures/ResultCategoryExtensions.cs b/src/MultiShop/SearchStructures/ResultCategoryExtensions.cs new file mode 100644 index 0000000..315f845 --- /dev/null +++ b/src/MultiShop/SearchStructures/ResultCategoryExtensions.cs @@ -0,0 +1,46 @@ +using System; +using System.ComponentModel; +using MultiShop.ShopFramework; + +namespace MultiShop.SearchStructures +{ + public static class ResultCategoryExtensions + { + public static int? CompareListings(this ResultsProfile.Category category, ProductListingInfo a, ProductListingInfo b) + { + switch (category) + { + case ResultsProfile.Category.RatingPriceRatio: + float dealDiff = a.RatingToPriceRatio - b.RatingToPriceRatio; + int dealCeil = (int)Math.Ceiling(Math.Abs(dealDiff)); + return dealDiff < 0 ? -dealCeil : dealCeil; + case ResultsProfile.Category.Price: + float priceDiff = b.Listing.UpperPrice - a.Listing.UpperPrice; + int priceCeil = (int)Math.Ceiling(Math.Abs(priceDiff)); + return priceDiff < 0 ? -priceCeil : priceCeil; + case ResultsProfile.Category.Purchases: + return a.Listing.PurchaseCount - b.Listing.PurchaseCount; + case ResultsProfile.Category.Reviews: + return a.Listing.ReviewCount - b.Listing.ReviewCount; + } + + throw new ArgumentException($"{category} does not have a defined comparison."); + } + + public static string FriendlyName(this ResultsProfile.Category category) + { + switch (category) + { + case ResultsProfile.Category.RatingPriceRatio: + return "Best rating to price ratio first"; + case ResultsProfile.Category.Price: + return "Lowest price first"; + case ResultsProfile.Category.Purchases: + return "Most purchases first"; + case ResultsProfile.Category.Reviews: + return "Most reviews first"; + } + throw new ArgumentException($"{category} does not have a friendly name defined."); + } + } +} \ No newline at end of file diff --git a/src/MultiShop/SearchStructures/ResultsProfile.cs b/src/MultiShop/SearchStructures/ResultsProfile.cs new file mode 100644 index 0000000..ac428d4 --- /dev/null +++ b/src/MultiShop/SearchStructures/ResultsProfile.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace MultiShop.SearchStructures +{ + public class ResultsProfile + { + public List Order { get; private set; } = new List(Enum.GetValues().Length); + + public ResultsProfile() + { + foreach (Category category in Enum.GetValues()) + { + Order.Add(category); + } + } + + public Category GetCategory(int position) + { + return Order[position]; + } + + public enum Category + { + RatingPriceRatio, + Price, + Purchases, + Reviews, + } + } +} \ No newline at end of file diff --git a/src/MultiShop/SearchStructures/SearchProfile.cs b/src/MultiShop/SearchStructures/SearchProfile.cs new file mode 100644 index 0000000..e306e9b --- /dev/null +++ b/src/MultiShop/SearchStructures/SearchProfile.cs @@ -0,0 +1,89 @@ +using System.Collections.Generic; +using MultiShop.ShopFramework; + +namespace MultiShop.SearchStructures +{ + public class SearchProfile + { + public Currency currency; + public int maxResults; + public float minRating; + public bool keepUnrated; + public bool enableUpperPrice; + private int upperPrice; + public int UpperPrice + { + get + { + return upperPrice; + } + set + { + if (enableUpperPrice) upperPrice = value; + } + } + public int lowerPrice; + public int minPurchases; + public bool keepUnknownPurchaseCount; + public int minReviews; + public bool keepUnknownRatingCount; + public bool enableMaxShippingFee; + private int maxShippingFee; + public int MaxShippingFee { + get { + return maxShippingFee; + } + set { + if (enableMaxShippingFee) maxShippingFee = value; + } + } + public bool keepUnknownShipping; + public ShopStateTracker shopStates = new ShopStateTracker(); + + public SearchProfile() + { + currency = Currency.CAD; + maxResults = 100; + minRating = 0.8f; + keepUnrated = true; + enableUpperPrice = false; + upperPrice = 0; + lowerPrice = 0; + minPurchases = 0; + keepUnknownPurchaseCount = true; + minReviews = 0; + keepUnknownRatingCount = true; + enableMaxShippingFee = false; + maxShippingFee = 0; + keepUnknownShipping = true; + } + + public class ShopStateTracker + { + private HashSet shopsEnabled = new HashSet(); + public bool this[string name] + { + get + { + return shopsEnabled.Contains(name); + } + + set + { + if (value == false && !CanDisableShop()) return; + if (value) + { + shopsEnabled.Add(name); + } + else + { + shopsEnabled.Remove(name); + } + } + } + public bool CanDisableShop() { + return shopsEnabled.Count > 1; + } + } + } +} \ No newline at end of file diff --git a/src/MultiShop/Shared/CustomDropdown.razor b/src/MultiShop/Shared/CustomDropdown.razor new file mode 100644 index 0000000..60160e8 --- /dev/null +++ b/src/MultiShop/Shared/CustomDropdown.razor @@ -0,0 +1,45 @@ +@inject IJSRuntime JS + +
+ + +
+ +@code { + [Parameter] + public RenderFragment ButtonContent { get; set; } + + [Parameter] + public RenderFragment DropdownContent { get; set; } + + [Parameter] + public string AdditionalButtonClasses { get; set; } + + [Parameter] + public string Justify { get; set; } + + private ElementReference dropdown; + private string ButtonCss + { + get => "btn " + AdditionalButtonClasses; + } + + protected override async Task OnParametersSetAsync() + { + AdditionalButtonClasses = AdditionalButtonClasses ?? ""; + Justify = Justify ?? "center"; + await base.OnParametersSetAsync(); + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + await JS.InvokeVoidAsync("customDropdown", dropdown, Justify); + } + await base.OnAfterRenderAsync(firstRender); + } + +} diff --git a/src/MultiShop/Shared/DragAndDropList.razor b/src/MultiShop/Shared/DragAndDropList.razor new file mode 100644 index 0000000..74f6bd9 --- /dev/null +++ b/src/MultiShop/Shared/DragAndDropList.razor @@ -0,0 +1,65 @@ +@using SimpleLogger +@typeparam TItem +@inject IJSRuntime JS + +
    + @foreach (TItem item in Items) + { +
  • + + @DraggableItem(item) +
  • + } +
+ + +@code { + [Parameter] + public List Items { get; set; } + + [Parameter] + public string AdditionalListClasses { get; set; } + + [Parameter] + public EventCallback OnOrderChange { get; set; } + + [Parameter] + public RenderFragment DraggableItem { get; set; } + + private ElementReference dragAndDrop; + + private int itemDraggedIndex = -1; + + + private string ListGroupCss + { + get => "list-group " + AdditionalListClasses; + } + + protected override async Task OnParametersSetAsync() + { + AdditionalListClasses = AdditionalListClasses ?? ""; + await base.OnParametersSetAsync(); + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + await JS.InvokeVoidAsync("dragAndDropList", dragAndDrop); + } + await base.OnAfterRenderAsync(firstRender); + } + + + private async Task OnDrop(TItem dropped) + { + TItem item = Items[itemDraggedIndex]; + if (item.Equals(dropped)) return; + int indexOfDrop = Items.IndexOf(dropped); + Items.RemoveAt(itemDraggedIndex); + Items.Insert(indexOfDrop, item); + itemDraggedIndex = -1; + await OnOrderChange.InvokeAsync(); + } +} diff --git a/src/MultiShop/Shared/ListingTableView.razor b/src/MultiShop/Shared/ListingTableView.razor new file mode 100644 index 0000000..60e6462 --- /dev/null +++ b/src/MultiShop/Shared/ListingTableView.razor @@ -0,0 +1,99 @@ +@using ShopFramework +@using SearchStructures + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
NamePriceShippingPurchasesRatingReviews
+
@product.Listing.Name
+ From @product.ShopName + @if (product.Listing.ConvertedPrices) + { + Converted price + } + @foreach (ResultsProfile.Category c in product.Tops) + { + @CategoryTags(c) + } +
+ @if (product.Listing.UpperPrice != product.Listing.LowerPrice) + { +
+ @product.Listing.LowerPrice to @product.Listing.UpperPrice +
+ } + else + { +
+ @GetOrNA(product.Listing.LowerPrice) +
+ } +
+
+ @GetOrNA(product.Listing.Shipping) +
+
+
+ @GetOrNA(product.Listing.PurchaseCount) +
+
+
+ @(product.Listing.Rating != null ? string.Format("{0:P2}", product.Listing.Rating) : "N/A") +
+
@GetOrNA(product.Listing.ReviewCount) + View +
+
+ +@code { + + [Parameter] + public List Products { get; set; } + + private string PriceCellHeight { get => "height: " + "4rem"; } + + private string GetOrNA(object data, string prepend = null, string append = null) + { + return data != null ? (prepend + data.ToString() + append) : "N/A"; + } + + private string CategoryTags(ResultsProfile.Category c) + { + switch (c) + { + case ResultsProfile.Category.RatingPriceRatio: + return "Best rating to price ratio"; + case ResultsProfile.Category.Price: + return "Lowest price"; + case ResultsProfile.Category.Purchases: + return "Most purchases"; + case ResultsProfile.Category.Reviews: + return "Most reviews"; + } + throw new ArgumentException($"{c} does not have an associated string."); + } + +} \ No newline at end of file diff --git a/src/MultiShop/Shared/ListingTableView.razor.css b/src/MultiShop/Shared/ListingTableView.razor.css new file mode 100644 index 0000000..d5fc6cc --- /dev/null +++ b/src/MultiShop/Shared/ListingTableView.razor.css @@ -0,0 +1,3 @@ +tbody > tr > th > div { + width: 45em; +} \ No newline at end of file diff --git a/src/MultiShop/Shared/MainLayout.razor b/src/MultiShop/Shared/MainLayout.razor index a76e097..d91bb71 100644 --- a/src/MultiShop/Shared/MainLayout.razor +++ b/src/MultiShop/Shared/MainLayout.razor @@ -1,17 +1,82 @@ +@using ShopFramework +@using SimpleLogger +@using System.Reflection +@using Microsoft.Extensions.Configuration +@inject HttpClient Http +@inject IConfiguration Configuration @inherits LayoutComponentBase +@implements IDisposable
- - +
- -
- @Body + @if (modulesLoaded) + { + + @Body + + }
+ +@code { + private bool modulesLoaded = false; + + private Dictionary shops = new Dictionary(); + + protected override async Task OnInitializedAsync() + { + await DownloadShopModules(); + await base.OnInitializedAsync(); + } + private async Task DownloadShopModules() { + Logger.Log($"Fetching shop modules.", LogLevel.Debug); + string[] shopNames = await Http.GetFromJsonAsync(Configuration["ModulesList"]); + Task[] assemblyDownloadTasks = new Task[shopNames.Length]; + + for (int i = 0; i < shopNames.Length; i++) + { + string shopPath = Configuration["ModulesDir"] + shopNames[i] + ".dll"; + assemblyDownloadTasks[i] = Http.GetByteArrayAsync(shopPath); + Logger.Log($"Downloading \"{shopPath}\".", LogLevel.Debug); + } + + AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyDependencyRequest; + + foreach (Task task in assemblyDownloadTasks) + { + Assembly assembly = AppDomain.CurrentDomain.Load(await task); + + foreach (Type type in assembly.GetTypes()) + { + if (typeof(IShop).IsAssignableFrom(type)) { + IShop shop = Activator.CreateInstance(type) as IShop; + if (shop != null) { + shop.Initialize(); + shops.Add(shop.ShopName, shop); + Logger.Log($"Registered and started lifetime of module for \"{shop.ShopName}\".", LogLevel.Debug); + } + } + } + } + + modulesLoaded = true; + } + + + private Assembly OnAssemblyDependencyRequest(object sender, ResolveEventArgs args) { + Logger.Log($"Assembly {args.RequestingAssembly} is requesting dependency assembly {args.Name}. Attempting to retrieve...", LogLevel.Debug); + return AppDomain.CurrentDomain.Load(Http.GetByteArrayAsync(Configuration["ModulesDir"] + args.Name + ".dll").Result); + } + + + public void Dispose() { + foreach (string name in shops.Keys) + { + shops[name].Dispose(); + Logger.Log($"Ending lifetime of shop module for \"{name}\"."); + } + } +} \ No newline at end of file diff --git a/src/MultiShop/Shared/MainLayout.razor.css b/src/MultiShop/Shared/MainLayout.razor.css index 43c355a..b2332aa 100644 --- a/src/MultiShop/Shared/MainLayout.razor.css +++ b/src/MultiShop/Shared/MainLayout.razor.css @@ -8,28 +8,6 @@ flex: 1; } -.sidebar { - background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); -} - -.top-row { - background-color: #f7f7f7; - border-bottom: 1px solid #d6d5d5; - justify-content: flex-end; - height: 3.5rem; - display: flex; - align-items: center; -} - - .top-row ::deep a, .top-row .btn-link { - white-space: nowrap; - margin-left: 1.5rem; - } - - .top-row a:first-child { - overflow: hidden; - text-overflow: ellipsis; - } @media (max-width: 640.98px) { .top-row:not(.auth) { @@ -39,32 +17,4 @@ .top-row.auth { justify-content: space-between; } - - .top-row a, .top-row .btn-link { - margin-left: 0; - } -} - -@media (min-width: 641px) { - .page { - flex-direction: row; - } - - .sidebar { - width: 250px; - height: 100vh; - position: sticky; - top: 0; - } - - .top-row { - position: sticky; - top: 0; - z-index: 1; - } - - .main > div { - padding-left: 2rem !important; - padding-right: 1.5rem !important; - } -} +} \ No newline at end of file diff --git a/src/MultiShop/Shared/NavMenu.razor b/src/MultiShop/Shared/NavMenu.razor index 667fc4c..791fde0 100644 --- a/src/MultiShop/Shared/NavMenu.razor +++ b/src/MultiShop/Shared/NavMenu.razor @@ -1,34 +1,32 @@ - -
- -
+
+ +
+ @code { private bool collapseNavMenu = true; - private string NavMenuCssClass => collapseNavMenu ? "collapse" : null; + private string NavMenuCssClass => (collapseNavMenu ? "collapse " : " ") + "navbar-collapse"; private void ToggleNavMenu() { diff --git a/src/MultiShop/Shared/NavMenu.razor.css b/src/MultiShop/Shared/NavMenu.razor.css deleted file mode 100644 index acc5f9f..0000000 --- a/src/MultiShop/Shared/NavMenu.razor.css +++ /dev/null @@ -1,62 +0,0 @@ -.navbar-toggler { - background-color: rgba(255, 255, 255, 0.1); -} - -.top-row { - height: 3.5rem; - background-color: rgba(0,0,0,0.4); -} - -.navbar-brand { - font-size: 1.1rem; -} - -.oi { - width: 2rem; - font-size: 1.1rem; - vertical-align: text-top; - top: -2px; -} - -.nav-item { - font-size: 0.9rem; - padding-bottom: 0.5rem; -} - - .nav-item:first-of-type { - padding-top: 1rem; - } - - .nav-item:last-of-type { - padding-bottom: 1rem; - } - - .nav-item ::deep a { - color: #d7d7d7; - border-radius: 4px; - height: 3rem; - display: flex; - align-items: center; - line-height: 3rem; - } - -.nav-item ::deep a.active { - background-color: rgba(255,255,255,0.25); - color: white; -} - -.nav-item ::deep a:hover { - background-color: rgba(255,255,255,0.1); - color: white; -} - -@media (min-width: 641px) { - .navbar-toggler { - display: none; - } - - .collapse { - /* Never collapse the sidebar for wide screens */ - display: block; - } -} diff --git a/src/MultiShop/Shared/SurveyPrompt.razor b/src/MultiShop/Shared/SurveyPrompt.razor deleted file mode 100644 index 1ec40da..0000000 --- a/src/MultiShop/Shared/SurveyPrompt.razor +++ /dev/null @@ -1,16 +0,0 @@ - - -@code { - // Demonstrates how a parent component can supply parameters - [Parameter] - public string Title { get; set; } -} diff --git a/src/MultiShop/wwwroot/100.png b/src/MultiShop/wwwroot/100.png new file mode 100644 index 0000000000000000000000000000000000000000..8a1daa0121d524256c1d1b45ff5e7ed771784c52 GIT binary patch literal 255 zcmeAS@N?(olHy`uVBq!ia0vp^DImVS)*E46%Oq(`s&YU^>_U&7| zc=6GrN4Ia^zG~H~jT<-Ko_$0QsEo6~BeIx*f$sR&2=kJHM z&5N9@w|=XzipNCbb|s-xrJeH)f2E%im~OIrio1Qy&Sj1+>x<+1)+oPni}kGd6Ohy! z>6x1RttNZTv!7LG>?+Ii&zjEnadB}e3778<6P>u`Anzuis~9|8{an^LB{Ts5qZeh6 literal 0 HcmV?d00001 diff --git a/src/MultiShop/wwwroot/appsettings.json b/src/MultiShop/wwwroot/appsettings.json index 1010a01..3d11293 100644 --- a/src/MultiShop/wwwroot/appsettings.json +++ b/src/MultiShop/wwwroot/appsettings.json @@ -1,4 +1,5 @@ { - "LogLevel" : "DEBUG", - "ModulesList" : "modules/modules_content" + "LogLevel" : "Debug", + "ModulesDir" : "modules/", + "ModulesList" : "modules/modules_content.json" } \ No newline at end of file diff --git a/src/MultiShop/wwwroot/css/app.css b/src/MultiShop/wwwroot/css/app.css index caebf2a..d9b36b5 100644 --- a/src/MultiShop/wwwroot/css/app.css +++ b/src/MultiShop/wwwroot/css/app.css @@ -4,30 +4,35 @@ html, body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; } -a, .btn-link { - color: #0366d6; -} - -.btn-primary { - color: #fff; - background-color: #1b6ec2; - border-color: #1861ac; -} - .content { - padding-top: 1.1rem; + padding-top: 1.5rem; } -.valid.modified:not([type=checkbox]) { - outline: 1px solid #26b050; +.table.table-top-borderless thead th { + border-top-style: none; } -.invalid { - outline: 1px solid red; +.btn.btn-tab { + border-bottom-style: none; + border-bottom-left-radius: 0em; + border-bottom-right-radius: 0em; } -.validation-message { - color: red; +.list-group-top-square-left .list-group-item:first-child { + border-top-left-radius: 0em; +} + +.list-group-top-square-right .list-group-item:first-child { + border-top-right-radius: 0em; +} + +li.list-group-item.list-group-item-hover:hover { + background-color: #F5F5F5; +} + +li.list-group-nospacing { + padding: 0px; + margin: 0px; } #blazor-error-ui { diff --git a/src/MultiShop/wwwroot/index.html b/src/MultiShop/wwwroot/index.html index 671dc76..a4d232e 100644 --- a/src/MultiShop/wwwroot/index.html +++ b/src/MultiShop/wwwroot/index.html @@ -19,7 +19,8 @@ Reload 🗙 + - + \ No newline at end of file diff --git a/src/MultiShop/wwwroot/js/ComponentsSupport.js b/src/MultiShop/wwwroot/js/ComponentsSupport.js new file mode 100644 index 0000000..c4e2472 --- /dev/null +++ b/src/MultiShop/wwwroot/js/ComponentsSupport.js @@ -0,0 +1,69 @@ +function customDropdown(elem, justify) { + let btn = elem.querySelector("button"); + let dropdown = elem.querySelector("div"); + if (justify.toLowerCase() == "left") { + dropdown.style.left = "0px"; + } else if (justify.toLowerCase() == "center") { + dropdown.style.left = "50%"; + } else if (justify.toLowerCase() == "right") { + dropdown.style.right = "0px"; + } + + let openFunc = () => { + btn.classList.add("active"); + dropdown.classList.remove("invisible"); + dropdown.focus(); + } + + let closeFunc = () => { + btn.classList.remove("active"); + dropdown.classList.add("invisible"); + } + + btn.addEventListener("click", () => { + if (!btn.classList.contains("active")) { + openFunc(); + } else { + closeFunc(); + } + }); + dropdown.addEventListener("focusout", (e) => { + if (e.relatedTarget != btn) { + closeFunc(); + } + }); + dropdown.addEventListener("keyup", (e) => { + if (e.code == "Escape") { + dropdown.blur(); + } + }); +} + +function dragAndDropList(elem) { + elem.addEventListener("dragover", (e) => { + e.preventDefault(); + }); + let itemDragged; + for (let i = 0; i < elem.childElementCount; i++) { + let e = elem.children[i]; + e.addEventListener("dragstart", () => { + itemDragged = e; + e.classList.add("list-group-item-secondary"); + e.classList.remove("list-group-item-hover"); + }); + e.addEventListener("dragenter", () => { + e.classList.add("list-group-item-primary"); + e.classList.remove("list-group-item-hover"); + }); + e.addEventListener("dragleave", () => { + e.classList.remove("list-group-item-primary"); + e.classList.add("list-group-item-hover"); + }); + e.addEventListener("drop", () => { + e.classList.add("list-group-item-hover"); + e.classList.remove("list-group-item-primary"); + itemDragged.classList.remove("list-group-item-secondary"); + itemDragged.classList.add("list-group-item-hover"); + }); + } +} \ No newline at end of file diff --git a/src/MultiShop/wwwroot/modules/AliExpressShop.dll b/src/MultiShop/wwwroot/modules/AliExpressShop.dll new file mode 100644 index 0000000000000000000000000000000000000000..9b583d37d9f374dede89b3c8831f6ef28cd0f114 GIT binary patch literal 26112 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 listingsTask = shop.Search("mpu6050", 1); - listingsTask.Wait(); - IEnumerable listings = listingsTask.Result; - Assert.NotEmpty(listings); - foreach (ProductListing listing in listings) + shop.SetupSession("mpu6050", Currency.CAD); + //Then + int count = 0; + await foreach (ProductListing listing in shop) { Assert.False(string.IsNullOrWhiteSpace(listing.Name)); - Assert.True(listing.LowerPrice != 0); + count += 1; + if (count > MAX_RESULTS) return; } } [Fact] - public void Search_SearchForItem_MultiplePages() - { - //Given - Shop shop = new Shop(); - shop.Initiate(Currency.CAD); - //When - Task> listingsTask = shop.Search("mpu6050", 2); - listingsTask.Wait(); - IEnumerable listings = listingsTask.Result; - //Then - Assert.NotEmpty(listings); - foreach (ProductListing listing in listings) - { - Assert.False(string.IsNullOrWhiteSpace(listing.Name)); - } - } - [Fact] - public void Search_USD_ResultsFound() + public async void Search_USD_ResultsFound() { //Given + const int MAX_RESULTS = 120; Shop shop = new Shop(); - shop.Initiate(Currency.USD); + shop.UseProxy = false; + shop.Initialize(); //When - Task> listingsTask = shop.Search("mpu6050", 1); - listingsTask.Wait(); - IEnumerable listings = listingsTask.Result; + shop.SetupSession("mpu6050", Currency.USD); //Then - Assert.NotEmpty(listings); - foreach (ProductListing listing in listings) + + int count = 0; + await foreach (ProductListing listing in shop) { Assert.False(string.IsNullOrWhiteSpace(listing.Name)); Assert.True(listing.LowerPrice != 0); + count += 1; + if (count > MAX_RESULTS) return; } } } diff --git a/test/AliExpressShop.Tests/XUnitLogger.cs b/test/AliExpressShop.Tests/XUnitLogger.cs index abaa83d..520149c 100644 --- a/test/AliExpressShop.Tests/XUnitLogger.cs +++ b/test/AliExpressShop.Tests/XUnitLogger.cs @@ -6,7 +6,7 @@ namespace GameServiceWarden.Core.Tests { public class XUnitLogger : ILogReceiver { - public LogLevel Level => LogLevel.DEBUG; + public LogLevel Level => LogLevel.Debug; public string Identifier => GetType().Name; From 99656133c9e332d0373f7a06947ae8f3bf98d3cf Mon Sep 17 00:00:00 2001 From: Harrison Deng Date: Mon, 10 May 2021 00:30:40 -0500 Subject: [PATCH 007/167] Changed up results configuration layout. Removed dropdown portion. Added buttons to change sort order. Visual indicator for disabling changes to sort order list. Renamed a namespace. --- SimpleLogger | 2 +- .../ProductListingInfo.cs | 2 +- .../ResultCategoryExtensions.cs | 2 +- .../ResultsProfile.cs | 2 +- .../SearchProfile.cs | 2 +- src/MultiShop/Pages/Index.razor | 2 +- src/MultiShop/Pages/Search.razor | 74 +++++++++++-------- src/MultiShop/Shared/CustomDropdown.razor | 45 ----------- src/MultiShop/Shared/DragAndDropList.razor | 40 ++++++++-- src/MultiShop/Shared/ListingTableView.razor | 3 +- src/MultiShop/wwwroot/js/ComponentsSupport.js | 46 ------------ 11 files changed, 84 insertions(+), 136 deletions(-) rename src/MultiShop/{SearchStructures => DataStructures}/ProductListingInfo.cs (96%) rename src/MultiShop/{SearchStructures => DataStructures}/ResultCategoryExtensions.cs (98%) rename src/MultiShop/{SearchStructures => DataStructures}/ResultsProfile.cs (94%) rename src/MultiShop/{SearchStructures => DataStructures}/SearchProfile.cs (98%) delete mode 100644 src/MultiShop/Shared/CustomDropdown.razor diff --git a/SimpleLogger b/SimpleLogger index f275ff3..3049ccc 160000 --- a/SimpleLogger +++ b/SimpleLogger @@ -1 +1 @@ -Subproject commit f275ff330db936d7eabc6dc435952ec0752edbc9 +Subproject commit 3049cccc3efa3323a85f1ae591969b8f5d35be5d diff --git a/src/MultiShop/SearchStructures/ProductListingInfo.cs b/src/MultiShop/DataStructures/ProductListingInfo.cs similarity index 96% rename from src/MultiShop/SearchStructures/ProductListingInfo.cs rename to src/MultiShop/DataStructures/ProductListingInfo.cs index 3fdb0dc..84256f0 100644 --- a/src/MultiShop/SearchStructures/ProductListingInfo.cs +++ b/src/MultiShop/DataStructures/ProductListingInfo.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using MultiShop.ShopFramework; -namespace MultiShop.SearchStructures +namespace MultiShop.DataStructures { public class ProductListingInfo { diff --git a/src/MultiShop/SearchStructures/ResultCategoryExtensions.cs b/src/MultiShop/DataStructures/ResultCategoryExtensions.cs similarity index 98% rename from src/MultiShop/SearchStructures/ResultCategoryExtensions.cs rename to src/MultiShop/DataStructures/ResultCategoryExtensions.cs index 315f845..126f859 100644 --- a/src/MultiShop/SearchStructures/ResultCategoryExtensions.cs +++ b/src/MultiShop/DataStructures/ResultCategoryExtensions.cs @@ -2,7 +2,7 @@ using System; using System.ComponentModel; using MultiShop.ShopFramework; -namespace MultiShop.SearchStructures +namespace MultiShop.DataStructures { public static class ResultCategoryExtensions { diff --git a/src/MultiShop/SearchStructures/ResultsProfile.cs b/src/MultiShop/DataStructures/ResultsProfile.cs similarity index 94% rename from src/MultiShop/SearchStructures/ResultsProfile.cs rename to src/MultiShop/DataStructures/ResultsProfile.cs index ac428d4..6101fb7 100644 --- a/src/MultiShop/SearchStructures/ResultsProfile.cs +++ b/src/MultiShop/DataStructures/ResultsProfile.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; -namespace MultiShop.SearchStructures +namespace MultiShop.DataStructures { public class ResultsProfile { diff --git a/src/MultiShop/SearchStructures/SearchProfile.cs b/src/MultiShop/DataStructures/SearchProfile.cs similarity index 98% rename from src/MultiShop/SearchStructures/SearchProfile.cs rename to src/MultiShop/DataStructures/SearchProfile.cs index e306e9b..c5130b6 100644 --- a/src/MultiShop/SearchStructures/SearchProfile.cs +++ b/src/MultiShop/DataStructures/SearchProfile.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using MultiShop.ShopFramework; -namespace MultiShop.SearchStructures +namespace MultiShop.DataStructures { public class SearchProfile { diff --git a/src/MultiShop/Pages/Index.razor b/src/MultiShop/Pages/Index.razor index e16ad3b..d26f184 100644 --- a/src/MultiShop/Pages/Index.razor +++ b/src/MultiShop/Pages/Index.razor @@ -1,3 +1,3 @@ @page "/" -

Welcome to MultiShop!

+

Welcome to MultiShop!

\ No newline at end of file diff --git a/src/MultiShop/Pages/Search.razor b/src/MultiShop/Pages/Search.razor index e8ddd5d..a504b34 100644 --- a/src/MultiShop/Pages/Search.razor +++ b/src/MultiShop/Pages/Search.razor @@ -2,12 +2,12 @@ @using Microsoft.Extensions.Configuration @using ShopFramework @using SimpleLogger -@using SearchStructures +@using DataStructures @inject HttpClient Http @inject IConfiguration Configuration @inject IJSRuntime js -@* TODO: Finish sorting, move things to individual components where possible, key search results. *@ +@* TODO: Add buttons for the order changing. Add main page. *@
@@ -178,7 +178,28 @@
-
+
+ +
+ @if (showResultsConfiguration) + { +
+
+
+
Results Order
+
What's important to you?
+

The results will be sorted by the top category. If the compared results are equal or don't have a value for that category, the next category on the list will be used and so on.

+ + + @(item.FriendlyName()) + + +
+
+
+ } + +
@if (searching) { @@ -220,19 +241,6 @@ Search for something to see the results! }
- - - - - - - - @(item.FriendlyName()) - - - - -
@@ -251,11 +259,17 @@ private ResultsProfile activeResultsProfile = new ResultsProfile(); private bool showSearchConfiguration = false; + private bool showResultsConfiguration = false; + private string ToggleSearchConfigButtonCss { get => "btn btn-outline-secondary" + (showSearchConfiguration ? " active" : ""); } + private string ToggleResultsConfigurationcss { + get => "btn btn-outline-secondary btn-tab" + (showResultsConfiguration ? " active" : ""); + } + private bool searched = false; private bool searching = false; private bool organizing = false; @@ -306,7 +320,8 @@ await foreach (ProductListing listing in Shops[shopName]) { resultsChecked += 1; - if (resultsChecked % 50 == 0) { + if (resultsChecked % 50 == 0) + { StateHasChanged(); await Task.Yield(); } @@ -355,7 +370,7 @@ } searching = false; searched = true; - + foreach (ResultsProfile.Category c in greatest.Keys) { foreach (ProductListingInfo info in greatest[c]) @@ -373,19 +388,20 @@ organizing = true; StateHasChanged(); - List sortedResults = await Task.Run>(() => { + List sortedResults = await Task.Run>(() => + { List sorted = new List(listings); sorted.Sort((a, b) => - { - foreach (ResultsProfile.Category category in activeResultsProfile.Order) - { - int? compareResult = category.CompareListings(a, b); - if (compareResult.HasValue && compareResult.Value != 0) - { - return -compareResult.Value; - } - } - return 0; + { + foreach (ResultsProfile.Category category in activeResultsProfile.Order) + { + int? compareResult = category.CompareListings(a, b); + if (compareResult.HasValue && compareResult.Value != 0) + { + return -compareResult.Value; + } + } + return 0; }); return sorted; }); diff --git a/src/MultiShop/Shared/CustomDropdown.razor b/src/MultiShop/Shared/CustomDropdown.razor deleted file mode 100644 index 60160e8..0000000 --- a/src/MultiShop/Shared/CustomDropdown.razor +++ /dev/null @@ -1,45 +0,0 @@ -@inject IJSRuntime JS - -
- - -
- -@code { - [Parameter] - public RenderFragment ButtonContent { get; set; } - - [Parameter] - public RenderFragment DropdownContent { get; set; } - - [Parameter] - public string AdditionalButtonClasses { get; set; } - - [Parameter] - public string Justify { get; set; } - - private ElementReference dropdown; - private string ButtonCss - { - get => "btn " + AdditionalButtonClasses; - } - - protected override async Task OnParametersSetAsync() - { - AdditionalButtonClasses = AdditionalButtonClasses ?? ""; - Justify = Justify ?? "center"; - await base.OnParametersSetAsync(); - } - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender) - { - await JS.InvokeVoidAsync("customDropdown", dropdown, Justify); - } - await base.OnAfterRenderAsync(firstRender); - } - -} diff --git a/src/MultiShop/Shared/DragAndDropList.razor b/src/MultiShop/Shared/DragAndDropList.razor index 74f6bd9..94b4456 100644 --- a/src/MultiShop/Shared/DragAndDropList.razor +++ b/src/MultiShop/Shared/DragAndDropList.razor @@ -5,9 +5,16 @@
    @foreach (TItem item in Items) { -
  • - - @DraggableItem(item) +
  • +
    +
    + + +
    +
    + @DraggableItem(item) +
    +
  • }
@@ -29,6 +36,7 @@ private ElementReference dragAndDrop; private int itemDraggedIndex = -1; + private bool processingDropChange = false; private string ListGroupCss @@ -54,12 +62,28 @@ private async Task OnDrop(TItem dropped) { - TItem item = Items[itemDraggedIndex]; - if (item.Equals(dropped)) return; - int indexOfDrop = Items.IndexOf(dropped); - Items.RemoveAt(itemDraggedIndex); - Items.Insert(indexOfDrop, item); + int dragIndex = itemDraggedIndex; itemDraggedIndex = -1; + await MoveOrder(dragIndex, Items.IndexOf(dropped)); + } + + private async Task OnButtonClickMove(int index, bool up) { + if (up) { + await MoveOrder(index, index - 1); + } else { + await MoveOrder(index, index + 1); + } + } + + private async Task MoveOrder(int from, int to) + { + Logger.Log($"Attempting to move from {from} to {to}.", LogLevel.Debug); + if (from == to || from >= Items.Count || from < 0 || to > Items.Count || to < 0) return; + TItem item = Items[from]; + Items.RemoveAt(from); + Items.Insert(to, item); + processingDropChange = true; await OnOrderChange.InvokeAsync(); + processingDropChange = false; } } diff --git a/src/MultiShop/Shared/ListingTableView.razor b/src/MultiShop/Shared/ListingTableView.razor index 60e6462..ea47ad7 100644 --- a/src/MultiShop/Shared/ListingTableView.razor +++ b/src/MultiShop/Shared/ListingTableView.razor @@ -1,5 +1,4 @@ -@using ShopFramework -@using SearchStructures +@using DataStructures
diff --git a/src/MultiShop/wwwroot/js/ComponentsSupport.js b/src/MultiShop/wwwroot/js/ComponentsSupport.js index c4e2472..2122ce1 100644 --- a/src/MultiShop/wwwroot/js/ComponentsSupport.js +++ b/src/MultiShop/wwwroot/js/ComponentsSupport.js @@ -1,44 +1,3 @@ -function customDropdown(elem, justify) { - let btn = elem.querySelector("button"); - let dropdown = elem.querySelector("div"); - if (justify.toLowerCase() == "left") { - dropdown.style.left = "0px"; - } else if (justify.toLowerCase() == "center") { - dropdown.style.left = "50%"; - } else if (justify.toLowerCase() == "right") { - dropdown.style.right = "0px"; - } - - let openFunc = () => { - btn.classList.add("active"); - dropdown.classList.remove("invisible"); - dropdown.focus(); - } - - let closeFunc = () => { - btn.classList.remove("active"); - dropdown.classList.add("invisible"); - } - - btn.addEventListener("click", () => { - if (!btn.classList.contains("active")) { - openFunc(); - } else { - closeFunc(); - } - }); - dropdown.addEventListener("focusout", (e) => { - if (e.relatedTarget != btn) { - closeFunc(); - } - }); - dropdown.addEventListener("keyup", (e) => { - if (e.code == "Escape") { - dropdown.blur(); - } - }); -} - function dragAndDropList(elem) { elem.addEventListener("dragover", (e) => { e.preventDefault(); @@ -49,21 +8,16 @@ function dragAndDropList(elem) { e.addEventListener("dragstart", () => { itemDragged = e; e.classList.add("list-group-item-secondary"); - e.classList.remove("list-group-item-hover"); }); e.addEventListener("dragenter", () => { e.classList.add("list-group-item-primary"); - e.classList.remove("list-group-item-hover"); }); e.addEventListener("dragleave", () => { e.classList.remove("list-group-item-primary"); - e.classList.add("list-group-item-hover"); }); e.addEventListener("drop", () => { - e.classList.add("list-group-item-hover"); e.classList.remove("list-group-item-primary"); itemDragged.classList.remove("list-group-item-secondary"); - itemDragged.classList.add("list-group-item-hover"); }); } } \ No newline at end of file From 3218fbf4e38cafbcc91942cc297dfe81d9018932 Mon Sep 17 00:00:00 2001 From: Harrison Deng Date: Mon, 10 May 2021 19:59:15 -0500 Subject: [PATCH 008/167] Properly added SimpleLogger as git submodule. --- .gitmodules | 3 +++ SimpleLogger | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 .gitmodules diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..0d12baa --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "SimpleLogger"] + path = SimpleLogger + url = https://systems.reslate.xyz/git/ydeng/SimpleLogger.git diff --git a/SimpleLogger b/SimpleLogger index 3049ccc..f9931ee 160000 --- a/SimpleLogger +++ b/SimpleLogger @@ -1 +1 @@ -Subproject commit 3049cccc3efa3323a85f1ae591969b8f5d35be5d +Subproject commit f9931eea4295353befffcf1880ea510cc259e9f2 From 04d4caf2bd55fa5e4dbfaeaa38a287a8750b73f2 Mon Sep 17 00:00:00 2001 From: Harrison Deng Date: Mon, 10 May 2021 20:03:08 -0500 Subject: [PATCH 009/167] Added response to cancellation token to AliExpressShop. --- src/AliExpressShop/Shop.cs | 2 +- src/AliExpressShop/ShopEnumerator.cs | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/AliExpressShop/Shop.cs b/src/AliExpressShop/Shop.cs index fc24017..217919e 100644 --- a/src/AliExpressShop/Shop.cs +++ b/src/AliExpressShop/Shop.cs @@ -64,7 +64,7 @@ namespace AliExpressShop public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) { - return new ShopEnumerator(query, currency, http, UseProxy); + return new ShopEnumerator(cancellationToken, query, currency, http, UseProxy); } } } diff --git a/src/AliExpressShop/ShopEnumerator.cs b/src/AliExpressShop/ShopEnumerator.cs index ee9fb0a..0fde592 100644 --- a/src/AliExpressShop/ShopEnumerator.cs +++ b/src/AliExpressShop/ShopEnumerator.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.IO; using System.Net.Http; using System.Text.RegularExpressions; +using System.Threading; using System.Threading.Tasks; using GameServiceWarden.Core.Collection; using MultiShop.ShopFramework; @@ -13,6 +14,7 @@ namespace AliExpressShop { class ShopEnumerator : IAsyncEnumerator { + private CancellationToken cancellationToken; private LRUCache<(string, Currency), float> conversionCache = new LRUCache<(string, Currency), float>(); private string query; private Currency currency; @@ -24,8 +26,9 @@ namespace AliExpressShop public ProductListing Current {get; private set;} - public ShopEnumerator(string query, Currency currency, HttpClient http, bool useProxy = true) + public ShopEnumerator(CancellationToken cancellationToken, string query, Currency currency, HttpClient http, bool useProxy = true) { + this.cancellationToken = cancellationToken; this.query = query; this.currency = currency; this.http = http; @@ -64,19 +67,20 @@ namespace AliExpressShop double waitTime = DELAY - (DateTime.Now - start).TotalMilliseconds; if (waitTime > 0) { Logger.Log($"Delaying next page by {waitTime}ms.", LogLevel.Debug); - await Task.Delay((int)Math.Ceiling(waitTime)); + await Task.Delay((int)Math.Ceiling(waitTime), cancellationToken); } Logger.Log($"Sending GET request with uri: {request.RequestUri}", LogLevel.Debug); - HttpResponseMessage response = await http.SendAsync(request); + HttpResponseMessage response = await http.SendAsync(request, cancellationToken); start = DateTime.Now; string data = null; - using (StreamReader reader = new StreamReader(await response.Content.ReadAsStreamAsync())) + using (StreamReader reader = new StreamReader(await response.Content.ReadAsStreamAsync(cancellationToken))) { string line = null; while ((line = await reader.ReadLineAsync()) != null && data == null) { + if (cancellationToken.IsCancellationRequested) throw new OperationCanceledException(); if (dataLineRegex.IsMatch(line)) { data = line.Trim(); Logger.Log($"Found line with listing data.", LogLevel.Debug); @@ -229,9 +233,9 @@ namespace AliExpressShop private async Task FetchConversion(string from, Currency to) { if (from.Equals(to.ToString())) return 1; HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, string.Format("https://api.exchangerate.host/convert?from={0}&to={1}", from, to)); - HttpResponseMessage response = await http.SendAsync(request); + HttpResponseMessage response = await http.SendAsync(request, cancellationToken); string results = null; - using (StreamReader reader = new StreamReader(await response.Content.ReadAsStreamAsync())) + using (StreamReader reader = new StreamReader(await response.Content.ReadAsStreamAsync(cancellationToken))) { results = await reader.ReadToEndAsync(); } From e675962c35786661c386c917db3ec27aae8f3f1c Mon Sep 17 00:00:00 2001 From: Harrison Deng Date: Tue, 11 May 2021 01:52:57 -0500 Subject: [PATCH 010/167] Added Banggood shop. --- src/BanggoodShop/BanggoodShop.csproj | 16 +++ src/BanggoodShop/Shop.cs | 59 +++++++++++ src/BanggoodShop/ShopEnumerator.cs | 98 ++++++++++++++++++ src/MultiShop/DataStructures/SearchProfile.cs | 6 +- src/MultiShop/Pages/Search.razor | 4 +- src/MultiShop/Shared/MainLayout.razor | 26 ++++- .../wwwroot/modules/BanggoodShop.dll | Bin 0 -> 12288 bytes .../wwwroot/modules/HtmlAgilityPack.dll | Bin 0 -> 161792 bytes .../wwwroot/modules/modules_content.json | 4 +- test/AliExpressShop.Tests/ShopTest.cs | 7 +- test/AliExpressShop.Tests/XUnitLogger.cs | 2 +- .../BanggoodShop.Tests.csproj | 27 +++++ test/BanggoodShop.Tests/ShopTest.cs | 36 +++++++ test/BanggoodShop.Tests/XUnitLogger.cs | 33 ++++++ 14 files changed, 302 insertions(+), 16 deletions(-) create mode 100644 src/BanggoodShop/BanggoodShop.csproj create mode 100644 src/BanggoodShop/Shop.cs create mode 100644 src/BanggoodShop/ShopEnumerator.cs create mode 100644 src/MultiShop/wwwroot/modules/BanggoodShop.dll create mode 100644 src/MultiShop/wwwroot/modules/HtmlAgilityPack.dll create mode 100644 test/BanggoodShop.Tests/BanggoodShop.Tests.csproj create mode 100644 test/BanggoodShop.Tests/ShopTest.cs create mode 100644 test/BanggoodShop.Tests/XUnitLogger.cs diff --git a/src/BanggoodShop/BanggoodShop.csproj b/src/BanggoodShop/BanggoodShop.csproj new file mode 100644 index 0000000..38c6a2a --- /dev/null +++ b/src/BanggoodShop/BanggoodShop.csproj @@ -0,0 +1,16 @@ + + + + + + + + + + + + + net5.0 + + + diff --git a/src/BanggoodShop/Shop.cs b/src/BanggoodShop/Shop.cs new file mode 100644 index 0000000..3f82e85 --- /dev/null +++ b/src/BanggoodShop/Shop.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Runtime.CompilerServices; +using System.Threading; +using MultiShop.ShopFramework; + +namespace BanggoodShop +{ + public class Shop : IShop + { + public bool UseProxy { get; set; } = true; + private bool disposedValue; + + public string ShopName => "Banggood"; + + public string ShopDescription => "A online retailer based in China."; + + public string ShopModuleAuthor => "Reslate"; + + private HttpClient http; + private string query; + private Currency currency; + + public IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) + { + return new ShopEnumerator(query, currency, http, UseProxy, cancellationToken); + } + + public void Initialize() + { + this.http = new HttpClient(); + } + + public void SetupSession(string query, Currency currency) + { + this.query = query; + this.currency = currency; + } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + http.Dispose(); + } + disposedValue = true; + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/BanggoodShop/ShopEnumerator.cs b/src/BanggoodShop/ShopEnumerator.cs new file mode 100644 index 0000000..3e53fc5 --- /dev/null +++ b/src/BanggoodShop/ShopEnumerator.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using System.Xml; +using HtmlAgilityPack; +using MultiShop.ShopFramework; +using SimpleLogger; + +namespace BanggoodShop +{ + class ShopEnumerator : IAsyncEnumerator + { + const string PROXY_FORMAT = "https://cors.bridged.cc/{0}"; + private const string QUERY_FORMAT = "https://www.banggood.com/search/{0}/0-0-0-1-1-60-0-price-0-0_p-{1}.html?DCC=CA¤cy={2}"; + HttpClient http; + private string query; + private Currency currency; + private bool useProxy; + private CancellationToken cancellationToken; + + private IEnumerator pageListings; + private int currentPage; + private DateTime lastScrape; + + public ProductListing Current { get; private set; } + + public ShopEnumerator(string query, Currency currency, HttpClient http, bool useProxy, CancellationToken cancellationToken) + { + query = query.Replace(' ', '-'); + this.query = query; + this.currency = currency; + this.http = http; + this.useProxy = useProxy; + this.cancellationToken = cancellationToken; + } + + private async Task> ScrapePage(int page) + { + string requestUrl = string.Format(QUERY_FORMAT, query, page, currency.ToString()); + if (useProxy) requestUrl = string.Format(PROXY_FORMAT, requestUrl); + TimeSpan difference = DateTime.Now - lastScrape; + if (difference.TotalMilliseconds < 200) { + await Task.Delay((int)Math.Ceiling(200 - difference.TotalMilliseconds)); + } + HttpResponseMessage response = await http.GetAsync(requestUrl); + lastScrape = DateTime.Now; + HtmlDocument html = new HtmlDocument(); + html.Load(await response.Content.ReadAsStreamAsync()); + HtmlNodeCollection collection = html.DocumentNode.SelectNodes(@"//div[@class='product-list']/ul[@class='goodlist cf']/li"); + if (collection == null) return null; + List results = new List(); + foreach (HtmlNode node in collection) + { + ProductListing listing = new ProductListing(); + HtmlNode productNode = node.SelectSingleNode(@"div/a[1]"); + listing.Name = productNode.InnerText; + Logger.Log($"Found name: {listing.Name}", LogLevel.Debug); + listing.URL = productNode.GetAttributeValue("href", null); + Logger.Log($"Found URL: {listing.URL}", LogLevel.Debug); + listing.ImageURL = node.SelectSingleNode(@"div/span[@class='img notranslate']/a/img").GetAttributeValue("data-src", null); + Logger.Log($"Found image URL: {listing.ImageURL}", LogLevel.Debug); + listing.LowerPrice = float.Parse(Regex.Match(node.SelectSingleNode(@"div/span[@class='price-box']/span").InnerText, @"(\d*\.\d*)").Groups[1].Value); + Logger.Log($"Found price: {listing.LowerPrice}", LogLevel.Debug); + listing.UpperPrice = listing.LowerPrice; + listing.ReviewCount = int.Parse(Regex.Match(node.SelectSingleNode(@"div/a[2]").InnerText, @"(\d+) reviews?").Groups[1].Value); + Logger.Log($"Found reviews: {listing.ReviewCount}", LogLevel.Debug); + results.Add(listing); + } + return results; + } + + public ValueTask DisposeAsync() + { + return ValueTask.CompletedTask; + } + + public async ValueTask MoveNextAsync() + { + if (pageListings == null || !pageListings.MoveNext()) + { + currentPage += 1; + pageListings?.Dispose(); + IEnumerable pageEnumerable = await ScrapePage(currentPage); + if (pageEnumerable == null) return false; + pageListings = pageEnumerable.GetEnumerator(); + pageListings.MoveNext(); + } + Current = pageListings.Current; + return true; + } + } +} \ No newline at end of file diff --git a/src/MultiShop/DataStructures/SearchProfile.cs b/src/MultiShop/DataStructures/SearchProfile.cs index c5130b6..e0d2ed4 100644 --- a/src/MultiShop/DataStructures/SearchProfile.cs +++ b/src/MultiShop/DataStructures/SearchProfile.cs @@ -70,7 +70,7 @@ namespace MultiShop.DataStructures set { - if (value == false && !CanDisableShop()) return; + if (value == false && !(shopsEnabled.Count > 1)) return; if (value) { shopsEnabled.Add(name); @@ -81,8 +81,8 @@ namespace MultiShop.DataStructures } } } - public bool CanDisableShop() { - return shopsEnabled.Count > 1; + public bool IsToggleable(string shop) { + return (shopsEnabled.Contains(shop) && shopsEnabled.Count > 1) || !shopsEnabled.Contains(shop); } } } diff --git a/src/MultiShop/Pages/Search.razor b/src/MultiShop/Pages/Search.razor index a504b34..9867f74 100644 --- a/src/MultiShop/Pages/Search.razor +++ b/src/MultiShop/Pages/Search.razor @@ -109,7 +109,7 @@ @foreach (string shop in Shops.Keys) {
- +
} @@ -330,7 +330,7 @@ if (listing.Shipping == null && !activeProfile.keepUnknownShipping || (activeProfile.enableMaxShippingFee && listing.Shipping > activeProfile.MaxShippingFee)) continue; float shippingDifference = listing.Shipping != null ? listing.Shipping.Value : 0; if (!(listing.LowerPrice + shippingDifference >= activeProfile.lowerPrice && (!activeProfile.enableUpperPrice || listing.UpperPrice + shippingDifference <= activeProfile.UpperPrice))) continue; - if ((listing.Rating == null && !activeProfile.keepUnrated) || activeProfile.minRating > (listing.Rating == null ? 0 : listing.Rating)) continue; + if ((listing.Rating == null && !activeProfile.keepUnrated) && activeProfile.minRating > (listing.Rating == null ? 0 : listing.Rating)) continue; if ((listing.PurchaseCount == null && !activeProfile.keepUnknownPurchaseCount) || activeProfile.minPurchases > (listing.PurchaseCount == null ? 0 : listing.PurchaseCount)) continue; if ((listing.ReviewCount == null && !activeProfile.keepUnknownRatingCount) || activeProfile.minReviews > (listing.ReviewCount == null ? 0 : listing.ReviewCount)) continue; diff --git a/src/MultiShop/Shared/MainLayout.razor b/src/MultiShop/Shared/MainLayout.razor index d91bb71..c20b2fd 100644 --- a/src/MultiShop/Shared/MainLayout.razor +++ b/src/MultiShop/Shared/MainLayout.razor @@ -25,6 +25,7 @@ private bool modulesLoaded = false; private Dictionary shops = new Dictionary(); + private Dictionary unusedDependencies = new Dictionary(); protected override async Task OnInitializedAsync() { @@ -48,7 +49,7 @@ foreach (Task task in assemblyDownloadTasks) { Assembly assembly = AppDomain.CurrentDomain.Load(await task); - + bool assigned = false; foreach (Type type in assembly.GetTypes()) { if (typeof(IShop).IsAssignableFrom(type)) { @@ -58,17 +59,34 @@ shops.Add(shop.ShopName, shop); Logger.Log($"Registered and started lifetime of module for \"{shop.ShopName}\".", LogLevel.Debug); } + assigned = true; } } + if (!assigned) { + unusedDependencies.Add(assembly.FullName, assembly); + Logger.Log($"Assembly \"{assembly.FullName}\" did not contain a shop module. Storing it as potential extension.", LogLevel.Debug); + } } - + foreach (string assembly in unusedDependencies.Keys) + { + Logger.Log($"{assembly} was unused.", LogLevel.Warning); + } + unusedDependencies.Clear(); modulesLoaded = true; } private Assembly OnAssemblyDependencyRequest(object sender, ResolveEventArgs args) { - Logger.Log($"Assembly {args.RequestingAssembly} is requesting dependency assembly {args.Name}. Attempting to retrieve...", LogLevel.Debug); - return AppDomain.CurrentDomain.Load(Http.GetByteArrayAsync(Configuration["ModulesDir"] + args.Name + ".dll").Result); + Logger.Log($"Assembly {args.RequestingAssembly} is requesting dependency assembly {args.Name}.", LogLevel.Debug); + if (unusedDependencies.ContainsKey(args.Name)) + { + Logger.Log("Dependency found.", LogLevel.Debug); + Assembly dependency = unusedDependencies[args.Name]; + unusedDependencies.Remove(args.Name); + return dependency; + } + Logger.Log($"No dependency under name {args.Name}", LogLevel.Debug); + return null; } diff --git a/src/MultiShop/wwwroot/modules/BanggoodShop.dll b/src/MultiShop/wwwroot/modules/BanggoodShop.dll new file mode 100644 index 0000000000000000000000000000000000000000..e6051e5cce0db2b612d40e55e5d74324d558a9ab GIT binary patch literal 12288 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 D#G<;- literal 0 HcmV?d00001 diff --git a/src/MultiShop/wwwroot/modules/HtmlAgilityPack.dll b/src/MultiShop/wwwroot/modules/HtmlAgilityPack.dll new file mode 100644 index 0000000000000000000000000000000000000000..e18de303c1d9f6174205986ea930f01eaa6e4c6d GIT binary patch literal 161792 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 A%K!iX literal 0 HcmV?d00001 diff --git a/src/MultiShop/wwwroot/modules/modules_content.json b/src/MultiShop/wwwroot/modules/modules_content.json index 8c4a535..6c67e7b 100644 --- a/src/MultiShop/wwwroot/modules/modules_content.json +++ b/src/MultiShop/wwwroot/modules/modules_content.json @@ -1,3 +1,5 @@ [ - "AliExpressShop" + "AliExpressShop", + "HtmlAgilityPack", + "BanggoodShop" ] \ No newline at end of file diff --git a/test/AliExpressShop.Tests/ShopTest.cs b/test/AliExpressShop.Tests/ShopTest.cs index f09998f..0214025 100644 --- a/test/AliExpressShop.Tests/ShopTest.cs +++ b/test/AliExpressShop.Tests/ShopTest.cs @@ -1,7 +1,3 @@ -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using GameServiceWarden.Core.Tests; using MultiShop.ShopFramework; using SimpleLogger; using Xunit; @@ -34,10 +30,10 @@ namespace AliExpressShop.Tests count += 1; if (count > MAX_RESULTS) return; } + shop.Dispose(); } [Fact] - public async void Search_USD_ResultsFound() { //Given @@ -57,6 +53,7 @@ namespace AliExpressShop.Tests count += 1; if (count > MAX_RESULTS) return; } + shop.Dispose(); } } } diff --git a/test/AliExpressShop.Tests/XUnitLogger.cs b/test/AliExpressShop.Tests/XUnitLogger.cs index 520149c..7fe2715 100644 --- a/test/AliExpressShop.Tests/XUnitLogger.cs +++ b/test/AliExpressShop.Tests/XUnitLogger.cs @@ -2,7 +2,7 @@ using System; using SimpleLogger; using Xunit.Abstractions; -namespace GameServiceWarden.Core.Tests +namespace AliExpressShop { public class XUnitLogger : ILogReceiver { diff --git a/test/BanggoodShop.Tests/BanggoodShop.Tests.csproj b/test/BanggoodShop.Tests/BanggoodShop.Tests.csproj new file mode 100644 index 0000000..a421dbc --- /dev/null +++ b/test/BanggoodShop.Tests/BanggoodShop.Tests.csproj @@ -0,0 +1,27 @@ + + + + net5.0 + + false + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + diff --git a/test/BanggoodShop.Tests/ShopTest.cs b/test/BanggoodShop.Tests/ShopTest.cs new file mode 100644 index 0000000..9fe8f81 --- /dev/null +++ b/test/BanggoodShop.Tests/ShopTest.cs @@ -0,0 +1,36 @@ +using MultiShop.ShopFramework; +using SimpleLogger; +using Xunit; +using Xunit.Abstractions; + +namespace BanggoodShop.Tests +{ + public class ShopTest + { + public ShopTest(ITestOutputHelper output) + { + Logger.AddLogListener(new XUnitLogger(output)); + } + + + [Fact] + public async void Search_CAD_ResultsFound() + { + //Given + const int MAX_RESULTS = 100; + Shop shop = new Shop(); + shop.UseProxy = false; + //When + shop.Initialize(); + shop.SetupSession("samsung galaxy 20 case", Currency.CAD); + //Then + int count = 0; + await foreach (ProductListing listing in shop) + { + count += 1; + Assert.False(string.IsNullOrWhiteSpace(listing.Name)); + if (count >= MAX_RESULTS) return; + } + } + } +} diff --git a/test/BanggoodShop.Tests/XUnitLogger.cs b/test/BanggoodShop.Tests/XUnitLogger.cs new file mode 100644 index 0000000..7e76ffd --- /dev/null +++ b/test/BanggoodShop.Tests/XUnitLogger.cs @@ -0,0 +1,33 @@ +using System; +using SimpleLogger; +using Xunit.Abstractions; + +namespace BanggoodShop +{ + public class XUnitLogger : ILogReceiver + { + public LogLevel Level => LogLevel.Debug; + + public string Identifier => GetType().Name; + + private ITestOutputHelper outputHelper; + + public XUnitLogger(ITestOutputHelper output) + { + this.outputHelper = output; + } + + public void Flush() + { + } + + public void LogMessage(string message, DateTime time, LogLevel level) + { + try + { + outputHelper.WriteLine($"[{time.ToShortTimeString()}][{level.ToString()}]: {message}"); + } + catch (InvalidOperationException) { }; + } + } +} \ No newline at end of file From e07b234eb291f9a5370296a67740580b14a9e031 Mon Sep 17 00:00:00 2001 From: Harrison Deng Date: Tue, 18 May 2021 19:34:32 -0500 Subject: [PATCH 011/167] Made rating price ratio use more strict. Reorganized modules_content.json. --- .../DataStructures/ProductListingInfo.cs | 5 ++-- .../ResultCategoryExtensions.cs | 5 ++-- src/MultiShop/Pages/Index.razor | 1 + src/MultiShop/Pages/Search.razor | 23 +++++++++---------- .../wwwroot/modules/modules_content.json | 2 +- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/MultiShop/DataStructures/ProductListingInfo.cs b/src/MultiShop/DataStructures/ProductListingInfo.cs index 84256f0..9e8846c 100644 --- a/src/MultiShop/DataStructures/ProductListingInfo.cs +++ b/src/MultiShop/DataStructures/ProductListingInfo.cs @@ -7,12 +7,11 @@ namespace MultiShop.DataStructures { public ProductListing Listing { get; private set; } public string ShopName { get; private set; } - public float RatingToPriceRatio { + public float? RatingToPriceRatio { get { int reviewFactor = Listing.ReviewCount.HasValue ? Listing.ReviewCount.Value : 1; int purchaseFactor = Listing.PurchaseCount.HasValue ? Listing.PurchaseCount.Value : 1; - float ratingFactor = 1 + (Listing.Rating.HasValue ? Listing.Rating.Value : 0); - return (ratingFactor * (reviewFactor > purchaseFactor ? reviewFactor : purchaseFactor))/(Listing.LowerPrice * Listing.UpperPrice); + return (Listing.Rating * (reviewFactor > purchaseFactor ? reviewFactor : purchaseFactor))/(Listing.LowerPrice * Listing.UpperPrice); } } public ISet Tops { get; private set; } = new HashSet(); diff --git a/src/MultiShop/DataStructures/ResultCategoryExtensions.cs b/src/MultiShop/DataStructures/ResultCategoryExtensions.cs index 126f859..41e9de2 100644 --- a/src/MultiShop/DataStructures/ResultCategoryExtensions.cs +++ b/src/MultiShop/DataStructures/ResultCategoryExtensions.cs @@ -11,8 +11,9 @@ namespace MultiShop.DataStructures switch (category) { case ResultsProfile.Category.RatingPriceRatio: - float dealDiff = a.RatingToPriceRatio - b.RatingToPriceRatio; - int dealCeil = (int)Math.Ceiling(Math.Abs(dealDiff)); + float? dealDiff = a.RatingToPriceRatio - b.RatingToPriceRatio; + if (!dealDiff.HasValue) return null; + int dealCeil = (int)Math.Ceiling(Math.Abs(dealDiff.Value)); return dealDiff < 0 ? -dealCeil : dealCeil; case ResultsProfile.Category.Price: float priceDiff = b.Listing.UpperPrice - a.Listing.UpperPrice; diff --git a/src/MultiShop/Pages/Index.razor b/src/MultiShop/Pages/Index.razor index d26f184..032f142 100644 --- a/src/MultiShop/Pages/Index.razor +++ b/src/MultiShop/Pages/Index.razor @@ -1,3 +1,4 @@ @page "/" +@* TODO: Add main page content.*@

Welcome to MultiShop!

\ No newline at end of file diff --git a/src/MultiShop/Pages/Search.razor b/src/MultiShop/Pages/Search.razor index 9867f74..f752050 100644 --- a/src/MultiShop/Pages/Search.razor +++ b/src/MultiShop/Pages/Search.razor @@ -7,8 +7,7 @@ @inject IConfiguration Configuration @inject IJSRuntime js -@* TODO: Add buttons for the order changing. Add main page. *@ - +@* TODO: Split C# code into a partial class. *@
@@ -392,16 +391,16 @@ { List sorted = new List(listings); sorted.Sort((a, b) => - { - foreach (ResultsProfile.Category category in activeResultsProfile.Order) - { - int? compareResult = category.CompareListings(a, b); - if (compareResult.HasValue && compareResult.Value != 0) - { - return -compareResult.Value; - } - } - return 0; + { + foreach (ResultsProfile.Category category in activeResultsProfile.Order) + { + int? compareResult = category.CompareListings(a, b); + if (compareResult.HasValue && compareResult.Value != 0) + { + return -compareResult.Value; + } + } + return 0; }); return sorted; }); diff --git a/src/MultiShop/wwwroot/modules/modules_content.json b/src/MultiShop/wwwroot/modules/modules_content.json index 6c67e7b..911bf06 100644 --- a/src/MultiShop/wwwroot/modules/modules_content.json +++ b/src/MultiShop/wwwroot/modules/modules_content.json @@ -1,5 +1,5 @@ [ - "AliExpressShop", "HtmlAgilityPack", + "AliExpressShop", "BanggoodShop" ] \ No newline at end of file From 6c684372df64bee570c46a3531e4c40757f4f743 Mon Sep 17 00:00:00 2001 From: Harrison Deng Date: Fri, 21 May 2021 13:32:25 -0500 Subject: [PATCH 012/167] Changed to hosted blazorwasm project. Restructured project following changes. Moved shop assembly fetching to public facing Web API. --- src/AliExpressShop/AliExpressShop.csproj | 12 - .../AliExpressModule}/LRUCache.cs | 0 .../MultiShop.Shop.AliExpressModule.csproj | 12 + .../AliExpressModule}/Shop.cs | 4 +- .../AliExpressModule}/ShopEnumerator.cs | 4 +- .../MultiShop.Shop.BanggoodModule.csproj} | 4 +- .../BanggoodModule}/Shop.cs | 4 +- .../BanggoodModule}/ShopEnumerator.cs | 4 +- .../Framework}/Currency.cs | 2 +- .../Framework}/IShop.cs | 2 +- .../MultiShop.Shop.Framework.csproj} | 0 .../Framework}/ProductListing.cs | 2 +- src/MultiShop/App.razor | 10 - src/MultiShop/Client/App.razor | 39 ++ src/MultiShop/Client/App.razor.cs | 116 ++++++ .../MultiShop.Client.csproj} | 7 +- .../Client/Pages/Authentication.razor | 7 + src/MultiShop/Client/Pages/Counter.razor | 16 + src/MultiShop/Client/Pages/FetchData.razor | 56 +++ src/MultiShop/{ => Client}/Pages/Index.razor | 0 src/MultiShop/{ => Client}/Pages/Info.razor | 2 +- src/MultiShop/{ => Client}/Pages/Search.razor | 242 ++++-------- src/MultiShop/Client/Pages/Search.razor.cs | 196 +++++++++ .../Pages/Search.razor.css} | 0 src/MultiShop/Client/Program.cs | 34 ++ .../Properties/launchSettings.json | 4 +- .../{ => Client}/Shared/DragAndDropList.razor | 0 .../Client/Shared/LoginDisplay.razor | 24 ++ src/MultiShop/Client/Shared/MainLayout.razor | 10 + .../{ => Client}/Shared/MainLayout.razor.css | 0 .../{ => Client}/Shared/NavMenu.razor | 2 +- .../Client/Shared/RedirectToLogin.razor | 8 + src/MultiShop/{ => Client}/_Imports.razor | 7 +- .../100.png => Client/wwwroot/100x100.png} | Bin .../{ => Client}/wwwroot/css/app.css | 0 .../wwwroot/css/bootstrap/bootstrap.min.css | 0 .../css/bootstrap/bootstrap.min.css.map | 0 .../wwwroot/css/open-iconic/FONT-LICENSE | 0 .../wwwroot/css/open-iconic/ICON-LICENSE | 0 .../wwwroot/css/open-iconic/README.md | 0 .../font/css/open-iconic-bootstrap.min.css | 0 .../open-iconic/font/fonts/open-iconic.eot | Bin .../open-iconic/font/fonts/open-iconic.otf | Bin .../open-iconic/font/fonts/open-iconic.svg | 0 .../open-iconic/font/fonts/open-iconic.ttf | Bin .../open-iconic/font/fonts/open-iconic.woff | Bin .../{ => Client}/wwwroot/favicon.ico | Bin src/MultiShop/{ => Client}/wwwroot/index.html | 6 +- .../wwwroot/js/ComponentsSupport.js | 0 src/MultiShop/Program.cs | 22 -- .../Pages/Shared/_LoginPartial.cshtml | 35 ++ .../OidcConfigurationController.cs | 26 ++ .../Controllers/ShopModulesController.cs | 42 ++ .../Controllers/WeatherForecastController.cs | 42 ++ .../Server/Data/ApplicationDbContext.cs | 21 + ...000000000_CreateIdentitySchema.Designer.cs | 373 ++++++++++++++++++ .../00000000000000_CreateIdentitySchema.cs | 288 ++++++++++++++ .../ApplicationDbContextModelSnapshot.cs | 371 +++++++++++++++++ .../Server/Models/ApplicationUser.cs | 12 + src/MultiShop/Server/MultiShop.Server.csproj | 30 ++ src/MultiShop/Server/Pages/Error.cshtml | 42 ++ src/MultiShop/Server/Pages/Error.cshtml.cs | 32 ++ src/MultiShop/Server/Program.cs | 26 ++ .../Server/Properties/launchSettings.json | 30 ++ src/MultiShop/Server/Startup.cs | 82 ++++ src/MultiShop/Server/app.db | Bin 0 -> 139264 bytes .../Server/appsettings.Development.json | 14 + src/MultiShop/Server/appsettings.json | 21 + .../modules/HtmlAgilityPack.dll | Bin .../MultiShop.Shop.AliExpressModule.dll | Bin 0 -> 26112 bytes .../modules/MultiShop.Shop.BanggoodModule.dll | Bin 0 -> 12800 bytes .../modules/SimpleLogger.dll | Bin src/MultiShop/Shared/ListingTableView.razor | 98 ----- src/MultiShop/Shared/MainLayout.razor | 100 ----- src/MultiShop/Shared/MultiShop.Shared.csproj | 14 + .../ProductListingInfo.cs | 4 +- .../ResultCategoryExtensions.cs | 4 +- .../ResultsProfile.cs | 2 +- .../SearchProfile.cs | 4 +- src/MultiShop/Shared/WeatherForecast.cs | 15 + src/MultiShop/wwwroot/appsettings.json | 5 - .../wwwroot/modules/AliExpressShop.dll | Bin 26112 -> 0 bytes .../wwwroot/modules/BanggoodShop.dll | Bin 12288 -> 0 bytes src/MultiShop/wwwroot/modules/gen_metadata.py | 14 - .../wwwroot/modules/modules_content.json | 5 - ...tiShop.Shop.AliExpressModule.Tests.csproj} | 4 +- .../AliExpressModule.Tests}/ShopTest.cs | 4 +- .../AliExpressModule.Tests}/XUnitLogger.cs | 2 +- ...ultiShop.Shop.BanggoodModule.Tests.csproj} | 4 +- .../BanggoodModule.Tests}/ShopTest.cs | 5 +- .../BanggoodModule.Tests}/XUnitLogger.cs | 2 +- 91 files changed, 2153 insertions(+), 478 deletions(-) delete mode 100644 src/AliExpressShop/AliExpressShop.csproj rename src/{AliExpressShop => MultiShop.Shop/AliExpressModule}/LRUCache.cs (100%) create mode 100644 src/MultiShop.Shop/AliExpressModule/MultiShop.Shop.AliExpressModule.csproj rename src/{AliExpressShop => MultiShop.Shop/AliExpressModule}/Shop.cs (96%) rename src/{AliExpressShop => MultiShop.Shop/AliExpressModule}/ShopEnumerator.cs (99%) rename src/{BanggoodShop/BanggoodShop.csproj => MultiShop.Shop/BanggoodModule/MultiShop.Shop.BanggoodModule.csproj} (60%) rename src/{BanggoodShop => MultiShop.Shop/BanggoodModule}/Shop.cs (95%) rename src/{BanggoodShop => MultiShop.Shop/BanggoodModule}/ShopEnumerator.cs (98%) rename src/{MultiShop.ShopFramework => MultiShop.Shop/Framework}/Currency.cs (65%) rename src/{MultiShop.ShopFramework => MultiShop.Shop/Framework}/IShop.cs (92%) rename src/{MultiShop.ShopFramework/MultiShop.ShopFramework.csproj => MultiShop.Shop/Framework/MultiShop.Shop.Framework.csproj} (100%) rename src/{MultiShop.ShopFramework => MultiShop.Shop/Framework}/ProductListing.cs (96%) delete mode 100644 src/MultiShop/App.razor create mode 100644 src/MultiShop/Client/App.razor create mode 100644 src/MultiShop/Client/App.razor.cs rename src/MultiShop/{MultiShop.csproj => Client/MultiShop.Client.csproj} (53%) create mode 100644 src/MultiShop/Client/Pages/Authentication.razor create mode 100644 src/MultiShop/Client/Pages/Counter.razor create mode 100644 src/MultiShop/Client/Pages/FetchData.razor rename src/MultiShop/{ => Client}/Pages/Index.razor (100%) rename src/MultiShop/{ => Client}/Pages/Info.razor (86%) rename src/MultiShop/{ => Client}/Pages/Search.razor (69%) create mode 100644 src/MultiShop/Client/Pages/Search.razor.cs rename src/MultiShop/{Shared/ListingTableView.razor.css => Client/Pages/Search.razor.css} (100%) create mode 100644 src/MultiShop/Client/Program.cs rename src/MultiShop/{ => Client}/Properties/launchSettings.json (92%) rename src/MultiShop/{ => Client}/Shared/DragAndDropList.razor (100%) create mode 100644 src/MultiShop/Client/Shared/LoginDisplay.razor create mode 100644 src/MultiShop/Client/Shared/MainLayout.razor rename src/MultiShop/{ => Client}/Shared/MainLayout.razor.css (100%) rename src/MultiShop/{ => Client}/Shared/NavMenu.razor (92%) create mode 100644 src/MultiShop/Client/Shared/RedirectToLogin.razor rename src/MultiShop/{ => Client}/_Imports.razor (68%) rename src/MultiShop/{wwwroot/100.png => Client/wwwroot/100x100.png} (100%) rename src/MultiShop/{ => Client}/wwwroot/css/app.css (100%) rename src/MultiShop/{ => Client}/wwwroot/css/bootstrap/bootstrap.min.css (100%) rename src/MultiShop/{ => Client}/wwwroot/css/bootstrap/bootstrap.min.css.map (100%) rename src/MultiShop/{ => Client}/wwwroot/css/open-iconic/FONT-LICENSE (100%) rename src/MultiShop/{ => Client}/wwwroot/css/open-iconic/ICON-LICENSE (100%) rename src/MultiShop/{ => Client}/wwwroot/css/open-iconic/README.md (100%) rename src/MultiShop/{ => Client}/wwwroot/css/open-iconic/font/css/open-iconic-bootstrap.min.css (100%) rename src/MultiShop/{ => Client}/wwwroot/css/open-iconic/font/fonts/open-iconic.eot (100%) rename src/MultiShop/{ => Client}/wwwroot/css/open-iconic/font/fonts/open-iconic.otf (100%) rename src/MultiShop/{ => Client}/wwwroot/css/open-iconic/font/fonts/open-iconic.svg (100%) rename src/MultiShop/{ => Client}/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf (100%) rename src/MultiShop/{ => Client}/wwwroot/css/open-iconic/font/fonts/open-iconic.woff (100%) rename src/MultiShop/{ => Client}/wwwroot/favicon.ico (100%) rename src/MultiShop/{ => Client}/wwwroot/index.html (77%) rename src/MultiShop/{ => Client}/wwwroot/js/ComponentsSupport.js (100%) delete mode 100644 src/MultiShop/Program.cs create mode 100644 src/MultiShop/Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml create mode 100644 src/MultiShop/Server/Controllers/OidcConfigurationController.cs create mode 100644 src/MultiShop/Server/Controllers/ShopModulesController.cs create mode 100644 src/MultiShop/Server/Controllers/WeatherForecastController.cs create mode 100644 src/MultiShop/Server/Data/ApplicationDbContext.cs create mode 100644 src/MultiShop/Server/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs create mode 100644 src/MultiShop/Server/Data/Migrations/00000000000000_CreateIdentitySchema.cs create mode 100644 src/MultiShop/Server/Data/Migrations/ApplicationDbContextModelSnapshot.cs create mode 100644 src/MultiShop/Server/Models/ApplicationUser.cs create mode 100644 src/MultiShop/Server/MultiShop.Server.csproj create mode 100644 src/MultiShop/Server/Pages/Error.cshtml create mode 100644 src/MultiShop/Server/Pages/Error.cshtml.cs create mode 100644 src/MultiShop/Server/Program.cs create mode 100644 src/MultiShop/Server/Properties/launchSettings.json create mode 100644 src/MultiShop/Server/Startup.cs create mode 100644 src/MultiShop/Server/app.db create mode 100644 src/MultiShop/Server/appsettings.Development.json create mode 100644 src/MultiShop/Server/appsettings.json rename src/MultiShop/{wwwroot => Server}/modules/HtmlAgilityPack.dll (100%) create mode 100644 src/MultiShop/Server/modules/MultiShop.Shop.AliExpressModule.dll create mode 100644 src/MultiShop/Server/modules/MultiShop.Shop.BanggoodModule.dll rename src/MultiShop/{wwwroot => Server}/modules/SimpleLogger.dll (100%) delete mode 100644 src/MultiShop/Shared/ListingTableView.razor delete mode 100644 src/MultiShop/Shared/MainLayout.razor create mode 100644 src/MultiShop/Shared/MultiShop.Shared.csproj rename src/MultiShop/{DataStructures => Shared}/ProductListingInfo.cs (93%) rename src/MultiShop/{DataStructures => Shared}/ResultCategoryExtensions.cs (95%) rename src/MultiShop/{DataStructures => Shared}/ResultsProfile.cs (95%) rename src/MultiShop/{DataStructures => Shared}/SearchProfile.cs (97%) create mode 100644 src/MultiShop/Shared/WeatherForecast.cs delete mode 100644 src/MultiShop/wwwroot/appsettings.json delete mode 100644 src/MultiShop/wwwroot/modules/AliExpressShop.dll delete mode 100644 src/MultiShop/wwwroot/modules/BanggoodShop.dll delete mode 100644 src/MultiShop/wwwroot/modules/gen_metadata.py delete mode 100644 src/MultiShop/wwwroot/modules/modules_content.json rename test/{AliExpressShop.Tests/AliExpressShop.Tests.csproj => MultiShop.Shop/AliExpressModule.Tests/MultiShop.Shop.AliExpressModule.Tests.csproj} (80%) rename test/{AliExpressShop.Tests => MultiShop.Shop/AliExpressModule.Tests}/ShopTest.cs (95%) rename test/{AliExpressShop.Tests => MultiShop.Shop/AliExpressModule.Tests}/XUnitLogger.cs (94%) rename test/{BanggoodShop.Tests/BanggoodShop.Tests.csproj => MultiShop.Shop/BanggoodModule.Tests/MultiShop.Shop.BanggoodModule.Tests.csproj} (81%) rename test/{BanggoodShop.Tests => MultiShop.Shop/BanggoodModule.Tests}/ShopTest.cs (91%) rename test/{BanggoodShop.Tests => MultiShop.Shop/BanggoodModule.Tests}/XUnitLogger.cs (94%) diff --git a/src/AliExpressShop/AliExpressShop.csproj b/src/AliExpressShop/AliExpressShop.csproj deleted file mode 100644 index c7f0d6c..0000000 --- a/src/AliExpressShop/AliExpressShop.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - net5.0 - - - diff --git a/src/AliExpressShop/LRUCache.cs b/src/MultiShop.Shop/AliExpressModule/LRUCache.cs similarity index 100% rename from src/AliExpressShop/LRUCache.cs rename to src/MultiShop.Shop/AliExpressModule/LRUCache.cs diff --git a/src/MultiShop.Shop/AliExpressModule/MultiShop.Shop.AliExpressModule.csproj b/src/MultiShop.Shop/AliExpressModule/MultiShop.Shop.AliExpressModule.csproj new file mode 100644 index 0000000..b8ce4d9 --- /dev/null +++ b/src/MultiShop.Shop/AliExpressModule/MultiShop.Shop.AliExpressModule.csproj @@ -0,0 +1,12 @@ + + + + + + + + + net5.0 + + + diff --git a/src/AliExpressShop/Shop.cs b/src/MultiShop.Shop/AliExpressModule/Shop.cs similarity index 96% rename from src/AliExpressShop/Shop.cs rename to src/MultiShop.Shop/AliExpressModule/Shop.cs index 217919e..64a4e9a 100644 --- a/src/AliExpressShop/Shop.cs +++ b/src/MultiShop.Shop/AliExpressModule/Shop.cs @@ -10,10 +10,10 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using GameServiceWarden.Core.Collection; -using MultiShop.ShopFramework; +using MultiShop.Shop.Framework; using SimpleLogger; -namespace AliExpressShop +namespace MultiShop.Shop.AliExpressModule { public class Shop : IShop { diff --git a/src/AliExpressShop/ShopEnumerator.cs b/src/MultiShop.Shop/AliExpressModule/ShopEnumerator.cs similarity index 99% rename from src/AliExpressShop/ShopEnumerator.cs rename to src/MultiShop.Shop/AliExpressModule/ShopEnumerator.cs index 0fde592..691ca51 100644 --- a/src/AliExpressShop/ShopEnumerator.cs +++ b/src/MultiShop.Shop/AliExpressModule/ShopEnumerator.cs @@ -7,10 +7,10 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using GameServiceWarden.Core.Collection; -using MultiShop.ShopFramework; +using MultiShop.Shop.Framework; using SimpleLogger; -namespace AliExpressShop +namespace MultiShop.Shop.AliExpressModule { class ShopEnumerator : IAsyncEnumerator { diff --git a/src/BanggoodShop/BanggoodShop.csproj b/src/MultiShop.Shop/BanggoodModule/MultiShop.Shop.BanggoodModule.csproj similarity index 60% rename from src/BanggoodShop/BanggoodShop.csproj rename to src/MultiShop.Shop/BanggoodModule/MultiShop.Shop.BanggoodModule.csproj index 38c6a2a..a3d49eb 100644 --- a/src/BanggoodShop/BanggoodShop.csproj +++ b/src/MultiShop.Shop/BanggoodModule/MultiShop.Shop.BanggoodModule.csproj @@ -1,8 +1,8 @@ - - + + diff --git a/src/BanggoodShop/Shop.cs b/src/MultiShop.Shop/BanggoodModule/Shop.cs similarity index 95% rename from src/BanggoodShop/Shop.cs rename to src/MultiShop.Shop/BanggoodModule/Shop.cs index 3f82e85..73becbc 100644 --- a/src/BanggoodShop/Shop.cs +++ b/src/MultiShop.Shop/BanggoodModule/Shop.cs @@ -3,9 +3,9 @@ using System.Collections.Generic; using System.Net.Http; using System.Runtime.CompilerServices; using System.Threading; -using MultiShop.ShopFramework; +using MultiShop.Shop.Framework; -namespace BanggoodShop +namespace MultiShop.Shop.BanggoodModule { public class Shop : IShop { diff --git a/src/BanggoodShop/ShopEnumerator.cs b/src/MultiShop.Shop/BanggoodModule/ShopEnumerator.cs similarity index 98% rename from src/BanggoodShop/ShopEnumerator.cs rename to src/MultiShop.Shop/BanggoodModule/ShopEnumerator.cs index 3e53fc5..8b680c7 100644 --- a/src/BanggoodShop/ShopEnumerator.cs +++ b/src/MultiShop.Shop/BanggoodModule/ShopEnumerator.cs @@ -9,10 +9,10 @@ using System.Threading; using System.Threading.Tasks; using System.Xml; using HtmlAgilityPack; -using MultiShop.ShopFramework; +using MultiShop.Shop.Framework; using SimpleLogger; -namespace BanggoodShop +namespace MultiShop.Shop.BanggoodModule { class ShopEnumerator : IAsyncEnumerator { diff --git a/src/MultiShop.ShopFramework/Currency.cs b/src/MultiShop.Shop/Framework/Currency.cs similarity index 65% rename from src/MultiShop.ShopFramework/Currency.cs rename to src/MultiShop.Shop/Framework/Currency.cs index 7a40e4b..72cb998 100644 --- a/src/MultiShop.ShopFramework/Currency.cs +++ b/src/MultiShop.Shop/Framework/Currency.cs @@ -1,4 +1,4 @@ -namespace MultiShop.ShopFramework +namespace MultiShop.Shop.Framework { public enum Currency { diff --git a/src/MultiShop.ShopFramework/IShop.cs b/src/MultiShop.Shop/Framework/IShop.cs similarity index 92% rename from src/MultiShop.ShopFramework/IShop.cs rename to src/MultiShop.Shop/Framework/IShop.cs index 3e289a4..9ad25d1 100644 --- a/src/MultiShop.ShopFramework/IShop.cs +++ b/src/MultiShop.Shop/Framework/IShop.cs @@ -4,7 +4,7 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; -namespace MultiShop.ShopFramework +namespace MultiShop.Shop.Framework { public interface IShop : IAsyncEnumerable, IDisposable { diff --git a/src/MultiShop.ShopFramework/MultiShop.ShopFramework.csproj b/src/MultiShop.Shop/Framework/MultiShop.Shop.Framework.csproj similarity index 100% rename from src/MultiShop.ShopFramework/MultiShop.ShopFramework.csproj rename to src/MultiShop.Shop/Framework/MultiShop.Shop.Framework.csproj diff --git a/src/MultiShop.ShopFramework/ProductListing.cs b/src/MultiShop.Shop/Framework/ProductListing.cs similarity index 96% rename from src/MultiShop.ShopFramework/ProductListing.cs rename to src/MultiShop.Shop/Framework/ProductListing.cs index 3e8e9d7..94d3fb9 100644 --- a/src/MultiShop.ShopFramework/ProductListing.cs +++ b/src/MultiShop.Shop/Framework/ProductListing.cs @@ -1,4 +1,4 @@ -namespace MultiShop.ShopFramework +namespace MultiShop.Shop.Framework { public struct ProductListing { diff --git a/src/MultiShop/App.razor b/src/MultiShop/App.razor deleted file mode 100644 index b941644..0000000 --- a/src/MultiShop/App.razor +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - -

Sorry, there's nothing at this address.

-
-
-
diff --git a/src/MultiShop/Client/App.razor b/src/MultiShop/Client/App.razor new file mode 100644 index 0000000..bd523ed --- /dev/null +++ b/src/MultiShop/Client/App.razor @@ -0,0 +1,39 @@ +@using System.Reflection +@using Microsoft.Extensions.Configuration +@inject IHttpClientFactory HttpFactory +@inject IConfiguration Configuration +@implements IDisposable + + + + + + @if (modulesLoaded) + { + + + + @if (!authState.User.Identity.IsAuthenticated) + { + + } + else + { +

You are not authorized to access this resource.

+ } +
+
+
+ } + else + { +

Loading modules...

+ } +
+ + +

Sorry, there's nothing at this address.

+
+
+
+
\ No newline at end of file diff --git a/src/MultiShop/Client/App.razor.cs b/src/MultiShop/Client/App.razor.cs new file mode 100644 index 0000000..274f291 --- /dev/null +++ b/src/MultiShop/Client/App.razor.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Net.Http.Json; +using System.Reflection; +using System.Runtime.Loader; +using System.Threading.Tasks; +using MultiShop.Shop.Framework; +using SimpleLogger; + +namespace MultiShop.Client +{ + public partial class App + { + private bool modulesLoaded = false; + + private Dictionary shops = new Dictionary(); + private Dictionary assemblyData = new Dictionary(); + private Dictionary assemblyCache = new Dictionary(); + protected override async Task OnInitializedAsync() + { + await DownloadShopModules(); + await base.OnInitializedAsync(); + } + private async Task DownloadShopModules() + { + HttpClient http = HttpFactory.CreateClient("Public-MultiShop.ServerAPI"); + Logger.Log($"Fetching shop modules.", LogLevel.Debug); + string[] assemblyFileNames = await http.GetFromJsonAsync("ShopModules"); + Dictionary, string> downloadTasks = new Dictionary, string>(assemblyFileNames.Length); + + foreach (string assemblyFileName in assemblyFileNames) + { + Logger.Log($"Downloading \"{assemblyFileName}\"...", LogLevel.Debug); + downloadTasks.Add(http.GetByteArrayAsync(Path.Join("ShopModules", assemblyFileName + ".dll")), assemblyFileName); + } + + while (downloadTasks.Count != 0) + { + Task data = await Task.WhenAny(downloadTasks.Keys); + string assemblyFileName = downloadTasks[data]; + Logger.Log($"\"{assemblyFileName}\" completed downloading.", LogLevel.Debug); + assemblyData.Add(assemblyFileName, data.Result); + downloadTasks.Remove(data); + } + + AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyDependencyRequest; + + foreach (string assemblyFileName in assemblyData.Keys) + { + Assembly assembly = AppDomain.CurrentDomain.Load(assemblyData[assemblyFileName]); + bool used = false; + foreach (Type type in assembly.GetTypes()) + { + if (typeof(IShop).IsAssignableFrom(type)) + { + IShop shop = Activator.CreateInstance(type) as IShop; + if (shop != null) + { + shop.Initialize(); + shops.Add(shop.ShopName, shop); + Logger.Log($"Registered and started lifetime of module for \"{shop.ShopName}\".", LogLevel.Debug); + used = true; + } + } + } + if (!used) { + Logger.Log($"Since unused, caching \"{assemblyFileName}\".", LogLevel.Debug); + assemblyCache.Add(assemblyFileName, assembly); + } + assemblyData.Remove(assemblyFileName); + } + foreach (string assembly in assemblyData.Keys) + { + Logger.Log($"{assembly} was unused.", LogLevel.Warning); + } + foreach (string assembly in assemblyCache.Keys) + { + Logger.Log($"\"{assembly}\" was unused.", LogLevel.Warning); + } + assemblyData.Clear(); + assemblyCache.Clear(); + modulesLoaded = true; + } + + + private Assembly OnAssemblyDependencyRequest(object sender, ResolveEventArgs args) + { + string dependencyName = args.Name.Substring(0, args.Name.IndexOf(',')); + Logger.Log($"Assembly \"{args.RequestingAssembly.GetName().Name}\" is requesting dependency assembly \"{dependencyName}\".", LogLevel.Debug); + if (assemblyCache.ContainsKey(dependencyName)) { + Logger.Log($"Found \"{dependencyName}\" in cache.", LogLevel.Debug); + Assembly dep = assemblyCache[dependencyName]; + assemblyCache.Remove(dependencyName); + return dep; + } else if (assemblyData.ContainsKey(dependencyName)) { + return AppDomain.CurrentDomain.Load(assemblyData[dependencyName]); + } else { + Logger.Log($"No dependency under name \"{args.Name}\"", LogLevel.Warning); + return null; + } + } + + + public void Dispose() + { + foreach (string name in shops.Keys) + { + shops[name].Dispose(); + Logger.Log($"Ending lifetime of shop module for \"{name}\"."); + } + } + } +} \ No newline at end of file diff --git a/src/MultiShop/MultiShop.csproj b/src/MultiShop/Client/MultiShop.Client.csproj similarity index 53% rename from src/MultiShop/MultiShop.csproj rename to src/MultiShop/Client/MultiShop.Client.csproj index 8031445..7838df0 100644 --- a/src/MultiShop/MultiShop.csproj +++ b/src/MultiShop/Client/MultiShop.Client.csproj @@ -7,12 +7,15 @@ + + - - + + +
diff --git a/src/MultiShop/Client/Pages/Authentication.razor b/src/MultiShop/Client/Pages/Authentication.razor new file mode 100644 index 0000000..ec9aa2f --- /dev/null +++ b/src/MultiShop/Client/Pages/Authentication.razor @@ -0,0 +1,7 @@ +@page "/authentication/{action}" +@using Microsoft.AspNetCore.Components.WebAssembly.Authentication + + +@code{ + [Parameter] public string Action { get; set; } +} diff --git a/src/MultiShop/Client/Pages/Counter.razor b/src/MultiShop/Client/Pages/Counter.razor new file mode 100644 index 0000000..bd823e5 --- /dev/null +++ b/src/MultiShop/Client/Pages/Counter.razor @@ -0,0 +1,16 @@ +@page "/counter" + +

Counter

+ +

Current count: @currentCount

+ + + +@code { + private int currentCount = 0; + + private void IncrementCount() + { + currentCount++; + } +} diff --git a/src/MultiShop/Client/Pages/FetchData.razor b/src/MultiShop/Client/Pages/FetchData.razor new file mode 100644 index 0000000..91a9935 --- /dev/null +++ b/src/MultiShop/Client/Pages/FetchData.razor @@ -0,0 +1,56 @@ +@page "/fetchdata" +@using Microsoft.AspNetCore.Authorization +@using Microsoft.AspNetCore.Components.WebAssembly.Authentication +@using MultiShop.Shared +@attribute [Authorize] +@inject HttpClient Http + +

Weather forecast

+ +

This component demonstrates fetching data from the server.

+ +@if (forecasts == null) +{ +

Loading...

+} +else +{ +
+ + + + + + + + + + @foreach (var forecast in forecasts) + { + + + + + + + } + +
DateTemp. (C)Temp. (F)Summary
@forecast.Date.ToShortDateString()@forecast.TemperatureC@forecast.TemperatureF@forecast.Summary
+} + +@code { + private WeatherForecast[] forecasts; + + protected override async Task OnInitializedAsync() + { + try + { + forecasts = await Http.GetFromJsonAsync("WeatherForecast"); + } + catch (AccessTokenNotAvailableException exception) + { + exception.Redirect(); + } + } + +} diff --git a/src/MultiShop/Pages/Index.razor b/src/MultiShop/Client/Pages/Index.razor similarity index 100% rename from src/MultiShop/Pages/Index.razor rename to src/MultiShop/Client/Pages/Index.razor diff --git a/src/MultiShop/Pages/Info.razor b/src/MultiShop/Client/Pages/Info.razor similarity index 86% rename from src/MultiShop/Pages/Info.razor rename to src/MultiShop/Client/Pages/Info.razor index dec2b7f..10848e6 100644 --- a/src/MultiShop/Pages/Info.razor +++ b/src/MultiShop/Client/Pages/Info.razor @@ -1,5 +1,5 @@ @page "/info" -@using ShopFramework +@using Shop.Framework
diff --git a/src/MultiShop/Pages/Search.razor b/src/MultiShop/Client/Pages/Search.razor similarity index 69% rename from src/MultiShop/Pages/Search.razor rename to src/MultiShop/Client/Pages/Search.razor index f752050..04b50ed 100644 --- a/src/MultiShop/Pages/Search.razor +++ b/src/MultiShop/Client/Pages/Search.razor @@ -1,13 +1,10 @@ @page "/search/{Query?}" @using Microsoft.Extensions.Configuration -@using ShopFramework -@using SimpleLogger -@using DataStructures +@using MultiShop.Shared @inject HttpClient Http @inject IConfiguration Configuration @inject IJSRuntime js -@* TODO: Split C# code into a partial class. *@
@@ -242,171 +239,76 @@
- + @if (!organizing && listings.Count > 0) + { +
+ + + + + + + + + + + + + @if (!showSearchConfiguration && !searching) { + + + + + + + + + + + + + + } +
NamePriceShippingPurchasesRatingReviews
+
@product.Listing.Name
+ From @product.ShopName + @if (product.Listing.ConvertedPrices) + { + Converted price + } + @foreach (ResultsProfile.Category c in product.Tops) + { + @CategoryTags(c) + } +
+ @if (product.Listing.UpperPrice != product.Listing.LowerPrice) + { +
+ @product.Listing.LowerPrice to @product.Listing.UpperPrice +
+ } + else + { +
+ @GetOrNA(product.Listing.LowerPrice) +
+ } +
+
+ @GetOrNA(product.Listing.Shipping) +
+
+
+ @GetOrNA(product.Listing.PurchaseCount) +
+
+
+ @(product.Listing.Rating != null ? string.Format("{0:P2}", product.Listing.Rating) : "N/A") +
+
@GetOrNA(product.Listing.ReviewCount) + View +
+
+ }
- - -@code { - [CascadingParameter(Name = "Shops")] - public Dictionary Shops { get; set; } - - [Parameter] - public string Query { get; set; } - - private SearchProfile activeProfile = new SearchProfile(); - private ResultsProfile activeResultsProfile = new ResultsProfile(); - - private bool showSearchConfiguration = false; - private bool showResultsConfiguration = false; - - private string ToggleSearchConfigButtonCss - { - get => "btn btn-outline-secondary" + (showSearchConfiguration ? " active" : ""); - } - - private string ToggleResultsConfigurationcss { - get => "btn btn-outline-secondary btn-tab" + (showResultsConfiguration ? " active" : ""); - } - - private bool searched = false; - private bool searching = false; - private bool organizing = false; - - private int resultsChecked = 0; - private List listings = new List(); - - protected override void OnInitialized() - { - foreach (string shop in Shops.Keys) - { - activeProfile.shopStates[shop] = true; - } - base.OnInitialized(); - } - - protected override async Task OnInitializedAsync() - { - await base.OnInitializedAsync(); - } - - protected override async Task OnParametersSetAsync() - { - if (!string.IsNullOrEmpty(Query)) - { - await PerformSearch(Query); - } - await base.OnParametersSetAsync(); - } - - private async Task PerformSearch(string query) - { - if (string.IsNullOrWhiteSpace(query)) return; - if (searching) return; - searching = true; - Logger.Log($"Received search request for \"{query}\".", LogLevel.Debug); - resultsChecked = 0; - listings.Clear(); - Dictionary> greatest = new Dictionary>(); - foreach (string shopName in Shops.Keys) - { - if (activeProfile.shopStates[shopName]) - { - Logger.Log($"Querying \"{shopName}\" for products."); - Shops[shopName].SetupSession(query, activeProfile.currency); - int shopViableResults = 0; - await foreach (ProductListing listing in Shops[shopName]) - { - resultsChecked += 1; - if (resultsChecked % 50 == 0) - { - StateHasChanged(); - await Task.Yield(); - } - - - if (listing.Shipping == null && !activeProfile.keepUnknownShipping || (activeProfile.enableMaxShippingFee && listing.Shipping > activeProfile.MaxShippingFee)) continue; - float shippingDifference = listing.Shipping != null ? listing.Shipping.Value : 0; - if (!(listing.LowerPrice + shippingDifference >= activeProfile.lowerPrice && (!activeProfile.enableUpperPrice || listing.UpperPrice + shippingDifference <= activeProfile.UpperPrice))) continue; - if ((listing.Rating == null && !activeProfile.keepUnrated) && activeProfile.minRating > (listing.Rating == null ? 0 : listing.Rating)) continue; - if ((listing.PurchaseCount == null && !activeProfile.keepUnknownPurchaseCount) || activeProfile.minPurchases > (listing.PurchaseCount == null ? 0 : listing.PurchaseCount)) continue; - if ((listing.ReviewCount == null && !activeProfile.keepUnknownRatingCount) || activeProfile.minReviews > (listing.ReviewCount == null ? 0 : listing.ReviewCount)) continue; - - ProductListingInfo info = new ProductListingInfo(listing, shopName); - listings.Add(info); - await Task.Yield(); - foreach (ResultsProfile.Category c in Enum.GetValues()) - { - if (!greatest.ContainsKey(c)) greatest[c] = new List(); - if (greatest[c].Count > 0) - { - int? compResult = c.CompareListings(info, greatest[c][0]); - if (compResult.HasValue) - { - if (compResult > 0) greatest[c].Clear(); - if (compResult >= 0) greatest[c].Add(info); - } - } - else - { - if (c.CompareListings(info, info).HasValue) - { - greatest[c].Add(info); - } - } - } - - shopViableResults += 1; - if (shopViableResults >= activeProfile.maxResults) break; - } - Logger.Log($"\"{shopName}\" has completed. There are {listings.Count} results in total.", LogLevel.Debug); - } - else - { - Logger.Log($"Skipping {shopName} since it's disabled."); - } - } - searching = false; - searched = true; - - foreach (ResultsProfile.Category c in greatest.Keys) - { - foreach (ProductListingInfo info in greatest[c]) - { - info.Tops.Add(c); - } - } - - await Organize(activeResultsProfile.Order); - } - - private async Task Organize(List order) - { - if (searching) return; - organizing = true; - StateHasChanged(); - - List sortedResults = await Task.Run>(() => - { - List sorted = new List(listings); - sorted.Sort((a, b) => - { - foreach (ResultsProfile.Category category in activeResultsProfile.Order) - { - int? compareResult = category.CompareListings(a, b); - if (compareResult.HasValue && compareResult.Value != 0) - { - return -compareResult.Value; - } - } - return 0; - }); - return sorted; - }); - listings.Clear(); - listings.AddRange(sortedResults); - organizing = false; - StateHasChanged(); - } -} diff --git a/src/MultiShop/Client/Pages/Search.razor.cs b/src/MultiShop/Client/Pages/Search.razor.cs new file mode 100644 index 0000000..4273ef5 --- /dev/null +++ b/src/MultiShop/Client/Pages/Search.razor.cs @@ -0,0 +1,196 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using MultiShop.Shared; +using MultiShop.Shop.Framework; +using SimpleLogger; + +namespace MultiShop.Client.Pages +{ + public partial class Search + { + [CascadingParameter(Name = "Shops")] + public Dictionary Shops { get; set; } + + [Parameter] + public string Query { get; set; } + + private SearchProfile activeProfile = new SearchProfile(); + private ResultsProfile activeResultsProfile = new ResultsProfile(); + + private bool showSearchConfiguration = false; + private bool showResultsConfiguration = false; + + private string ToggleSearchConfigButtonCss + { + get => "btn btn-outline-secondary" + (showSearchConfiguration ? " active" : ""); + } + + private string ToggleResultsConfigurationcss { + get => "btn btn-outline-secondary btn-tab" + (showResultsConfiguration ? " active" : ""); + } + + private bool searched = false; + private bool searching = false; + private bool organizing = false; + + private int resultsChecked = 0; + private List listings = new List(); + + protected override void OnInitialized() + { + foreach (string shop in Shops.Keys) + { + activeProfile.shopStates[shop] = true; + } + base.OnInitialized(); + } + + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + } + + protected override async Task OnParametersSetAsync() + { + if (!string.IsNullOrEmpty(Query)) + { + await PerformSearch(Query); + } + await base.OnParametersSetAsync(); + } + + private async Task PerformSearch(string query) + { + if (string.IsNullOrWhiteSpace(query)) return; + if (searching) return; + searching = true; + Logger.Log($"Received search request for \"{query}\".", LogLevel.Debug); + resultsChecked = 0; + listings.Clear(); + Dictionary> greatest = new Dictionary>(); + foreach (string shopName in Shops.Keys) + { + if (activeProfile.shopStates[shopName]) + { + Logger.Log($"Querying \"{shopName}\" for products."); + Shops[shopName].SetupSession(query, activeProfile.currency); + int shopViableResults = 0; + await foreach (ProductListing listing in Shops[shopName]) + { + resultsChecked += 1; + if (resultsChecked % 50 == 0) + { + StateHasChanged(); + await Task.Yield(); + } + + + if (listing.Shipping == null && !activeProfile.keepUnknownShipping || (activeProfile.enableMaxShippingFee && listing.Shipping > activeProfile.MaxShippingFee)) continue; + float shippingDifference = listing.Shipping != null ? listing.Shipping.Value : 0; + if (!(listing.LowerPrice + shippingDifference >= activeProfile.lowerPrice && (!activeProfile.enableUpperPrice || listing.UpperPrice + shippingDifference <= activeProfile.UpperPrice))) continue; + if ((listing.Rating == null && !activeProfile.keepUnrated) && activeProfile.minRating > (listing.Rating == null ? 0 : listing.Rating)) continue; + if ((listing.PurchaseCount == null && !activeProfile.keepUnknownPurchaseCount) || activeProfile.minPurchases > (listing.PurchaseCount == null ? 0 : listing.PurchaseCount)) continue; + if ((listing.ReviewCount == null && !activeProfile.keepUnknownRatingCount) || activeProfile.minReviews > (listing.ReviewCount == null ? 0 : listing.ReviewCount)) continue; + + ProductListingInfo info = new ProductListingInfo(listing, shopName); + listings.Add(info); + foreach (ResultsProfile.Category c in Enum.GetValues()) + { + if (!greatest.ContainsKey(c)) greatest[c] = new List(); + if (greatest[c].Count > 0) + { + int? compResult = c.CompareListings(info, greatest[c][0]); + if (compResult.HasValue) + { + if (compResult > 0) greatest[c].Clear(); + if (compResult >= 0) greatest[c].Add(info); + } + } + else + { + if (c.CompareListings(info, info).HasValue) + { + greatest[c].Add(info); + } + } + } + + shopViableResults += 1; + if (shopViableResults >= activeProfile.maxResults) break; + } + Logger.Log($"\"{shopName}\" has completed. There are {listings.Count} results in total.", LogLevel.Debug); + } + else + { + Logger.Log($"Skipping {shopName} since it's disabled."); + } + } + searching = false; + searched = true; + + foreach (ResultsProfile.Category c in greatest.Keys) + { + foreach (ProductListingInfo info in greatest[c]) + { + info.Tops.Add(c); + } + } + + await Organize(activeResultsProfile.Order); + } + + private async Task Organize(List order) + { + if (searching) return; + organizing = true; + StateHasChanged(); + + List sortedResults = await Task.Run>(() => + { + List sorted = new List(listings); + sorted.Sort((a, b) => + { + foreach (ResultsProfile.Category category in activeResultsProfile.Order) + { + int? compareResult = category.CompareListings(a, b); + if (compareResult.HasValue && compareResult.Value != 0) + { + return -compareResult.Value; + } + } + return 0; + }); + return sorted; + }); + listings.Clear(); + listings.AddRange(sortedResults); + organizing = false; + StateHasChanged(); + } + + + private string GetOrNA(object data, string prepend = null, string append = null) + { + return data != null ? (prepend + data.ToString() + append) : "N/A"; + } + + private string CategoryTags(ResultsProfile.Category c) + { + switch (c) + { + case ResultsProfile.Category.RatingPriceRatio: + return "Best rating to price ratio"; + case ResultsProfile.Category.Price: + return "Lowest price"; + case ResultsProfile.Category.Purchases: + return "Most purchases"; + case ResultsProfile.Category.Reviews: + return "Most reviews"; + } + throw new ArgumentException($"{c} does not have an associated string."); + } + } +} \ No newline at end of file diff --git a/src/MultiShop/Shared/ListingTableView.razor.css b/src/MultiShop/Client/Pages/Search.razor.css similarity index 100% rename from src/MultiShop/Shared/ListingTableView.razor.css rename to src/MultiShop/Client/Pages/Search.razor.css diff --git a/src/MultiShop/Client/Program.cs b/src/MultiShop/Client/Program.cs new file mode 100644 index 0000000..730b4ee --- /dev/null +++ b/src/MultiShop/Client/Program.cs @@ -0,0 +1,34 @@ +using System; +using System.Net.Http; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Text; +using Microsoft.AspNetCore.Components.WebAssembly.Authentication; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using SimpleLogger; + +namespace MultiShop.Client +{ + public class Program + { + public static async Task Main(string[] args) + { + Logger.AddLogListener(new ConsoleLogReceiver() {Level = LogLevel.Debug}); + var builder = WebAssemblyHostBuilder.CreateDefault(args); + builder.RootComponents.Add("#app"); + + builder.Services.AddHttpClient("MultiShop.ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)).AddHttpMessageHandler(); + + builder.Services.AddHttpClient("Public-MultiShop.ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)); + + // Supply HttpClient instances that include access tokens when making requests to the server project + builder.Services.AddScoped(sp => sp.GetRequiredService().CreateClient("MultiShop.ServerAPI")); + + builder.Services.AddApiAuthorization(); + + await builder.Build().RunAsync(); + } + } +} diff --git a/src/MultiShop/Properties/launchSettings.json b/src/MultiShop/Client/Properties/launchSettings.json similarity index 92% rename from src/MultiShop/Properties/launchSettings.json rename to src/MultiShop/Client/Properties/launchSettings.json index 4b243ab..253847b 100644 --- a/src/MultiShop/Properties/launchSettings.json +++ b/src/MultiShop/Client/Properties/launchSettings.json @@ -3,8 +3,8 @@ "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { - "applicationUrl": "http://localhost:46072", - "sslPort": 44317 + "applicationUrl": "http://localhost:5738", + "sslPort": 44353 } }, "profiles": { diff --git a/src/MultiShop/Shared/DragAndDropList.razor b/src/MultiShop/Client/Shared/DragAndDropList.razor similarity index 100% rename from src/MultiShop/Shared/DragAndDropList.razor rename to src/MultiShop/Client/Shared/DragAndDropList.razor diff --git a/src/MultiShop/Client/Shared/LoginDisplay.razor b/src/MultiShop/Client/Shared/LoginDisplay.razor new file mode 100644 index 0000000..bbb2cb3 --- /dev/null +++ b/src/MultiShop/Client/Shared/LoginDisplay.razor @@ -0,0 +1,24 @@ +@using Microsoft.AspNetCore.Components.Authorization +@using Microsoft.AspNetCore.Components.WebAssembly.Authentication + +@inject NavigationManager Navigation +@inject SignOutSessionStateManager SignOutManager + + + + Hello, @context.User.Identity.Name! + + + + Register + Log in + + + +@code{ + private async Task BeginSignOut(MouseEventArgs args) + { + await SignOutManager.SetSignOutState(); + Navigation.NavigateTo("authentication/logout"); + } +} diff --git a/src/MultiShop/Client/Shared/MainLayout.razor b/src/MultiShop/Client/Shared/MainLayout.razor new file mode 100644 index 0000000..f7def2c --- /dev/null +++ b/src/MultiShop/Client/Shared/MainLayout.razor @@ -0,0 +1,10 @@ +@inherits LayoutComponentBase + +
+ +
+
+ @Body +
+
+
\ No newline at end of file diff --git a/src/MultiShop/Shared/MainLayout.razor.css b/src/MultiShop/Client/Shared/MainLayout.razor.css similarity index 100% rename from src/MultiShop/Shared/MainLayout.razor.css rename to src/MultiShop/Client/Shared/MainLayout.razor.css diff --git a/src/MultiShop/Shared/NavMenu.razor b/src/MultiShop/Client/Shared/NavMenu.razor similarity index 92% rename from src/MultiShop/Shared/NavMenu.razor rename to src/MultiShop/Client/Shared/NavMenu.razor index 791fde0..6c06a7f 100644 --- a/src/MultiShop/Shared/NavMenu.razor +++ b/src/MultiShop/Client/Shared/NavMenu.razor @@ -1,6 +1,6 @@ 
+ + - \ No newline at end of file + diff --git a/src/MultiShop/wwwroot/js/ComponentsSupport.js b/src/MultiShop/Client/wwwroot/js/ComponentsSupport.js similarity index 100% rename from src/MultiShop/wwwroot/js/ComponentsSupport.js rename to src/MultiShop/Client/wwwroot/js/ComponentsSupport.js diff --git a/src/MultiShop/Program.cs b/src/MultiShop/Program.cs deleted file mode 100644 index 24caf48..0000000 --- a/src/MultiShop/Program.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Components.WebAssembly.Hosting; -using Microsoft.Extensions.DependencyInjection; -using SimpleLogger; - -namespace MultiShop -{ - public class Program - { - public static async Task Main(string[] args) - { - - Logger.AddLogListener(new ConsoleLogReceiver() {Level = LogLevel.Debug}); - var builder = WebAssemblyHostBuilder.CreateDefault(args); - builder.RootComponents.Add("#app"); - builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); - await builder.Build().RunAsync(); - } - } -} diff --git a/src/MultiShop/Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml b/src/MultiShop/Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml new file mode 100644 index 0000000..2e57a9d --- /dev/null +++ b/src/MultiShop/Server/Areas/Identity/Pages/Shared/_LoginPartial.cshtml @@ -0,0 +1,35 @@ +@using Microsoft.AspNetCore.Identity +@using MultiShop.Server.Models +@inject SignInManager SignInManager +@inject UserManager UserManager +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers + +@{ + var returnUrl = "/"; + if (Context.Request.Query.TryGetValue("returnUrl", out var existingUrl)) { + returnUrl = existingUrl; + } +} + + diff --git a/src/MultiShop/Server/Controllers/OidcConfigurationController.cs b/src/MultiShop/Server/Controllers/OidcConfigurationController.cs new file mode 100644 index 0000000..6daea88 --- /dev/null +++ b/src/MultiShop/Server/Controllers/OidcConfigurationController.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.ApiAuthorization.IdentityServer; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +namespace MultiShop.Server.Controllers +{ + public class OidcConfigurationController : Controller + { + private readonly ILogger _logger; + + public OidcConfigurationController(IClientRequestParametersProvider clientRequestParametersProvider, ILogger logger) + { + ClientRequestParametersProvider = clientRequestParametersProvider; + _logger = logger; + } + + public IClientRequestParametersProvider ClientRequestParametersProvider { get; } + + [HttpGet("_configuration/{clientId}")] + public IActionResult GetClientRequestParameters([FromRoute]string clientId) + { + var parameters = ClientRequestParametersProvider.GetClientParameters(HttpContext, clientId); + return Ok(parameters); + } + } +} diff --git a/src/MultiShop/Server/Controllers/ShopModulesController.cs b/src/MultiShop/Server/Controllers/ShopModulesController.cs new file mode 100644 index 0000000..30fe516 --- /dev/null +++ b/src/MultiShop/Server/Controllers/ShopModulesController.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; + +namespace MultiShop.Server.Controllers +{ + [ApiController] + [Route("[controller]")] + public class ShopModulesController : ControllerBase + { + private readonly IConfiguration configuration; + private IDictionary shopAssemblyData; + + + + public ShopModulesController(IConfiguration configuration) + { + this.configuration = configuration; + this.shopAssemblyData = new Dictionary(); + } + + public IEnumerable GetShopModuleNames() { + foreach (string file in Directory.EnumerateFiles(configuration["ModulesDir"])) + { + if (Path.GetExtension(file).ToLower().Equals(".dll")) { + yield return Path.GetFileNameWithoutExtension(file); + } + } + } + + + [HttpGet] + [Route("{shopModuleName}")] + public ActionResult GetModule(string shopModuleName) { + string shopPath = Path.Join(configuration["ModulesDir"], shopModuleName); + if (!System.IO.File.Exists(shopPath)) return NotFound(); + return File(new FileStream(shopPath, FileMode.Open), "application/shop-dll"); + } + } +} \ No newline at end of file diff --git a/src/MultiShop/Server/Controllers/WeatherForecastController.cs b/src/MultiShop/Server/Controllers/WeatherForecastController.cs new file mode 100644 index 0000000..50f4aff --- /dev/null +++ b/src/MultiShop/Server/Controllers/WeatherForecastController.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using MultiShop.Shared; + +namespace MultiShop.Server.Controllers +{ + [Authorize] + [ApiController] + [Route("[controller]")] + public class WeatherForecastController : ControllerBase + { + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet] + public IEnumerable Get() + { + var rng = new Random(); + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateTime.Now.AddDays(index), + TemperatureC = rng.Next(-20, 55), + Summary = Summaries[rng.Next(Summaries.Length)] + }) + .ToArray(); + } + } +} diff --git a/src/MultiShop/Server/Data/ApplicationDbContext.cs b/src/MultiShop/Server/Data/ApplicationDbContext.cs new file mode 100644 index 0000000..70ff868 --- /dev/null +++ b/src/MultiShop/Server/Data/ApplicationDbContext.cs @@ -0,0 +1,21 @@ +using MultiShop.Server.Models; +using IdentityServer4.EntityFramework.Options; +using Microsoft.AspNetCore.ApiAuthorization.IdentityServer; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Options; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace MultiShop.Server.Data +{ + public class ApplicationDbContext : ApiAuthorizationDbContext + { + public ApplicationDbContext( + DbContextOptions options, + IOptions operationalStoreOptions) : base(options, operationalStoreOptions) + { + } + } +} diff --git a/src/MultiShop/Server/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs b/src/MultiShop/Server/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs new file mode 100644 index 0000000..8d883dc --- /dev/null +++ b/src/MultiShop/Server/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs @@ -0,0 +1,373 @@ +// +using System; +using MultiShop.Server.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace MultiShop.Server.Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("00000000000000_CreateIdentitySchema")] + partial class CreateIdentitySchema + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.0-rc.1.20417.2"); + + modelBuilder.Entity("MultiShop.Server.Models.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b => + { + b.Property("UserCode") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("CreationTime") + .HasColumnType("TEXT"); + + b.Property("Data") + .IsRequired() + .HasMaxLength(50000) + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("DeviceCode") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Expiration") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("UserCode"); + + b.HasIndex("DeviceCode") + .IsUnique(); + + b.HasIndex("Expiration"); + + b.ToTable("DeviceCodes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b => + { + b.Property("Key") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ConsumedTime") + .HasColumnType("TEXT"); + + b.Property("CreationTime") + .HasColumnType("TEXT"); + + b.Property("Data") + .IsRequired() + .HasMaxLength(50000) + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Expiration") + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Key"); + + b.HasIndex("Expiration"); + + b.HasIndex("SubjectId", "ClientId", "Type"); + + b.HasIndex("SubjectId", "SessionId", "Type"); + + b.ToTable("PersistedGrants"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("MultiShop.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("MultiShop.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MultiShop.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("MultiShop.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/MultiShop/Server/Data/Migrations/00000000000000_CreateIdentitySchema.cs b/src/MultiShop/Server/Data/Migrations/00000000000000_CreateIdentitySchema.cs new file mode 100644 index 0000000..0e07926 --- /dev/null +++ b/src/MultiShop/Server/Data/Migrations/00000000000000_CreateIdentitySchema.cs @@ -0,0 +1,288 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace MultiShop.Server.Data.Migrations +{ + public partial class CreateIdentitySchema : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "AspNetRoles", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + Name = table.Column(type: "TEXT", maxLength: 256, nullable: true), + NormalizedName = table.Column(type: "TEXT", maxLength: 256, nullable: true), + ConcurrencyStamp = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoles", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "AspNetUsers", + columns: table => new + { + Id = table.Column(type: "TEXT", nullable: false), + UserName = table.Column(type: "TEXT", maxLength: 256, nullable: true), + NormalizedUserName = table.Column(type: "TEXT", maxLength: 256, nullable: true), + Email = table.Column(type: "TEXT", maxLength: 256, nullable: true), + NormalizedEmail = table.Column(type: "TEXT", maxLength: 256, nullable: true), + EmailConfirmed = table.Column(type: "INTEGER", nullable: false), + PasswordHash = table.Column(type: "TEXT", nullable: true), + SecurityStamp = table.Column(type: "TEXT", nullable: true), + ConcurrencyStamp = table.Column(type: "TEXT", nullable: true), + PhoneNumber = table.Column(type: "TEXT", nullable: true), + PhoneNumberConfirmed = table.Column(type: "INTEGER", nullable: false), + TwoFactorEnabled = table.Column(type: "INTEGER", nullable: false), + LockoutEnd = table.Column(type: "TEXT", nullable: true), + LockoutEnabled = table.Column(type: "INTEGER", nullable: false), + AccessFailedCount = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUsers", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "DeviceCodes", + columns: table => new + { + UserCode = table.Column(type: "TEXT", maxLength: 200, nullable: false), + DeviceCode = table.Column(type: "TEXT", maxLength: 200, nullable: false), + SubjectId = table.Column(type: "TEXT", maxLength: 200, nullable: true), + SessionId = table.Column(type: "TEXT", maxLength: 100, nullable: true), + ClientId = table.Column(type: "TEXT", maxLength: 200, nullable: false), + Description = table.Column(type: "TEXT", maxLength: 200, nullable: true), + CreationTime = table.Column(type: "TEXT", nullable: false), + Expiration = table.Column(type: "TEXT", nullable: false), + Data = table.Column(type: "TEXT", maxLength: 50000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_DeviceCodes", x => x.UserCode); + }); + + migrationBuilder.CreateTable( + name: "PersistedGrants", + columns: table => new + { + Key = table.Column(type: "TEXT", maxLength: 200, nullable: false), + Type = table.Column(type: "TEXT", maxLength: 50, nullable: false), + SubjectId = table.Column(type: "TEXT", maxLength: 200, nullable: true), + SessionId = table.Column(type: "TEXT", maxLength: 100, nullable: true), + ClientId = table.Column(type: "TEXT", maxLength: 200, nullable: false), + Description = table.Column(type: "TEXT", maxLength: 200, nullable: true), + CreationTime = table.Column(type: "TEXT", nullable: false), + Expiration = table.Column(type: "TEXT", nullable: true), + ConsumedTime = table.Column(type: "TEXT", nullable: true), + Data = table.Column(type: "TEXT", maxLength: 50000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PersistedGrants", x => x.Key); + }); + + migrationBuilder.CreateTable( + name: "AspNetRoleClaims", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + RoleId = table.Column(type: "TEXT", nullable: false), + ClaimType = table.Column(type: "TEXT", nullable: true), + ClaimValue = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetRoleClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetRoleClaims_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserClaims", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + UserId = table.Column(type: "TEXT", nullable: false), + ClaimType = table.Column(type: "TEXT", nullable: true), + ClaimValue = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserClaims", x => x.Id); + table.ForeignKey( + name: "FK_AspNetUserClaims_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserLogins", + columns: table => new + { + LoginProvider = table.Column(type: "TEXT", maxLength: 128, nullable: false), + ProviderKey = table.Column(type: "TEXT", maxLength: 128, nullable: false), + ProviderDisplayName = table.Column(type: "TEXT", nullable: true), + UserId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserLogins", x => new { x.LoginProvider, x.ProviderKey }); + table.ForeignKey( + name: "FK_AspNetUserLogins_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserRoles", + columns: table => new + { + UserId = table.Column(type: "TEXT", nullable: false), + RoleId = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserRoles", x => new { x.UserId, x.RoleId }); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetRoles_RoleId", + column: x => x.RoleId, + principalTable: "AspNetRoles", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_AspNetUserRoles_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "AspNetUserTokens", + columns: table => new + { + UserId = table.Column(type: "TEXT", nullable: false), + LoginProvider = table.Column(type: "TEXT", maxLength: 128, nullable: false), + Name = table.Column(type: "TEXT", maxLength: 128, nullable: false), + Value = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_AspNetUserTokens", x => new { x.UserId, x.LoginProvider, x.Name }); + table.ForeignKey( + name: "FK_AspNetUserTokens_AspNetUsers_UserId", + column: x => x.UserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_AspNetRoleClaims_RoleId", + table: "AspNetRoleClaims", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "RoleNameIndex", + table: "AspNetRoles", + column: "NormalizedName", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserClaims_UserId", + table: "AspNetUserClaims", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserLogins_UserId", + table: "AspNetUserLogins", + column: "UserId"); + + migrationBuilder.CreateIndex( + name: "IX_AspNetUserRoles_RoleId", + table: "AspNetUserRoles", + column: "RoleId"); + + migrationBuilder.CreateIndex( + name: "EmailIndex", + table: "AspNetUsers", + column: "NormalizedEmail"); + + migrationBuilder.CreateIndex( + name: "UserNameIndex", + table: "AspNetUsers", + column: "NormalizedUserName", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_DeviceCodes_DeviceCode", + table: "DeviceCodes", + column: "DeviceCode", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_DeviceCodes_Expiration", + table: "DeviceCodes", + column: "Expiration"); + + migrationBuilder.CreateIndex( + name: "IX_PersistedGrants_Expiration", + table: "PersistedGrants", + column: "Expiration"); + + migrationBuilder.CreateIndex( + name: "IX_PersistedGrants_SubjectId_ClientId_Type", + table: "PersistedGrants", + columns: new[] { "SubjectId", "ClientId", "Type" }); + + migrationBuilder.CreateIndex( + name: "IX_PersistedGrants_SubjectId_SessionId_Type", + table: "PersistedGrants", + columns: new[] { "SubjectId", "SessionId", "Type" }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AspNetRoleClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserClaims"); + + migrationBuilder.DropTable( + name: "AspNetUserLogins"); + + migrationBuilder.DropTable( + name: "AspNetUserRoles"); + + migrationBuilder.DropTable( + name: "AspNetUserTokens"); + + migrationBuilder.DropTable( + name: "DeviceCodes"); + + migrationBuilder.DropTable( + name: "PersistedGrants"); + + migrationBuilder.DropTable( + name: "AspNetRoles"); + + migrationBuilder.DropTable( + name: "AspNetUsers"); + } + } +} diff --git a/src/MultiShop/Server/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/src/MultiShop/Server/Data/Migrations/ApplicationDbContextModelSnapshot.cs new file mode 100644 index 0000000..8dce4be --- /dev/null +++ b/src/MultiShop/Server/Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -0,0 +1,371 @@ +// +using System; +using MultiShop.Server.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +namespace MultiShop.Server.Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + partial class ApplicationDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "5.0.0-rc.1.20417.2"); + + modelBuilder.Entity("MultiShop.Server.Models.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b => + { + b.Property("UserCode") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("CreationTime") + .HasColumnType("TEXT"); + + b.Property("Data") + .IsRequired() + .HasMaxLength(50000) + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("DeviceCode") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Expiration") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.HasKey("UserCode"); + + b.HasIndex("DeviceCode") + .IsUnique(); + + b.HasIndex("Expiration"); + + b.ToTable("DeviceCodes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b => + { + b.Property("Key") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("ConsumedTime") + .HasColumnType("TEXT"); + + b.Property("CreationTime") + .HasColumnType("TEXT"); + + b.Property("Data") + .IsRequired() + .HasMaxLength(50000) + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Expiration") + .HasColumnType("TEXT"); + + b.Property("SessionId") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SubjectId") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.HasKey("Key"); + + b.HasIndex("Expiration"); + + b.HasIndex("SubjectId", "ClientId", "Type"); + + b.HasIndex("SubjectId", "SessionId", "Type"); + + b.ToTable("PersistedGrants"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(128) + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("MultiShop.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("MultiShop.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("MultiShop.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("MultiShop.Server.Models.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/MultiShop/Server/Models/ApplicationUser.cs b/src/MultiShop/Server/Models/ApplicationUser.cs new file mode 100644 index 0000000..74b623f --- /dev/null +++ b/src/MultiShop/Server/Models/ApplicationUser.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Identity; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace MultiShop.Server.Models +{ + public class ApplicationUser : IdentityUser + { + } +} diff --git a/src/MultiShop/Server/MultiShop.Server.csproj b/src/MultiShop/Server/MultiShop.Server.csproj new file mode 100644 index 0000000..95e76ca --- /dev/null +++ b/src/MultiShop/Server/MultiShop.Server.csproj @@ -0,0 +1,30 @@ + + + + net5.0 + MultiShop.Server-24B2AA0A-FA57-4FB2-B70E-6546BC734726 + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/MultiShop/Server/Pages/Error.cshtml b/src/MultiShop/Server/Pages/Error.cshtml new file mode 100644 index 0000000..f4b663b --- /dev/null +++ b/src/MultiShop/Server/Pages/Error.cshtml @@ -0,0 +1,42 @@ +@page +@model MultiShop.Server.Pages.ErrorModel + + + + + + + + Error + + + + + +
+
+

Error.

+

An error occurred while processing your request.

+ + @if (Model.ShowRequestId) + { +

+ Request ID: @Model.RequestId +

+ } + +

Development Mode

+

+ Swapping to the Development environment displays detailed information about the error that occurred. +

+

+ The Development environment shouldn't be enabled for deployed applications. + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development + and restarting the app. +

+
+
+ + + diff --git a/src/MultiShop/Server/Pages/Error.cshtml.cs b/src/MultiShop/Server/Pages/Error.cshtml.cs new file mode 100644 index 0000000..d1afba4 --- /dev/null +++ b/src/MultiShop/Server/Pages/Error.cshtml.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.Logging; + +namespace MultiShop.Server.Pages +{ + [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] + [IgnoreAntiforgeryToken] + public class ErrorModel : PageModel + { + public string RequestId { get; set; } + + public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + + private readonly ILogger _logger; + + public ErrorModel(ILogger logger) + { + _logger = logger; + } + + public void OnGet() + { + RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; + } + } +} diff --git a/src/MultiShop/Server/Program.cs b/src/MultiShop/Server/Program.cs new file mode 100644 index 0000000..0dd4395 --- /dev/null +++ b/src/MultiShop/Server/Program.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace MultiShop.Server +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +} diff --git a/src/MultiShop/Server/Properties/launchSettings.json b/src/MultiShop/Server/Properties/launchSettings.json new file mode 100644 index 0000000..3e4fd4c --- /dev/null +++ b/src/MultiShop/Server/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:5738", + "sslPort": 44353 + } + }, + "profiles": { + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "MultiShop.Server": { + "commandName": "Project", + "dotnetRunMessages": "true", + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "applicationUrl": "https://localhost:5001;http://localhost:5000", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } + } diff --git a/src/MultiShop/Server/Startup.cs b/src/MultiShop/Server/Startup.cs new file mode 100644 index 0000000..05e0f2d --- /dev/null +++ b/src/MultiShop/Server/Startup.cs @@ -0,0 +1,82 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.HttpsPolicy; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.ResponseCompression; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using System.Linq; +using MultiShop.Server.Data; +using MultiShop.Server.Models; + +namespace MultiShop.Server +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + public void ConfigureServices(IServiceCollection services) + { + services.AddDbContext(options => + options.UseSqlite( + Configuration.GetConnectionString("DefaultConnection"))); + + services.AddDatabaseDeveloperPageExceptionFilter(); + + services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true) + .AddEntityFrameworkStores(); + + services.AddIdentityServer() + .AddApiAuthorization(); + + services.AddAuthentication() + .AddIdentityServerJwt(); + + services.AddControllersWithViews(); + services.AddRazorPages(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + app.UseMigrationsEndPoint(); + app.UseWebAssemblyDebugging(); + } + else + { + app.UseExceptionHandler("/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); + } + + app.UseHttpsRedirection(); + app.UseBlazorFrameworkFiles(); + app.UseStaticFiles(); + + app.UseRouting(); + + app.UseIdentityServer(); + app.UseAuthentication(); + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapRazorPages(); + endpoints.MapControllers(); + endpoints.MapFallbackToFile("index.html"); + }); + } + } +} diff --git a/src/MultiShop/Server/app.db b/src/MultiShop/Server/app.db new file mode 100644 index 0000000000000000000000000000000000000000..3eef2bb381f8f1ea5751b5ef4cbd5e33a2bc93de GIT binary patch literal 139264 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 literal 0 HcmV?d00001 diff --git a/src/MultiShop/wwwroot/modules/SimpleLogger.dll b/src/MultiShop/Server/modules/SimpleLogger.dll similarity index 100% rename from src/MultiShop/wwwroot/modules/SimpleLogger.dll rename to src/MultiShop/Server/modules/SimpleLogger.dll diff --git a/src/MultiShop/Shared/ListingTableView.razor b/src/MultiShop/Shared/ListingTableView.razor deleted file mode 100644 index ea47ad7..0000000 --- a/src/MultiShop/Shared/ListingTableView.razor +++ /dev/null @@ -1,98 +0,0 @@ -@using DataStructures - -
- - - - - - - - - - - - - - - - - - - - - - - - - -
NamePriceShippingPurchasesRatingReviews
-
@product.Listing.Name
- From @product.ShopName - @if (product.Listing.ConvertedPrices) - { - Converted price - } - @foreach (ResultsProfile.Category c in product.Tops) - { - @CategoryTags(c) - } -
- @if (product.Listing.UpperPrice != product.Listing.LowerPrice) - { -
- @product.Listing.LowerPrice to @product.Listing.UpperPrice -
- } - else - { -
- @GetOrNA(product.Listing.LowerPrice) -
- } -
-
- @GetOrNA(product.Listing.Shipping) -
-
-
- @GetOrNA(product.Listing.PurchaseCount) -
-
-
- @(product.Listing.Rating != null ? string.Format("{0:P2}", product.Listing.Rating) : "N/A") -
-
@GetOrNA(product.Listing.ReviewCount) - View -
-
- -@code { - - [Parameter] - public List Products { get; set; } - - private string PriceCellHeight { get => "height: " + "4rem"; } - - private string GetOrNA(object data, string prepend = null, string append = null) - { - return data != null ? (prepend + data.ToString() + append) : "N/A"; - } - - private string CategoryTags(ResultsProfile.Category c) - { - switch (c) - { - case ResultsProfile.Category.RatingPriceRatio: - return "Best rating to price ratio"; - case ResultsProfile.Category.Price: - return "Lowest price"; - case ResultsProfile.Category.Purchases: - return "Most purchases"; - case ResultsProfile.Category.Reviews: - return "Most reviews"; - } - throw new ArgumentException($"{c} does not have an associated string."); - } - -} \ No newline at end of file diff --git a/src/MultiShop/Shared/MainLayout.razor b/src/MultiShop/Shared/MainLayout.razor deleted file mode 100644 index c20b2fd..0000000 --- a/src/MultiShop/Shared/MainLayout.razor +++ /dev/null @@ -1,100 +0,0 @@ -@using ShopFramework -@using SimpleLogger -@using System.Reflection -@using Microsoft.Extensions.Configuration -@inject HttpClient Http -@inject IConfiguration Configuration -@inherits LayoutComponentBase -@implements IDisposable - -
- -
-
- @if (modulesLoaded) - { - - @Body - - } -
-
-
- -@code { - private bool modulesLoaded = false; - - private Dictionary shops = new Dictionary(); - private Dictionary unusedDependencies = new Dictionary(); - - protected override async Task OnInitializedAsync() - { - await DownloadShopModules(); - await base.OnInitializedAsync(); - } - private async Task DownloadShopModules() { - Logger.Log($"Fetching shop modules.", LogLevel.Debug); - string[] shopNames = await Http.GetFromJsonAsync(Configuration["ModulesList"]); - Task[] assemblyDownloadTasks = new Task[shopNames.Length]; - - for (int i = 0; i < shopNames.Length; i++) - { - string shopPath = Configuration["ModulesDir"] + shopNames[i] + ".dll"; - assemblyDownloadTasks[i] = Http.GetByteArrayAsync(shopPath); - Logger.Log($"Downloading \"{shopPath}\".", LogLevel.Debug); - } - - AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyDependencyRequest; - - foreach (Task task in assemblyDownloadTasks) - { - Assembly assembly = AppDomain.CurrentDomain.Load(await task); - bool assigned = false; - foreach (Type type in assembly.GetTypes()) - { - if (typeof(IShop).IsAssignableFrom(type)) { - IShop shop = Activator.CreateInstance(type) as IShop; - if (shop != null) { - shop.Initialize(); - shops.Add(shop.ShopName, shop); - Logger.Log($"Registered and started lifetime of module for \"{shop.ShopName}\".", LogLevel.Debug); - } - assigned = true; - } - } - if (!assigned) { - unusedDependencies.Add(assembly.FullName, assembly); - Logger.Log($"Assembly \"{assembly.FullName}\" did not contain a shop module. Storing it as potential extension.", LogLevel.Debug); - } - } - foreach (string assembly in unusedDependencies.Keys) - { - Logger.Log($"{assembly} was unused.", LogLevel.Warning); - } - unusedDependencies.Clear(); - modulesLoaded = true; - } - - - private Assembly OnAssemblyDependencyRequest(object sender, ResolveEventArgs args) { - Logger.Log($"Assembly {args.RequestingAssembly} is requesting dependency assembly {args.Name}.", LogLevel.Debug); - if (unusedDependencies.ContainsKey(args.Name)) - { - Logger.Log("Dependency found.", LogLevel.Debug); - Assembly dependency = unusedDependencies[args.Name]; - unusedDependencies.Remove(args.Name); - return dependency; - } - Logger.Log($"No dependency under name {args.Name}", LogLevel.Debug); - return null; - } - - - public void Dispose() { - foreach (string name in shops.Keys) - { - shops[name].Dispose(); - Logger.Log($"Ending lifetime of shop module for \"{name}\"."); - } - } -} \ No newline at end of file diff --git a/src/MultiShop/Shared/MultiShop.Shared.csproj b/src/MultiShop/Shared/MultiShop.Shared.csproj new file mode 100644 index 0000000..f267fa1 --- /dev/null +++ b/src/MultiShop/Shared/MultiShop.Shared.csproj @@ -0,0 +1,14 @@ + + + + net5.0 + + + + + + + + + + diff --git a/src/MultiShop/DataStructures/ProductListingInfo.cs b/src/MultiShop/Shared/ProductListingInfo.cs similarity index 93% rename from src/MultiShop/DataStructures/ProductListingInfo.cs rename to src/MultiShop/Shared/ProductListingInfo.cs index 9e8846c..a1225e3 100644 --- a/src/MultiShop/DataStructures/ProductListingInfo.cs +++ b/src/MultiShop/Shared/ProductListingInfo.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; -using MultiShop.ShopFramework; +using MultiShop.Shop.Framework; -namespace MultiShop.DataStructures +namespace MultiShop.Shared { public class ProductListingInfo { diff --git a/src/MultiShop/DataStructures/ResultCategoryExtensions.cs b/src/MultiShop/Shared/ResultCategoryExtensions.cs similarity index 95% rename from src/MultiShop/DataStructures/ResultCategoryExtensions.cs rename to src/MultiShop/Shared/ResultCategoryExtensions.cs index 41e9de2..1ab77fe 100644 --- a/src/MultiShop/DataStructures/ResultCategoryExtensions.cs +++ b/src/MultiShop/Shared/ResultCategoryExtensions.cs @@ -1,8 +1,6 @@ using System; -using System.ComponentModel; -using MultiShop.ShopFramework; -namespace MultiShop.DataStructures +namespace MultiShop.Shared { public static class ResultCategoryExtensions { diff --git a/src/MultiShop/DataStructures/ResultsProfile.cs b/src/MultiShop/Shared/ResultsProfile.cs similarity index 95% rename from src/MultiShop/DataStructures/ResultsProfile.cs rename to src/MultiShop/Shared/ResultsProfile.cs index 6101fb7..a58b292 100644 --- a/src/MultiShop/DataStructures/ResultsProfile.cs +++ b/src/MultiShop/Shared/ResultsProfile.cs @@ -2,7 +2,7 @@ using System; using System.Collections.Generic; using System.Linq; -namespace MultiShop.DataStructures +namespace MultiShop.Shared { public class ResultsProfile { diff --git a/src/MultiShop/DataStructures/SearchProfile.cs b/src/MultiShop/Shared/SearchProfile.cs similarity index 97% rename from src/MultiShop/DataStructures/SearchProfile.cs rename to src/MultiShop/Shared/SearchProfile.cs index e0d2ed4..02f8ab2 100644 --- a/src/MultiShop/DataStructures/SearchProfile.cs +++ b/src/MultiShop/Shared/SearchProfile.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; -using MultiShop.ShopFramework; +using MultiShop.Shop.Framework; -namespace MultiShop.DataStructures +namespace MultiShop.Shared { public class SearchProfile { diff --git a/src/MultiShop/Shared/WeatherForecast.cs b/src/MultiShop/Shared/WeatherForecast.cs new file mode 100644 index 0000000..a531ade --- /dev/null +++ b/src/MultiShop/Shared/WeatherForecast.cs @@ -0,0 +1,15 @@ +using System; + +namespace MultiShop.Shared +{ + public class WeatherForecast + { + public DateTime Date { get; set; } + + public int TemperatureC { get; set; } + + public string Summary { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + } +} diff --git a/src/MultiShop/wwwroot/appsettings.json b/src/MultiShop/wwwroot/appsettings.json deleted file mode 100644 index 3d11293..0000000 --- a/src/MultiShop/wwwroot/appsettings.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "LogLevel" : "Debug", - "ModulesDir" : "modules/", - "ModulesList" : "modules/modules_content.json" -} \ No newline at end of file diff --git a/src/MultiShop/wwwroot/modules/AliExpressShop.dll b/src/MultiShop/wwwroot/modules/AliExpressShop.dll deleted file mode 100644 index 9b583d37d9f374dede89b3c8831f6ef28cd0f114..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26112 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 D#G<;- diff --git a/src/MultiShop/wwwroot/modules/gen_metadata.py b/src/MultiShop/wwwroot/modules/gen_metadata.py deleted file mode 100644 index a7d3edd..0000000 --- a/src/MultiShop/wwwroot/modules/gen_metadata.py +++ /dev/null @@ -1,14 +0,0 @@ -import os -import json - -modulepaths = [] - -for content in os.listdir(os.getcwd()): - components = os.path.splitext(content) - if (os.path.isfile(content) and components[1] == ".dll"): - print("Adding \"{0}\" to list of modules.".format(components[0])) - modulepaths.append(components[0]) - -file = open("modules_content.json", "w") -json.dump(modulepaths, file, sort_keys=True, indent=4) -file.close() \ No newline at end of file diff --git a/src/MultiShop/wwwroot/modules/modules_content.json b/src/MultiShop/wwwroot/modules/modules_content.json deleted file mode 100644 index 911bf06..0000000 --- a/src/MultiShop/wwwroot/modules/modules_content.json +++ /dev/null @@ -1,5 +0,0 @@ -[ - "HtmlAgilityPack", - "AliExpressShop", - "BanggoodShop" -] \ No newline at end of file diff --git a/test/AliExpressShop.Tests/AliExpressShop.Tests.csproj b/test/MultiShop.Shop/AliExpressModule.Tests/MultiShop.Shop.AliExpressModule.Tests.csproj similarity index 80% rename from test/AliExpressShop.Tests/AliExpressShop.Tests.csproj rename to test/MultiShop.Shop/AliExpressModule.Tests/MultiShop.Shop.AliExpressModule.Tests.csproj index cc8856e..9eaa029 100644 --- a/test/AliExpressShop.Tests/AliExpressShop.Tests.csproj +++ b/test/MultiShop.Shop/AliExpressModule.Tests/MultiShop.Shop.AliExpressModule.Tests.csproj @@ -20,8 +20,8 @@ - - + + diff --git a/test/AliExpressShop.Tests/ShopTest.cs b/test/MultiShop.Shop/AliExpressModule.Tests/ShopTest.cs similarity index 95% rename from test/AliExpressShop.Tests/ShopTest.cs rename to test/MultiShop.Shop/AliExpressModule.Tests/ShopTest.cs index 0214025..aa43c06 100644 --- a/test/AliExpressShop.Tests/ShopTest.cs +++ b/test/MultiShop.Shop/AliExpressModule.Tests/ShopTest.cs @@ -1,9 +1,9 @@ -using MultiShop.ShopFramework; +using MultiShop.Shop.Framework; using SimpleLogger; using Xunit; using Xunit.Abstractions; -namespace AliExpressShop.Tests +namespace MultiShop.Shop.AliExpressModule.Tests { public class ShopTest { diff --git a/test/AliExpressShop.Tests/XUnitLogger.cs b/test/MultiShop.Shop/AliExpressModule.Tests/XUnitLogger.cs similarity index 94% rename from test/AliExpressShop.Tests/XUnitLogger.cs rename to test/MultiShop.Shop/AliExpressModule.Tests/XUnitLogger.cs index 7fe2715..1512745 100644 --- a/test/AliExpressShop.Tests/XUnitLogger.cs +++ b/test/MultiShop.Shop/AliExpressModule.Tests/XUnitLogger.cs @@ -2,7 +2,7 @@ using System; using SimpleLogger; using Xunit.Abstractions; -namespace AliExpressShop +namespace MultiShop.Shop.AliExpressModule { public class XUnitLogger : ILogReceiver { diff --git a/test/BanggoodShop.Tests/BanggoodShop.Tests.csproj b/test/MultiShop.Shop/BanggoodModule.Tests/MultiShop.Shop.BanggoodModule.Tests.csproj similarity index 81% rename from test/BanggoodShop.Tests/BanggoodShop.Tests.csproj rename to test/MultiShop.Shop/BanggoodModule.Tests/MultiShop.Shop.BanggoodModule.Tests.csproj index a421dbc..187aed3 100644 --- a/test/BanggoodShop.Tests/BanggoodShop.Tests.csproj +++ b/test/MultiShop.Shop/BanggoodModule.Tests/MultiShop.Shop.BanggoodModule.Tests.csproj @@ -20,8 +20,8 @@ - - + + diff --git a/test/BanggoodShop.Tests/ShopTest.cs b/test/MultiShop.Shop/BanggoodModule.Tests/ShopTest.cs similarity index 91% rename from test/BanggoodShop.Tests/ShopTest.cs rename to test/MultiShop.Shop/BanggoodModule.Tests/ShopTest.cs index 9fe8f81..708d56e 100644 --- a/test/BanggoodShop.Tests/ShopTest.cs +++ b/test/MultiShop.Shop/BanggoodModule.Tests/ShopTest.cs @@ -1,9 +1,9 @@ -using MultiShop.ShopFramework; +using MultiShop.Shop.Framework; using SimpleLogger; using Xunit; using Xunit.Abstractions; -namespace BanggoodShop.Tests +namespace MultiShop.Shop.BanggoodModule.Tests { public class ShopTest { @@ -12,7 +12,6 @@ namespace BanggoodShop.Tests Logger.AddLogListener(new XUnitLogger(output)); } - [Fact] public async void Search_CAD_ResultsFound() { diff --git a/test/BanggoodShop.Tests/XUnitLogger.cs b/test/MultiShop.Shop/BanggoodModule.Tests/XUnitLogger.cs similarity index 94% rename from test/BanggoodShop.Tests/XUnitLogger.cs rename to test/MultiShop.Shop/BanggoodModule.Tests/XUnitLogger.cs index 7e76ffd..94d4c04 100644 --- a/test/BanggoodShop.Tests/XUnitLogger.cs +++ b/test/MultiShop.Shop/BanggoodModule.Tests/XUnitLogger.cs @@ -2,7 +2,7 @@ using System; using SimpleLogger; using Xunit.Abstractions; -namespace BanggoodShop +namespace MultiShop.Shop.BanggoodModule { public class XUnitLogger : ILogReceiver { From d5c89fa6cacf686815d9947bcabdf1386bd1c297 Mon Sep 17 00:00:00 2001 From: Harrison Deng Date: Fri, 21 May 2021 22:53:56 -0500 Subject: [PATCH 013/167] Added Task.Yield call to category top tagging portion. --- src/MultiShop/Client/Pages/Search.razor.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/MultiShop/Client/Pages/Search.razor.cs b/src/MultiShop/Client/Pages/Search.razor.cs index 4273ef5..17512df 100644 --- a/src/MultiShop/Client/Pages/Search.razor.cs +++ b/src/MultiShop/Client/Pages/Search.razor.cs @@ -131,11 +131,14 @@ namespace MultiShop.Client.Pages searching = false; searched = true; + int tagsAdded = 0; foreach (ResultsProfile.Category c in greatest.Keys) { foreach (ProductListingInfo info in greatest[c]) { info.Tops.Add(c); + tagsAdded += 1; + if (tagsAdded % 50 == 0) await Task.Yield(); } } @@ -147,7 +150,7 @@ namespace MultiShop.Client.Pages if (searching) return; organizing = true; StateHasChanged(); - + List sortedResults = await Task.Run>(() => { List sorted = new List(listings); From d1ea0c733741d7479aa048d49df5dce7252a2cb4 Mon Sep 17 00:00:00 2001 From: Harrison Deng Date: Sat, 22 May 2021 00:20:02 -0500 Subject: [PATCH 014/167] Removed a logging call. --- src/MultiShop/Client/Shared/DragAndDropList.razor | 1 - 1 file changed, 1 deletion(-) diff --git a/src/MultiShop/Client/Shared/DragAndDropList.razor b/src/MultiShop/Client/Shared/DragAndDropList.razor index 94b4456..4ed4689 100644 --- a/src/MultiShop/Client/Shared/DragAndDropList.razor +++ b/src/MultiShop/Client/Shared/DragAndDropList.razor @@ -77,7 +77,6 @@ private async Task MoveOrder(int from, int to) { - Logger.Log($"Attempting to move from {from} to {to}.", LogLevel.Debug); if (from == to || from >= Items.Count || from < 0 || to > Items.Count || to < 0) return; TItem item = Items[from]; Items.RemoveAt(from); From d2084efa7dc898de1e8a008645c2f446651d15fc Mon Sep 17 00:00:00 2001 From: Harrison Deng Date: Sat, 22 May 2021 00:22:44 -0500 Subject: [PATCH 015/167] Implemented quick sort with yielding. --- src/MultiShop/Client/Pages/Search.razor.cs | 60 +++++++++++++++++----- 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/src/MultiShop/Client/Pages/Search.razor.cs b/src/MultiShop/Client/Pages/Search.razor.cs index 17512df..ce941fd 100644 --- a/src/MultiShop/Client/Pages/Search.razor.cs +++ b/src/MultiShop/Client/Pages/Search.razor.cs @@ -147,14 +147,9 @@ namespace MultiShop.Client.Pages private async Task Organize(List order) { - if (searching) return; + if (searching || listings.Count <= 1) return; organizing = true; - StateHasChanged(); - - List sortedResults = await Task.Run>(() => - { - List sorted = new List(listings); - sorted.Sort((a, b) => + Comparison comparer = (a, b) => { foreach (ResultsProfile.Category category in activeResultsProfile.Order) { @@ -165,11 +160,52 @@ namespace MultiShop.Client.Pages } } return 0; - }); - return sorted; - }); - listings.Clear(); - listings.AddRange(sortedResults); + }; + + Func<(int, int), Task> partition = async (ilh) => { + ProductListingInfo swapTemp; + ProductListingInfo pivot = listings[ilh.Item2]; + int lastSwap = ilh.Item1 - 1; + for (int j = ilh.Item1; j <= ilh.Item2 - 1; j++) + { + if (comparer.Invoke(listings[j], pivot) <= 0) { + lastSwap += 1; + swapTemp = listings[lastSwap]; + listings[lastSwap] = listings[j]; + listings[j] = swapTemp; + } + await Task.Yield(); + } + swapTemp = listings[lastSwap+1]; + listings[lastSwap+1] = listings[ilh.Item2]; + listings[ilh.Item2] = swapTemp; + return lastSwap + 1; + }; + + Func<(int, int), Task> quickSort = async (ilh) => { + Stack<(int, int)> iterativeStack = new Stack<(int, int)>(); + iterativeStack.Push(ilh); + + while (iterativeStack.Count > 0) + { + (int, int) lh = iterativeStack.Pop(); + int p = await partition.Invoke((lh.Item1, lh.Item2)); + + if (p - 1 > lh.Item1) { + iterativeStack.Push((lh.Item1, p - 1)); + } + + if (p + 1 < lh.Item2) { + iterativeStack.Push((p + 1, lh.Item2)); + } + + await Task.Yield(); + } + }; + StateHasChanged(); + + await quickSort((0, listings.Count - 1)); + organizing = false; StateHasChanged(); } From d87025c8b44557cf23fca01b6c514cb2bc6b84d1 Mon Sep 17 00:00:00 2001 From: Harrison Deng Date: Sat, 22 May 2021 01:01:26 -0500 Subject: [PATCH 016/167] Removed ComponentSupport.js Unecessary for now. --- .../Client/Shared/DragAndDropList.razor | 14 ++--------- src/MultiShop/Client/wwwroot/index.html | 1 - .../Client/wwwroot/js/ComponentsSupport.js | 23 ------------------- 3 files changed, 2 insertions(+), 36 deletions(-) delete mode 100644 src/MultiShop/Client/wwwroot/js/ComponentsSupport.js diff --git a/src/MultiShop/Client/Shared/DragAndDropList.razor b/src/MultiShop/Client/Shared/DragAndDropList.razor index 4ed4689..74ead76 100644 --- a/src/MultiShop/Client/Shared/DragAndDropList.razor +++ b/src/MultiShop/Client/Shared/DragAndDropList.razor @@ -2,10 +2,10 @@ @typeparam TItem @inject IJSRuntime JS -
- +
@@ -153,17 +150,17 @@
- + Max shipping
- +
.00
- +
@@ -175,9 +172,9 @@
- +
- @if (showResultsConfiguration) + @if (status.ResultsConfiguring) {
@@ -197,7 +194,7 @@
- @if (searching) + @if (status.Searching) { @if (listings.Count != 0) { @@ -216,7 +213,7 @@ } else if (listings.Count != 0) { - @if (organizing) + @if (status.Organizing) {
Loading... @@ -228,7 +225,7 @@ Looked through @resultsChecked listings and found @listings.Count viable results. } } - else if (searched) + else if (status.Searched) { We've found @resultsChecked listings and unfortunately none matched your search. } @@ -241,74 +238,7 @@
@if (listings.Count > 0) { -
- - - - - - - - - - - - - @if (!showSearchConfiguration && !searching) { - - - - - - - - - - - - - - } -
NamePriceShippingPurchasesRatingReviews
-
@product.Listing.Name
- From @product.ShopName - @if (product.Listing.ConvertedPrices) - { - Converted price - } - @foreach (ResultsProfile.Category c in product.Tops) - { - @CategoryTags(c) - } -
- @if (product.Listing.UpperPrice != product.Listing.LowerPrice) - { -
- @product.Listing.LowerPrice to @product.Listing.UpperPrice -
- } - else - { -
- @GetOrNA(product.Listing.LowerPrice) -
- } -
-
- @GetOrNA(product.Listing.Shipping) -
-
-
- @GetOrNA(product.Listing.PurchaseCount) -
-
-
- @(product.Listing.Rating != null ? string.Format("{0:P2}", product.Listing.Rating) : "N/A") -
-
@GetOrNA(product.Listing.ReviewCount) - View -
-
+ @listingViews[CurrentView] }
diff --git a/src/MultiShop/Client/Pages/Search.razor.cs b/src/MultiShop/Client/Pages/Search.razor.cs index ce941fd..1674874 100644 --- a/src/MultiShop/Client/Pages/Search.razor.cs +++ b/src/MultiShop/Client/Pages/Search.razor.cs @@ -1,71 +1,95 @@ using System; using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Json; +using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Components; -using MultiShop.Shared; +using Microsoft.AspNetCore.Components.Authorization; +using MultiShop.Client.Extensions.Models; +using MultiShop.Client.Listing; +using MultiShop.Shared.Models; using MultiShop.Shop.Framework; using SimpleLogger; namespace MultiShop.Client.Pages { - public partial class Search + public partial class Search : IAsyncDisposable { + [CascadingParameter] + Task AuthenticationStateTask { get; set; } + [CascadingParameter(Name = "Shops")] public Dictionary Shops { get; set; } [Parameter] public string Query { get; set; } - private SearchProfile activeProfile = new SearchProfile(); - private ResultsProfile activeResultsProfile = new ResultsProfile(); + [Inject] + private HttpClient Http { get; set; } - private bool showSearchConfiguration = false; - private bool showResultsConfiguration = false; + private Status status = new Status(); - private string ToggleSearchConfigButtonCss - { - get => "btn btn-outline-secondary" + (showSearchConfiguration ? " active" : ""); - } + private Dictionary listingViews; - private string ToggleResultsConfigurationcss { - get => "btn btn-outline-secondary btn-tab" + (showResultsConfiguration ? " active" : ""); - } + private Views CurrentView = Views.Table; - private bool searched = false; - private bool searching = false; - private bool organizing = false; + private SearchProfile activeSearchProfile; + private ResultsProfile activeResultsProfile; - private int resultsChecked = 0; private List listings = new List(); - - protected override void OnInitialized() - { - foreach (string shop in Shops.Keys) - { - activeProfile.shopStates[shop] = true; - } - base.OnInitialized(); - } + private int resultsChecked = 0; protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); + + AuthenticationState authState = await AuthenticationStateTask; + + listingViews = new Dictionary() { + {Views.Table, new TableView(status)} + }; + + if (authState.User.Identity.IsAuthenticated) { + Logger.Log($"User \"{authState.User.Identity.Name}\" is authenticated. Checking for saved profiles.", LogLevel.Debug); + HttpResponseMessage searchProfileResponse = await Http.GetAsync("Profile/Search"); + if (searchProfileResponse.IsSuccessStatusCode) { + activeSearchProfile = await searchProfileResponse.Content.ReadFromJsonAsync(); + Logger.Log("Received: " + await searchProfileResponse.Content.ReadAsStringAsync()); + Logger.Log("Serialized then deserialized: " + JsonSerializer.Serialize(activeSearchProfile)); + } else { + Logger.Log("Could not load search profile from server. Using default.", LogLevel.Warning); + activeSearchProfile = new SearchProfile(); + } + + HttpResponseMessage resultsProfileResponse = await Http.GetAsync("Profile/Results"); + if (resultsProfileResponse.IsSuccessStatusCode) { + activeResultsProfile = await resultsProfileResponse.Content.ReadFromJsonAsync(); + } else { + Logger.Log("Could not load results profile from server.", LogLevel.Debug); + activeResultsProfile = new ResultsProfile(); + } + } else { + activeSearchProfile = new SearchProfile(); + activeResultsProfile = new ResultsProfile(); + } + activeSearchProfile.ShopStates.TotalShops = Shops.Count; } protected override async Task OnParametersSetAsync() { + await base.OnParametersSetAsync(); if (!string.IsNullOrEmpty(Query)) { await PerformSearch(Query); } - await base.OnParametersSetAsync(); } private async Task PerformSearch(string query) { if (string.IsNullOrWhiteSpace(query)) return; - if (searching) return; - searching = true; + if (status.Searching) return; + status.Searching = true; Logger.Log($"Received search request for \"{query}\".", LogLevel.Debug); resultsChecked = 0; listings.Clear(); @@ -73,10 +97,10 @@ namespace MultiShop.Client.Pages List>(); foreach (string shopName in Shops.Keys) { - if (activeProfile.shopStates[shopName]) + if (activeSearchProfile.ShopStates[shopName]) { Logger.Log($"Querying \"{shopName}\" for products."); - Shops[shopName].SetupSession(query, activeProfile.currency); + Shops[shopName].SetupSession(query, activeSearchProfile.Currency); int shopViableResults = 0; await foreach (ProductListing listing in Shops[shopName]) { @@ -88,12 +112,12 @@ namespace MultiShop.Client.Pages } - if (listing.Shipping == null && !activeProfile.keepUnknownShipping || (activeProfile.enableMaxShippingFee && listing.Shipping > activeProfile.MaxShippingFee)) continue; + if (listing.Shipping == null && !activeSearchProfile.KeepUnknownShipping || (activeSearchProfile.EnableMaxShippingFee && listing.Shipping > activeSearchProfile.MaxShippingFee)) continue; float shippingDifference = listing.Shipping != null ? listing.Shipping.Value : 0; - if (!(listing.LowerPrice + shippingDifference >= activeProfile.lowerPrice && (!activeProfile.enableUpperPrice || listing.UpperPrice + shippingDifference <= activeProfile.UpperPrice))) continue; - if ((listing.Rating == null && !activeProfile.keepUnrated) && activeProfile.minRating > (listing.Rating == null ? 0 : listing.Rating)) continue; - if ((listing.PurchaseCount == null && !activeProfile.keepUnknownPurchaseCount) || activeProfile.minPurchases > (listing.PurchaseCount == null ? 0 : listing.PurchaseCount)) continue; - if ((listing.ReviewCount == null && !activeProfile.keepUnknownRatingCount) || activeProfile.minReviews > (listing.ReviewCount == null ? 0 : listing.ReviewCount)) continue; + if (!(listing.LowerPrice + shippingDifference >= activeSearchProfile.LowerPrice && (!activeSearchProfile.EnableUpperPrice || listing.UpperPrice + shippingDifference <= activeSearchProfile.UpperPrice))) continue; + if ((listing.Rating == null && !activeSearchProfile.KeepUnrated) && activeSearchProfile.MinRating > (listing.Rating == null ? 0 : listing.Rating)) continue; + if ((listing.PurchaseCount == null && !activeSearchProfile.KeepUnknownPurchaseCount) || activeSearchProfile.MinPurchases > (listing.PurchaseCount == null ? 0 : listing.PurchaseCount)) continue; + if ((listing.ReviewCount == null && !activeSearchProfile.KeepUnknownRatingCount) || activeSearchProfile.MinReviews > (listing.ReviewCount == null ? 0 : listing.ReviewCount)) continue; ProductListingInfo info = new ProductListingInfo(listing, shopName); listings.Add(info); @@ -119,7 +143,7 @@ namespace MultiShop.Client.Pages } shopViableResults += 1; - if (shopViableResults >= activeProfile.maxResults) break; + if (shopViableResults >= activeSearchProfile.MaxResults) break; } Logger.Log($"\"{shopName}\" has completed. There are {listings.Count} results in total.", LogLevel.Debug); } @@ -128,8 +152,8 @@ namespace MultiShop.Client.Pages Logger.Log($"Skipping {shopName} since it's disabled."); } } - searching = false; - searched = true; + status.Searching = false; + status.Searched = true; int tagsAdded = 0; foreach (ResultsProfile.Category c in greatest.Keys) @@ -145,10 +169,10 @@ namespace MultiShop.Client.Pages await Organize(activeResultsProfile.Order); } - private async Task Organize(List order) + private async Task Organize(IList order) { - if (searching || listings.Count <= 1) return; - organizing = true; + if (status.Searching || listings.Count <= 1) return; + status.Organizing = true; Comparison comparer = (a, b) => { foreach (ResultsProfile.Category category in activeResultsProfile.Order) @@ -162,13 +186,15 @@ namespace MultiShop.Client.Pages return 0; }; - Func<(int, int), Task> partition = async (ilh) => { + Func<(int, int), Task> partition = async (ilh) => + { ProductListingInfo swapTemp; ProductListingInfo pivot = listings[ilh.Item2]; int lastSwap = ilh.Item1 - 1; for (int j = ilh.Item1; j <= ilh.Item2 - 1; j++) { - if (comparer.Invoke(listings[j], pivot) <= 0) { + if (comparer.Invoke(listings[j], pivot) <= 0) + { lastSwap += 1; swapTemp = listings[lastSwap]; listings[lastSwap] = listings[j]; @@ -176,26 +202,29 @@ namespace MultiShop.Client.Pages } await Task.Yield(); } - swapTemp = listings[lastSwap+1]; - listings[lastSwap+1] = listings[ilh.Item2]; + swapTemp = listings[lastSwap + 1]; + listings[lastSwap + 1] = listings[ilh.Item2]; listings[ilh.Item2] = swapTemp; return lastSwap + 1; }; - Func<(int, int), Task> quickSort = async (ilh) => { + Func<(int, int), Task> quickSort = async (ilh) => + { Stack<(int, int)> iterativeStack = new Stack<(int, int)>(); iterativeStack.Push(ilh); - + while (iterativeStack.Count > 0) { (int, int) lh = iterativeStack.Pop(); int p = await partition.Invoke((lh.Item1, lh.Item2)); - if (p - 1 > lh.Item1) { + if (p - 1 > lh.Item1) + { iterativeStack.Push((lh.Item1, p - 1)); } - if (p + 1 < lh.Item2) { + if (p + 1 < lh.Item2) + { iterativeStack.Push((p + 1, lh.Item2)); } @@ -206,30 +235,26 @@ namespace MultiShop.Client.Pages await quickSort((0, listings.Count - 1)); - organizing = false; + status.Organizing = false; StateHasChanged(); } - - private string GetOrNA(object data, string prepend = null, string append = null) + public async ValueTask DisposeAsync() { - return data != null ? (prepend + data.ToString() + append) : "N/A"; + AuthenticationState authState = await AuthenticationStateTask; + if (authState.User.Identity.IsAuthenticated) { + await Http.PutAsJsonAsync("Profile/Search", activeSearchProfile); + await Http.PutAsJsonAsync("Profile/Results", activeResultsProfile); + } } - private string CategoryTags(ResultsProfile.Category c) + public class Status { - switch (c) - { - case ResultsProfile.Category.RatingPriceRatio: - return "Best rating to price ratio"; - case ResultsProfile.Category.Price: - return "Lowest price"; - case ResultsProfile.Category.Purchases: - return "Most purchases"; - case ResultsProfile.Category.Reviews: - return "Most reviews"; - } - throw new ArgumentException($"{c} does not have an associated string."); + public bool SearchConfiguring { get; set; } + public bool ResultsConfiguring { get; set; } + public bool Organizing { get; set; } + public bool Searching { get; set; } + public bool Searched { get; set; } } } } \ No newline at end of file diff --git a/src/MultiShop/Client/Pages/Search.razor.css b/src/MultiShop/Client/Pages/Search.razor.css index 39685a7..8db9f50 100644 --- a/src/MultiShop/Client/Pages/Search.razor.css +++ b/src/MultiShop/Client/Pages/Search.razor.css @@ -4,10 +4,4 @@ tbody > tr > th > div { .table.table thead th { border-top-style: none; -} - -.btn.btn-tab { - border-bottom-style: none; - border-bottom-left-radius: 0em; - border-bottom-right-radius: 0em; } \ No newline at end of file diff --git a/src/MultiShop/Client/Program.cs b/src/MultiShop/Client/Program.cs index d7b3c3c..b579d30 100644 --- a/src/MultiShop/Client/Program.cs +++ b/src/MultiShop/Client/Program.cs @@ -22,7 +22,6 @@ namespace MultiShop.Client builder.Services.AddHttpClient("MultiShop.ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)).AddHttpMessageHandler(); - // Supply HttpClient instances that include access tokens when making requests to the server project builder.Services.AddScoped(sp => sp.GetRequiredService().CreateClient("MultiShop.ServerAPI")); Action configureClient = client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress); diff --git a/src/MultiShop/Client/Shared/DragAndDropList.razor b/src/MultiShop/Client/Shared/DragAndDropList.razor index 73a6d73..59a776f 100644 --- a/src/MultiShop/Client/Shared/DragAndDropList.razor +++ b/src/MultiShop/Client/Shared/DragAndDropList.razor @@ -22,7 +22,7 @@ @code { [Parameter] - public List Items { get; set; } + public IList Items { get; set; } [Parameter] public string AdditionalListClasses { get; set; } diff --git a/src/MultiShop/Client/Shared/NavMenu.razor b/src/MultiShop/Client/Shared/NavMenu.razor index 28c1761..6e3514c 100644 --- a/src/MultiShop/Client/Shared/NavMenu.razor +++ b/src/MultiShop/Client/Shared/NavMenu.razor @@ -36,7 +36,7 @@ Hello, @auth.User.Identity.Name! diff --git a/src/MultiShop/Client/Shared/ToggleableButton.razor b/src/MultiShop/Client/Shared/ToggleableButton.razor new file mode 100644 index 0000000..89d332a --- /dev/null +++ b/src/MultiShop/Client/Shared/ToggleableButton.razor @@ -0,0 +1,28 @@ + + +@code { + [Parameter(CaptureUnmatchedValues = true)] + public IReadOnlyDictionary AdditionalAttributes { get; set; } + + [Parameter] + public RenderFragment ChildContent { get; set; } + + [Parameter] + public EventCallback OnToggleCallback { get; set; } + + private async Task OnClick() { + state = !state; + await OnToggleCallback.InvokeAsync(state); + } + + private bool state; + + private string ButtonClasses + { + get + { + IReadOnlyDictionary t = AdditionalAttributes; + return (state ? "active " : "") + (AdditionalAttributes["class"] as string); + } + } +} \ No newline at end of file diff --git a/src/MultiShop/Client/wwwroot/css/app.css b/src/MultiShop/Client/wwwroot/css/app.css index 2bbae17..3c5a10e 100644 --- a/src/MultiShop/Client/wwwroot/css/app.css +++ b/src/MultiShop/Client/wwwroot/css/app.css @@ -26,3 +26,9 @@ html, body { right: 0.75rem; top: 0.5rem; } + +.btn.btn-tab { + border-bottom-style: none; + border-bottom-left-radius: 0em; + border-bottom-right-radius: 0em; +} \ No newline at end of file diff --git a/src/MultiShop/Server/Controllers/ProfileController.cs b/src/MultiShop/Server/Controllers/ProfileController.cs new file mode 100644 index 0000000..58b2916 --- /dev/null +++ b/src/MultiShop/Server/Controllers/ProfileController.cs @@ -0,0 +1,63 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Identity; +using Microsoft.AspNetCore.Mvc; +using MultiShop.Server.Data; +using MultiShop.Server.Models; +using MultiShop.Shared.Models; + +namespace MultiShop.Server.Controllers +{ + + [ApiController] + [Authorize] + [Route("[controller]")] + public class ProfileController : ControllerBase + { + private UserManager userManager; + private ApplicationDbContext dbContext; + public ProfileController(UserManager userManager, ApplicationDbContext dbContext) + { + this.userManager = userManager; + this.dbContext = dbContext; + } + + [HttpGet] + [Route("Search")] + public async Task GetSearchProfile() { + ApplicationUser userModel = await userManager.GetUserAsync(User); + return Ok(userModel.SearchProfile); + } + + [HttpGet] + [Route("Results")] + public async Task GetResultsProfile() { + ApplicationUser userModel = await userManager.GetUserAsync(User); + return Ok(userModel.ResultsProfile); + } + + [HttpPut] + [Route("Search")] + public async Task PutSearchProfile(SearchProfile searchProfile) { + ApplicationUser userModel = await userManager.GetUserAsync(User); + if (userModel.SearchProfile.Id != searchProfile.Id || userModel.Id != searchProfile.ApplicationUserId) { + return BadRequest(); + } + dbContext.Entry(userModel.SearchProfile).CurrentValues.SetValues(searchProfile); + await userManager.UpdateAsync(userModel); + return NoContent(); + } + + [HttpPut] + [Route("Results")] + public async Task PutResultsProfile(ResultsProfile resultsProfile) { + ApplicationUser userModel = await userManager.GetUserAsync(User); + if (userModel.ResultsProfile.Id != resultsProfile.Id) { + return BadRequest(); + } + dbContext.Entry(userModel.ResultsProfile).CurrentValues.SetValues(resultsProfile); + await userManager.UpdateAsync(userModel); + return NoContent(); + } + } +} \ No newline at end of file diff --git a/src/MultiShop/Server/Controllers/PublicApiSettingsController.cs b/src/MultiShop/Server/Controllers/PublicApiSettingsController.cs index 602ff2e..f695b29 100644 --- a/src/MultiShop/Server/Controllers/PublicApiSettingsController.cs +++ b/src/MultiShop/Server/Controllers/PublicApiSettingsController.cs @@ -16,10 +16,10 @@ namespace MultiShop.Server.Controllers } [HttpGet] - public IReadOnlyDictionary GetPublicConfiguration() { - return new Dictionary { + public IActionResult GetPublicConfiguration() { + return Ok(new Dictionary { {"IdentityServer:Registration", configuration["IdentityServer:Registration"]} - }; + }); } } } \ No newline at end of file diff --git a/src/MultiShop/Server/Controllers/ShopModulesController.cs b/src/MultiShop/Server/Controllers/ShopModulesController.cs index 250d309..30976c0 100644 --- a/src/MultiShop/Server/Controllers/ShopModulesController.cs +++ b/src/MultiShop/Server/Controllers/ShopModulesController.cs @@ -22,20 +22,22 @@ namespace MultiShop.Server.Controllers this.shopAssemblyData = new Dictionary(); } - public IEnumerable GetShopModuleNames() { + public IActionResult GetShopModuleNames() { + List moduleNames = new List(); ShopOptions options = configuration.GetSection(ShopOptions.Shop).Get(); foreach (string file in Directory.EnumerateFiles(options.Directory)) { if (Path.GetExtension(file).ToLower().Equals(".dll") && !(options.Disabled != null && options.Disabled.Contains(Path.GetFileNameWithoutExtension(file)))) { - yield return Path.GetFileNameWithoutExtension(file); + moduleNames.Add(Path.GetFileNameWithoutExtension(file)); } } + return Ok(moduleNames); } [HttpGet] [Route("{shopModuleName}")] - public ActionResult GetModule(string shopModuleName) { + public IActionResult GetModule(string shopModuleName) { ShopOptions options = configuration.GetSection(ShopOptions.Shop).Get(); string shopPath = Path.Join(options.Directory, shopModuleName); shopPath += ".dll"; diff --git a/src/MultiShop/Server/Data/ApplicationDbContext.cs b/src/MultiShop/Server/Data/ApplicationDbContext.cs index 70ff868..5afb0c5 100644 --- a/src/MultiShop/Server/Data/ApplicationDbContext.cs +++ b/src/MultiShop/Server/Data/ApplicationDbContext.cs @@ -7,15 +7,44 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using MultiShop.Shared.Models; +using System.Text.Json; +using Microsoft.EntityFrameworkCore.ChangeTracking; namespace MultiShop.Server.Data { public class ApplicationDbContext : ApiAuthorizationDbContext { - public ApplicationDbContext( - DbContextOptions options, - IOptions operationalStoreOptions) : base(options, operationalStoreOptions) + public ApplicationDbContext(DbContextOptions options, IOptions operationalStoreOptions) : base(options, operationalStoreOptions) { } + + protected override void OnModelCreating(ModelBuilder modelBuilder) { + base.OnModelCreating(modelBuilder); + + modelBuilder.Entity() + .Property(e => e.Order) + .HasConversion( + v => JsonSerializer.Serialize(v, null), + v => JsonSerializer.Deserialize>(v, null), + new ValueComparer>( + (a, b) => a.SequenceEqual(b), + c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())), + c => (IList) c.ToList() + ) + ); + + modelBuilder.Entity() + .Property(e => e.ShopStates) + .HasConversion( + v => JsonSerializer.Serialize(v, null), + v => JsonSerializer.Deserialize(v, null), + new ValueComparer( + (a, b) => a.Equals(b), + c => c.GetHashCode(), + c => c.Clone() + ) + ); + } } } diff --git a/src/MultiShop/Server/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs b/src/MultiShop/Server/Data/Migrations/20210525224658_InitialCreate.Designer.cs similarity index 76% rename from src/MultiShop/Server/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs rename to src/MultiShop/Server/Data/Migrations/20210525224658_InitialCreate.Designer.cs index 8d883dc..a9b3f2c 100644 --- a/src/MultiShop/Server/Data/Migrations/00000000000000_CreateIdentitySchema.Designer.cs +++ b/src/MultiShop/Server/Data/Migrations/20210525224658_InitialCreate.Designer.cs @@ -1,86 +1,22 @@ // using System; -using MultiShop.Server.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using MultiShop.Server.Data; namespace MultiShop.Server.Data.Migrations { [DbContext(typeof(ApplicationDbContext))] - [Migration("00000000000000_CreateIdentitySchema")] - partial class CreateIdentitySchema + [Migration("20210525224658_InitialCreate")] + partial class InitialCreate { protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "5.0.0-rc.1.20417.2"); - - modelBuilder.Entity("MultiShop.Server.Models.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccessFailedCount") - .HasColumnType("INTEGER"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("TEXT"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("EmailConfirmed") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnabled") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnd") - .HasColumnType("TEXT"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("PasswordHash") - .HasColumnType("TEXT"); - - b.Property("PhoneNumber") - .HasColumnType("TEXT"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("INTEGER"); - - b.Property("SecurityStamp") - .HasColumnType("TEXT"); - - b.Property("TwoFactorEnabled") - .HasColumnType("INTEGER"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.ToTable("AspNetUsers"); - }); + .HasAnnotation("ProductVersion", "5.0.6"); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b => { @@ -317,6 +253,154 @@ namespace MultiShop.Server.Data.Migrations b.ToTable("AspNetUserTokens"); }); + modelBuilder.Entity("MultiShop.Server.Models.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("MultiShop.Shared.Models.ResultsProfile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApplicationUserId") + .HasColumnType("TEXT"); + + b.Property("Order") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId") + .IsUnique(); + + b.ToTable("ResultsProfile"); + }); + + modelBuilder.Entity("MultiShop.Shared.Models.SearchProfile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApplicationUserId") + .HasColumnType("TEXT"); + + b.Property("Currency") + .HasColumnType("INTEGER"); + + b.Property("EnableMaxShippingFee") + .HasColumnType("INTEGER"); + + b.Property("EnableUpperPrice") + .HasColumnType("INTEGER"); + + b.Property("KeepUnknownPurchaseCount") + .HasColumnType("INTEGER"); + + b.Property("KeepUnknownRatingCount") + .HasColumnType("INTEGER"); + + b.Property("KeepUnknownShipping") + .HasColumnType("INTEGER"); + + b.Property("KeepUnrated") + .HasColumnType("INTEGER"); + + b.Property("LowerPrice") + .HasColumnType("INTEGER"); + + b.Property("MaxResults") + .HasColumnType("INTEGER"); + + b.Property("MaxShippingFee") + .HasColumnType("INTEGER"); + + b.Property("MinPurchases") + .HasColumnType("INTEGER"); + + b.Property("MinRating") + .HasColumnType("REAL"); + + b.Property("MinReviews") + .HasColumnType("INTEGER"); + + b.Property("ShopStates") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpperPrice") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId") + .IsUnique(); + + b.ToTable("SearchProfile"); + }); + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => { b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) @@ -367,6 +451,29 @@ namespace MultiShop.Server.Data.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); + + modelBuilder.Entity("MultiShop.Shared.Models.ResultsProfile", b => + { + b.HasOne("MultiShop.Server.Models.ApplicationUser", null) + .WithOne("ResultsProfile") + .HasForeignKey("MultiShop.Shared.Models.ResultsProfile", "ApplicationUserId"); + }); + + modelBuilder.Entity("MultiShop.Shared.Models.SearchProfile", b => + { + b.HasOne("MultiShop.Server.Models.ApplicationUser", null) + .WithOne("SearchProfile") + .HasForeignKey("MultiShop.Shared.Models.SearchProfile", "ApplicationUserId"); + }); + + modelBuilder.Entity("MultiShop.Server.Models.ApplicationUser", b => + { + b.Navigation("ResultsProfile") + .IsRequired(); + + b.Navigation("SearchProfile") + .IsRequired(); + }); #pragma warning restore 612, 618 } } diff --git a/src/MultiShop/Server/Data/Migrations/00000000000000_CreateIdentitySchema.cs b/src/MultiShop/Server/Data/Migrations/20210525224658_InitialCreate.cs similarity index 77% rename from src/MultiShop/Server/Data/Migrations/00000000000000_CreateIdentitySchema.cs rename to src/MultiShop/Server/Data/Migrations/20210525224658_InitialCreate.cs index 0e07926..803a8dc 100644 --- a/src/MultiShop/Server/Data/Migrations/00000000000000_CreateIdentitySchema.cs +++ b/src/MultiShop/Server/Data/Migrations/20210525224658_InitialCreate.cs @@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Migrations; namespace MultiShop.Server.Data.Migrations { - public partial class CreateIdentitySchema : Migration + public partial class InitialCreate : Migration { protected override void Up(MigrationBuilder migrationBuilder) { @@ -191,6 +191,60 @@ namespace MultiShop.Server.Data.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "ResultsProfile", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ApplicationUserId = table.Column(type: "TEXT", nullable: true), + Order = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ResultsProfile", x => x.Id); + table.ForeignKey( + name: "FK_ResultsProfile_AspNetUsers_ApplicationUserId", + column: x => x.ApplicationUserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + + migrationBuilder.CreateTable( + name: "SearchProfile", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ApplicationUserId = table.Column(type: "TEXT", nullable: true), + Currency = table.Column(type: "INTEGER", nullable: false), + MaxResults = table.Column(type: "INTEGER", nullable: false), + MinRating = table.Column(type: "REAL", nullable: false), + KeepUnrated = table.Column(type: "INTEGER", nullable: false), + EnableUpperPrice = table.Column(type: "INTEGER", nullable: false), + UpperPrice = table.Column(type: "INTEGER", nullable: false), + LowerPrice = table.Column(type: "INTEGER", nullable: false), + MinPurchases = table.Column(type: "INTEGER", nullable: false), + KeepUnknownPurchaseCount = table.Column(type: "INTEGER", nullable: false), + MinReviews = table.Column(type: "INTEGER", nullable: false), + KeepUnknownRatingCount = table.Column(type: "INTEGER", nullable: false), + EnableMaxShippingFee = table.Column(type: "INTEGER", nullable: false), + MaxShippingFee = table.Column(type: "INTEGER", nullable: false), + KeepUnknownShipping = table.Column(type: "INTEGER", nullable: false), + ShopStates = table.Column(type: "TEXT", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SearchProfile", x => x.Id); + table.ForeignKey( + name: "FK_SearchProfile_AspNetUsers_ApplicationUserId", + column: x => x.ApplicationUserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + migrationBuilder.CreateIndex( name: "IX_AspNetRoleClaims_RoleId", table: "AspNetRoleClaims", @@ -253,6 +307,18 @@ namespace MultiShop.Server.Data.Migrations name: "IX_PersistedGrants_SubjectId_SessionId_Type", table: "PersistedGrants", columns: new[] { "SubjectId", "SessionId", "Type" }); + + migrationBuilder.CreateIndex( + name: "IX_ResultsProfile_ApplicationUserId", + table: "ResultsProfile", + column: "ApplicationUserId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_SearchProfile_ApplicationUserId", + table: "SearchProfile", + column: "ApplicationUserId", + unique: true); } protected override void Down(MigrationBuilder migrationBuilder) @@ -278,6 +344,12 @@ namespace MultiShop.Server.Data.Migrations migrationBuilder.DropTable( name: "PersistedGrants"); + migrationBuilder.DropTable( + name: "ResultsProfile"); + + migrationBuilder.DropTable( + name: "SearchProfile"); + migrationBuilder.DropTable( name: "AspNetRoles"); diff --git a/src/MultiShop/Server/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/src/MultiShop/Server/Data/Migrations/ApplicationDbContextModelSnapshot.cs index 8dce4be..6284e00 100644 --- a/src/MultiShop/Server/Data/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/MultiShop/Server/Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -1,9 +1,9 @@ // using System; -using MultiShop.Server.Data; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using MultiShop.Server.Data; namespace MultiShop.Server.Data.Migrations { @@ -14,71 +14,7 @@ namespace MultiShop.Server.Data.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "5.0.0-rc.1.20417.2"); - - modelBuilder.Entity("MultiShop.Server.Models.ApplicationUser", b => - { - b.Property("Id") - .HasColumnType("TEXT"); - - b.Property("AccessFailedCount") - .HasColumnType("INTEGER"); - - b.Property("ConcurrencyStamp") - .IsConcurrencyToken() - .HasColumnType("TEXT"); - - b.Property("Email") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("EmailConfirmed") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnabled") - .HasColumnType("INTEGER"); - - b.Property("LockoutEnd") - .HasColumnType("TEXT"); - - b.Property("NormalizedEmail") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("NormalizedUserName") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.Property("PasswordHash") - .HasColumnType("TEXT"); - - b.Property("PhoneNumber") - .HasColumnType("TEXT"); - - b.Property("PhoneNumberConfirmed") - .HasColumnType("INTEGER"); - - b.Property("SecurityStamp") - .HasColumnType("TEXT"); - - b.Property("TwoFactorEnabled") - .HasColumnType("INTEGER"); - - b.Property("UserName") - .HasMaxLength(256) - .HasColumnType("TEXT"); - - b.HasKey("Id"); - - b.HasIndex("NormalizedEmail") - .HasDatabaseName("EmailIndex"); - - b.HasIndex("NormalizedUserName") - .IsUnique() - .HasDatabaseName("UserNameIndex"); - - b.ToTable("AspNetUsers"); - }); + .HasAnnotation("ProductVersion", "5.0.6"); modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.DeviceFlowCodes", b => { @@ -315,6 +251,154 @@ namespace MultiShop.Server.Data.Migrations b.ToTable("AspNetUserTokens"); }); + modelBuilder.Entity("MultiShop.Server.Models.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers"); + }); + + modelBuilder.Entity("MultiShop.Shared.Models.ResultsProfile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApplicationUserId") + .HasColumnType("TEXT"); + + b.Property("Order") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId") + .IsUnique(); + + b.ToTable("ResultsProfile"); + }); + + modelBuilder.Entity("MultiShop.Shared.Models.SearchProfile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApplicationUserId") + .HasColumnType("TEXT"); + + b.Property("Currency") + .HasColumnType("INTEGER"); + + b.Property("EnableMaxShippingFee") + .HasColumnType("INTEGER"); + + b.Property("EnableUpperPrice") + .HasColumnType("INTEGER"); + + b.Property("KeepUnknownPurchaseCount") + .HasColumnType("INTEGER"); + + b.Property("KeepUnknownRatingCount") + .HasColumnType("INTEGER"); + + b.Property("KeepUnknownShipping") + .HasColumnType("INTEGER"); + + b.Property("KeepUnrated") + .HasColumnType("INTEGER"); + + b.Property("LowerPrice") + .HasColumnType("INTEGER"); + + b.Property("MaxResults") + .HasColumnType("INTEGER"); + + b.Property("MaxShippingFee") + .HasColumnType("INTEGER"); + + b.Property("MinPurchases") + .HasColumnType("INTEGER"); + + b.Property("MinRating") + .HasColumnType("REAL"); + + b.Property("MinReviews") + .HasColumnType("INTEGER"); + + b.Property("ShopStates") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UpperPrice") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId") + .IsUnique(); + + b.ToTable("SearchProfile"); + }); + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => { b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole", null) @@ -365,6 +449,29 @@ namespace MultiShop.Server.Data.Migrations .OnDelete(DeleteBehavior.Cascade) .IsRequired(); }); + + modelBuilder.Entity("MultiShop.Shared.Models.ResultsProfile", b => + { + b.HasOne("MultiShop.Server.Models.ApplicationUser", null) + .WithOne("ResultsProfile") + .HasForeignKey("MultiShop.Shared.Models.ResultsProfile", "ApplicationUserId"); + }); + + modelBuilder.Entity("MultiShop.Shared.Models.SearchProfile", b => + { + b.HasOne("MultiShop.Server.Models.ApplicationUser", null) + .WithOne("SearchProfile") + .HasForeignKey("MultiShop.Shared.Models.SearchProfile", "ApplicationUserId"); + }); + + modelBuilder.Entity("MultiShop.Server.Models.ApplicationUser", b => + { + b.Navigation("ResultsProfile") + .IsRequired(); + + b.Navigation("SearchProfile") + .IsRequired(); + }); #pragma warning restore 612, 618 } } diff --git a/src/MultiShop/Server/Models/ApplicationUser.cs b/src/MultiShop/Server/Models/ApplicationUser.cs index 74b623f..79f3ff3 100644 --- a/src/MultiShop/Server/Models/ApplicationUser.cs +++ b/src/MultiShop/Server/Models/ApplicationUser.cs @@ -1,12 +1,15 @@ -using Microsoft.AspNetCore.Identity; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Identity; +using MultiShop.Shared.Models; namespace MultiShop.Server.Models { public class ApplicationUser : IdentityUser { + [Required] + public virtual SearchProfile SearchProfile { get; private set; } = new SearchProfile(); + + [Required] + public virtual ResultsProfile ResultsProfile { get; private set; } = new ResultsProfile(); } } diff --git a/src/MultiShop/Server/MultiShop.Server.csproj b/src/MultiShop/Server/MultiShop.Server.csproj index 8fcc3a1..56de510 100644 --- a/src/MultiShop/Server/MultiShop.Server.csproj +++ b/src/MultiShop/Server/MultiShop.Server.csproj @@ -11,6 +11,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all + diff --git a/src/MultiShop/Server/Startup.cs b/src/MultiShop/Server/Startup.cs index 05e0f2d..dc2f360 100644 --- a/src/MultiShop/Server/Startup.cs +++ b/src/MultiShop/Server/Startup.cs @@ -10,6 +10,8 @@ using Microsoft.Extensions.Hosting; using System.Linq; using MultiShop.Server.Data; using MultiShop.Server.Models; +using Microsoft.AspNetCore.Identity; +using System.Security.Claims; namespace MultiShop.Server { @@ -26,9 +28,10 @@ namespace MultiShop.Server // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { - services.AddDbContext(options => - options.UseSqlite( - Configuration.GetConnectionString("DefaultConnection"))); + services.AddDbContext(options => { + options.UseLazyLoadingProxies(); + options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")); + }); services.AddDatabaseDeveloperPageExceptionFilter(); @@ -38,6 +41,8 @@ namespace MultiShop.Server services.AddIdentityServer() .AddApiAuthorization(); + services.Configure(Options => Options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier); //Note: Despite default, doesn't work without this. + services.AddAuthentication() .AddIdentityServerJwt(); diff --git a/src/MultiShop/Server/modules/MultiShop.Shop.AliExpressModule.dll b/src/MultiShop/Server/modules/MultiShop.Shop.AliExpressModule.dll index 5b4743f9df6bc35ec74d981220207b3829a8cac2..ae01e7dfffda996f5305a779c65aa5cbfb840cd5 100644 GIT binary patch delta 107 zcmZoT!`N_!aY6@+WbFJc8+&+y1)8TlP;LMG*`x2B%u4xpn-6VX5!}PY(z)r%lF1fv z0Rlb;emwG=yUJs)-kzfqmY-WYc}85j0#q=g7Ago-t@8P!>vZ E0C+Vx`~Uy| delta 107 zcmZoT!`N_!aY6^nE!~$b8+&+y1%CdsZ<*et;@iS9bJebm$vvA_1ov>UF!J1ZG1($6 zK;Y!AdCRn}rMNG0pQD-bZ{p&~GveA6pn~E`P(hIDJT05&Oy6tuJ~*FX-)tX$kPQHq C(lQVL diff --git a/src/MultiShop/Server/modules/MultiShop.Shop.BanggoodModule.dll b/src/MultiShop/Server/modules/MultiShop.Shop.BanggoodModule.dll index 6999c8208a1415344fb770ee37bb23a9d14e13ec..e08aa06eb3cb297400dcfa2f3e92f9eeb3e976b1 100644 GIT binary patch delta 105 zcmZojX-JvS!D3;5^w!3n5G8>l$~$s+nGd*46KgT}^QPeX<_}7HSXuftA`VR6pb;Q& zsCw5N9@aXCojK1A8|HH_nk=B%tN;}>_z4vRs&>eE7(Z+3!WA;r*O?D)-mK}%0RW+k BF0=pu delta 105 zcmZojX-JvS!D1IS`Sr%05G8?~#l2fCw()td;dr)B=t=3^%^#HZu(D*b8l9NDK_ft5 zjZn$I=}O%WyCtIZPR-Pno-Cl*tN;}}@Ea-!RPAxm^5Ab3pT4HT?$isLH*5NG004cg BEph+= diff --git a/src/MultiShop/Shared/Models/ProductListingInfo.cs b/src/MultiShop/Shared/Models/ProductListingInfo.cs new file mode 100644 index 0000000..efa3f38 --- /dev/null +++ b/src/MultiShop/Shared/Models/ProductListingInfo.cs @@ -0,0 +1,72 @@ +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text.Json; +using MultiShop.Shared; +using MultiShop.Shop.Framework; + +namespace MultiShop.Shared.Models +{ + public class ProductListingInfo + { + public int Id { get; set; } + + private ProductListing? cachedListing; + + [Required] + private string _listing = null; + public ProductListing Listing + { + get + { + if (cachedListing == null) cachedListing = JsonSerializer.Deserialize(_listing); + return cachedListing.Value; + } + + set + { + _listing = JsonSerializer.Serialize(value); + cachedListing = value; + } + } + public string ShopName { get; private set; } + + public float? RatingToPriceRatio + { + get + { + int reviewFactor = Listing.ReviewCount.HasValue ? Listing.ReviewCount.Value : 1; + int purchaseFactor = Listing.PurchaseCount.HasValue ? Listing.PurchaseCount.Value : 1; + return (Listing.Rating * (reviewFactor > purchaseFactor ? reviewFactor : purchaseFactor)) / (Listing.LowerPrice * Listing.UpperPrice); + } + } + public ISet Tops { get; private set; } = new HashSet(); + + public ProductListingInfo(ProductListing listing, string shopName) + { + this.Listing = listing; + this.ShopName = shopName; + } + + public ProductListingInfo() + { + + } + + public override bool Equals(object obj) + { + + if (obj == null || GetType() != obj.GetType()) + { + return false; + } + ProductListingInfo other = (ProductListingInfo)obj; + return Id == other.Id && ShopName.Equals(other.ShopName) && Listing.Equals(other.Listing); + } + + public override int GetHashCode() + { + return Id; + } + } +} \ No newline at end of file diff --git a/src/MultiShop/Shared/ResultsProfile.cs b/src/MultiShop/Shared/Models/ResultsProfile.cs similarity index 56% rename from src/MultiShop/Shared/ResultsProfile.cs rename to src/MultiShop/Shared/Models/ResultsProfile.cs index 5ff7f69..cc6ba8a 100644 --- a/src/MultiShop/Shared/ResultsProfile.cs +++ b/src/MultiShop/Shared/Models/ResultsProfile.cs @@ -1,26 +1,28 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Linq; +using System.Text.Json; -namespace MultiShop.Shared +namespace MultiShop.Shared.Models { public class ResultsProfile { - public List Order { get; private set; } = new List(Enum.GetValues().Length); + public int Id { get; set; } + public string ApplicationUserId { get; set; } + + [Required] + public IList Order { get; set; } public ResultsProfile() { + Order = new List(Enum.GetValues().Length); foreach (Category category in Enum.GetValues()) { Order.Add(category); } } - public Category GetCategory(int position) - { - return Order[position]; - } - public enum Category { RatingPriceRatio, diff --git a/src/MultiShop/Shared/Models/SearchProfile.cs b/src/MultiShop/Shared/Models/SearchProfile.cs new file mode 100644 index 0000000..df8f855 --- /dev/null +++ b/src/MultiShop/Shared/Models/SearchProfile.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using MultiShop.Shop.Framework; + +namespace MultiShop.Shared.Models +{ + public class SearchProfile + { + public int Id { get; set; } + public string ApplicationUserId { get; set; } + + public Currency Currency { get; set; } = Currency.CAD; + public int MaxResults { get; set; } = 100; + public float MinRating { get; set; } = 0.8f; + public bool KeepUnrated { get; set; } = true; + public bool EnableUpperPrice { get; set; } = false; + private int _upperPrice; + + public int UpperPrice + { + get + { + return _upperPrice; + } + set + { + if (EnableUpperPrice) _upperPrice = value; + } + } + public int LowerPrice { get; set; } + public int MinPurchases { get; set; } + public bool KeepUnknownPurchaseCount { get; set; } = true; + public int MinReviews { get; set; } + public bool KeepUnknownRatingCount { get; set; } = true; + public bool EnableMaxShippingFee { get; set; } + private int _maxShippingFee; + + public int MaxShippingFee + { + get + { + return _maxShippingFee; + } + set + { + if (EnableMaxShippingFee) _maxShippingFee = value; + } + } + public bool KeepUnknownShipping { get; set; } + + [Required] + public ShopToggler ShopStates { get; set; } = new ShopToggler(); + + public sealed class ShopToggler : HashSet + { + public int TotalShops { get; set; } + public bool this[string name] { + get { + return !this.Contains(name); + } + set { + if (value == false && TotalShops - Count <= 1) return; + if (value) + { + this.Remove(name); + } + else + { + this.Add(name); + } + } + } + + public ShopToggler Clone() { + ShopToggler clone = new ShopToggler(); + clone.Union(this); + return clone; + } + + public bool IsShopToggleable(string shop) + { + return (!Contains(shop) && TotalShops - Count > 1) || Contains(shop); + } + } + + public override bool Equals(object obj) + { + if (obj == null || GetType() != obj.GetType()) + { + return false; + } + SearchProfile other = (SearchProfile) obj; + return + Id == other.Id && + Currency == other.Currency && + MaxResults == other.MaxResults && + MinRating == other.MinRating && + KeepUnrated == other.KeepUnrated && + EnableUpperPrice == other.EnableUpperPrice && + UpperPrice == other.UpperPrice && + LowerPrice == other.LowerPrice && + MinPurchases == other.MinPurchases && + KeepUnknownPurchaseCount == other.KeepUnknownPurchaseCount && + MinReviews == other.MinReviews && + KeepUnknownRatingCount == other.KeepUnknownRatingCount && + EnableMaxShippingFee == other.EnableMaxShippingFee && + MaxShippingFee == other.MaxShippingFee && + KeepUnknownShipping == other.KeepUnknownShipping && + ShopStates.Equals(other.ShopStates); + } + + public override int GetHashCode() + { + return Id; + } + } +} \ No newline at end of file diff --git a/src/MultiShop/Shared/ProductListingInfo.cs b/src/MultiShop/Shared/ProductListingInfo.cs deleted file mode 100644 index a1225e3..0000000 --- a/src/MultiShop/Shared/ProductListingInfo.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System.Collections.Generic; -using MultiShop.Shop.Framework; - -namespace MultiShop.Shared -{ - public class ProductListingInfo - { - public ProductListing Listing { get; private set; } - public string ShopName { get; private set; } - public float? RatingToPriceRatio { - get { - int reviewFactor = Listing.ReviewCount.HasValue ? Listing.ReviewCount.Value : 1; - int purchaseFactor = Listing.PurchaseCount.HasValue ? Listing.PurchaseCount.Value : 1; - return (Listing.Rating * (reviewFactor > purchaseFactor ? reviewFactor : purchaseFactor))/(Listing.LowerPrice * Listing.UpperPrice); - } - } - public ISet Tops { get; private set; } = new HashSet(); - - public ProductListingInfo(ProductListing listing, string shopName) - { - this.Listing = listing; - this.ShopName = shopName; - } - } -} \ No newline at end of file diff --git a/src/MultiShop/Shared/SearchProfile.cs b/src/MultiShop/Shared/SearchProfile.cs deleted file mode 100644 index 02f8ab2..0000000 --- a/src/MultiShop/Shared/SearchProfile.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System.Collections.Generic; -using MultiShop.Shop.Framework; - -namespace MultiShop.Shared -{ - public class SearchProfile - { - public Currency currency; - public int maxResults; - public float minRating; - public bool keepUnrated; - public bool enableUpperPrice; - private int upperPrice; - public int UpperPrice - { - get - { - return upperPrice; - } - set - { - if (enableUpperPrice) upperPrice = value; - } - } - public int lowerPrice; - public int minPurchases; - public bool keepUnknownPurchaseCount; - public int minReviews; - public bool keepUnknownRatingCount; - public bool enableMaxShippingFee; - private int maxShippingFee; - public int MaxShippingFee { - get { - return maxShippingFee; - } - set { - if (enableMaxShippingFee) maxShippingFee = value; - } - } - public bool keepUnknownShipping; - public ShopStateTracker shopStates = new ShopStateTracker(); - - public SearchProfile() - { - currency = Currency.CAD; - maxResults = 100; - minRating = 0.8f; - keepUnrated = true; - enableUpperPrice = false; - upperPrice = 0; - lowerPrice = 0; - minPurchases = 0; - keepUnknownPurchaseCount = true; - minReviews = 0; - keepUnknownRatingCount = true; - enableMaxShippingFee = false; - maxShippingFee = 0; - keepUnknownShipping = true; - } - - public class ShopStateTracker - { - private HashSet shopsEnabled = new HashSet(); - public bool this[string name] - { - get - { - return shopsEnabled.Contains(name); - } - - set - { - if (value == false && !(shopsEnabled.Count > 1)) return; - if (value) - { - shopsEnabled.Add(name); - } - else - { - shopsEnabled.Remove(name); - } - } - } - public bool IsToggleable(string shop) { - return (shopsEnabled.Contains(shop) && shopsEnabled.Count > 1) || !shopsEnabled.Contains(shop); - } - } - } -} \ No newline at end of file From 36ae3e5c99e95a201f9128d3f61c76c00954af13 Mon Sep 17 00:00:00 2001 From: Harrison Deng Date: Tue, 25 May 2021 20:48:47 -0500 Subject: [PATCH 028/167] Wrote script to completely regenerate database files. --- scripts/reset_db.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 scripts/reset_db.py diff --git a/scripts/reset_db.py b/scripts/reset_db.py new file mode 100644 index 0000000..1e24156 --- /dev/null +++ b/scripts/reset_db.py @@ -0,0 +1,25 @@ +import os +import shutil + +SERVER_DIR = "../src/MultiShop/Server" +DATA_DIR = "Data" +DB_MIGRATE_CMD = "dotnet ef migrations add InitialCreate -o {0}" +DB_UPDATE_CMD = "dotnet ef database update" + +print("Changing to database migrations directory.") +os.chdir(SERVER_DIR) + +migrationsDir = os.path.join(DATA_DIR, "Migrations") + +print("Deleting current migrations directory if it exists.") +shutil.rmtree(migrationsDir, ignore_errors=True) + +print("Deleting old app.db if it exists.") +if os.path.exists("app.db"): + os.remove("app.db") + +print("Creating migration.") +os.system(DB_MIGRATE_CMD.format(migrationsDir)) + +print("Updating database.") +os.system(DB_UPDATE_CMD) \ No newline at end of file From 862fbe15ed493dc82a5217c08986d2f44abe1f4f Mon Sep 17 00:00:00 2001 From: Harrison Deng Date: Tue, 25 May 2021 20:49:22 -0500 Subject: [PATCH 029/167] Reworked client assembly loading. --- src/MultiShop/Client/App.razor.cs | 106 ++++++++---------- .../{Models => }/ResultProfileExtensions.cs | 2 +- .../{Models => }/SearchProfileExtensions.cs | 2 +- .../Client/Module/ShopModuleLoadContext.cs | 39 +++++++ src/MultiShop/Client/Pages/Search.razor | 2 +- src/MultiShop/Client/Pages/Search.razor.cs | 2 +- .../Controllers/ShopModuleController.cs | 88 +++++++++++++++ .../Controllers/ShopModulesController.cs | 49 -------- ...{ShopsOptions.cs => ShopModulesOptions.cs} | 4 +- src/MultiShop/Server/appsettings.json | 2 +- .../{ => dependencies}/HtmlAgilityPack.dll | Bin .../{ => dependencies}/SimpleLogger.dll | Bin 12 files changed, 180 insertions(+), 116 deletions(-) rename src/MultiShop/Client/Extensions/{Models => }/ResultProfileExtensions.cs (97%) rename src/MultiShop/Client/Extensions/{Models => }/SearchProfileExtensions.cs (68%) create mode 100644 src/MultiShop/Client/Module/ShopModuleLoadContext.cs create mode 100644 src/MultiShop/Server/Controllers/ShopModuleController.cs delete mode 100644 src/MultiShop/Server/Controllers/ShopModulesController.cs rename src/MultiShop/Server/Options/{ShopsOptions.cs => ShopModulesOptions.cs} (78%) rename src/MultiShop/Server/modules/{ => dependencies}/HtmlAgilityPack.dll (100%) rename src/MultiShop/Server/modules/{ => dependencies}/SimpleLogger.dll (100%) diff --git a/src/MultiShop/Client/App.razor.cs b/src/MultiShop/Client/App.razor.cs index 0c66464..e0d1749 100644 --- a/src/MultiShop/Client/App.razor.cs +++ b/src/MultiShop/Client/App.razor.cs @@ -5,6 +5,7 @@ using System.Net.Http; using System.Net.Http.Json; using System.Reflection; using System.Threading.Tasks; +using MultiShop.Client.Module; using MultiShop.Shop.Framework; using SimpleLogger; @@ -15,99 +16,84 @@ namespace MultiShop.Client private bool modulesLoaded = false; private Dictionary shops = new Dictionary(); - private Dictionary assemblyData = new Dictionary(); - private Dictionary assemblyCache = new Dictionary(); protected override async Task OnInitializedAsync() { - await DownloadShopModules(); await base.OnInitializedAsync(); + await DownloadShopModules(); } private async Task DownloadShopModules() { HttpClient http = HttpFactory.CreateClient("Public-MultiShop.ServerAPI"); - Logger.Log($"Fetching shop modules.", LogLevel.Debug); - string[] assemblyFileNames = await http.GetFromJsonAsync("ShopModules"); - Dictionary, string> downloadTasks = new Dictionary, string>(assemblyFileNames.Length); - foreach (string assemblyFileName in assemblyFileNames) + Dictionary assemblyData = new Dictionary(); + + string[] moduleNames = await http.GetFromJsonAsync("ShopModule/Modules"); + string[] dependencyNames = await http.GetFromJsonAsync("ShopModule/Dependencies"); + Dictionary, string> downloadTasks = new Dictionary, string>(); + + Logger.Log("Beginning to download shop modules..."); + foreach (string moduleName in moduleNames) { - Logger.Log($"Downloading \"{assemblyFileName}\"...", LogLevel.Debug); - downloadTasks.Add(http.GetByteArrayAsync(Path.Join("ShopModules", assemblyFileName)), assemblyFileName); + Logger.Log($"Downloading shop: {moduleName}", LogLevel.Debug); + downloadTasks.Add(http.GetByteArrayAsync("shopModule/Modules/" + moduleName), moduleName); + } + Logger.Log("Beginning to download shop module dependencies..."); + foreach (string depName in dependencyNames) + { + Logger.Log($"Downloading shop module dependency: {depName}", LogLevel.Debug); + downloadTasks.Add(http.GetByteArrayAsync("ShopModule/Dependencies/" + depName), depName); } - while (downloadTasks.Count != 0) + while (downloadTasks.Count > 0) { - Task data = await Task.WhenAny(downloadTasks.Keys); - string assemblyFileName = downloadTasks[data]; - Logger.Log($"\"{assemblyFileName}\" completed downloading.", LogLevel.Debug); - assemblyData.Add(assemblyFileName, data.Result); - downloadTasks.Remove(data); + Task downloadTask = await Task.WhenAny(downloadTasks.Keys); + assemblyData.Add(downloadTasks[downloadTask], await downloadTask); + Logger.Log($"Shop module \"{downloadTasks[downloadTask]}\" completed downloading.", LogLevel.Debug); + downloadTasks.Remove(downloadTask); } + Logger.Log($"Downloaded {assemblyData.Count} assemblies in total."); - AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyDependencyRequest; - - foreach (string assemblyFileName in assemblyData.Keys) + ShopModuleLoadContext context = new ShopModuleLoadContext(assemblyData); + Logger.Log("Beginning to load shop modules."); + foreach (string moduleName in moduleNames) { - Assembly assembly = AppDomain.CurrentDomain.Load(assemblyData[assemblyFileName]); - bool used = false; - foreach (Type type in assembly.GetTypes()) + Logger.Log($"Attempting to load shop module: \"{moduleName}\"", LogLevel.Debug); + Assembly moduleAssembly = context.LoadFromAssemblyName(new AssemblyName(moduleName)); + bool shopLoaded = false; + foreach (Type type in moduleAssembly.GetTypes()) { - if (typeof(IShop).IsAssignableFrom(type)) - { + if (typeof(IShop).IsAssignableFrom(type)) { IShop shop = Activator.CreateInstance(type) as IShop; - if (shop != null) - { + if (shop != null) { + shopLoaded = true; shop.Initialize(); shops.Add(shop.ShopName, shop); - Logger.Log($"Registered and started lifetime of module for \"{shop.ShopName}\".", LogLevel.Debug); - used = true; + Logger.Log($"Added shop: {shop.ShopName}", LogLevel.Debug); } } } - if (!used) { - Logger.Log($"Since unused, caching \"{assemblyFileName}\".", LogLevel.Debug); - assemblyCache.Add(assemblyFileName, assembly); + if (!shopLoaded) { + Logger.Log($"Module \"{moduleName}\" was reported to be a shop module, but did not contain a shop interface. Please report this to the site administrator.", LogLevel.Warning); } - assemblyData.Remove(assemblyFileName); } - foreach (string assembly in assemblyData.Keys) - { - Logger.Log($"\"{assembly}\" was unused.", LogLevel.Warning); - } - foreach (string assembly in assemblyCache.Keys) - { - Logger.Log($"\"{assembly}\" was unused.", LogLevel.Warning); - } - assemblyData.Clear(); - assemblyCache.Clear(); + Logger.Log($"Shop module loading complete. Loaded a total of {shops.Count} shops."); modulesLoaded = true; - } - - - private Assembly OnAssemblyDependencyRequest(object sender, ResolveEventArgs args) - { - string dependencyName = args.Name.Substring(0, args.Name.IndexOf(',')); - Logger.Log($"Assembly \"{args.RequestingAssembly.GetName().Name}\" is requesting dependency assembly \"{dependencyName}\".", LogLevel.Debug); - if (assemblyCache.ContainsKey(dependencyName)) { - Logger.Log($"Found \"{dependencyName}\" in cache.", LogLevel.Debug); - Assembly dep = assemblyCache[dependencyName]; - assemblyCache.Remove(dependencyName); - return dep; - } else if (assemblyData.ContainsKey(dependencyName)) { - return AppDomain.CurrentDomain.Load(assemblyData[dependencyName]); - } else { - Logger.Log($"No dependency under name \"{args.Name}\"", LogLevel.Warning); - return null; + foreach (string assemblyName in context.UseCounter.Keys) + { + int usage = context.UseCounter[assemblyName]; + Logger.Log($"\"{assemblyName}\" was used {usage} times.", LogLevel.Debug); + if (usage <= 0) { + Logger.Log($"\"{assemblyName}\" was not used at all.", LogLevel.Warning); + } } } - public void Dispose() { foreach (string name in shops.Keys) { shops[name].Dispose(); - Logger.Log($"Ending lifetime of shop module for \"{name}\"."); + Logger.Log($"Ending lifetime of shop module for \"{name}\".", LogLevel.Debug); } } } diff --git a/src/MultiShop/Client/Extensions/Models/ResultProfileExtensions.cs b/src/MultiShop/Client/Extensions/ResultProfileExtensions.cs similarity index 97% rename from src/MultiShop/Client/Extensions/Models/ResultProfileExtensions.cs rename to src/MultiShop/Client/Extensions/ResultProfileExtensions.cs index ae315c6..607c7d2 100644 --- a/src/MultiShop/Client/Extensions/Models/ResultProfileExtensions.cs +++ b/src/MultiShop/Client/Extensions/ResultProfileExtensions.cs @@ -2,7 +2,7 @@ using System; using MultiShop.Shared; using MultiShop.Shared.Models; -namespace MultiShop.Client.Extensions.Models +namespace MultiShop.Client.Extensions { public static class ResultProfileExtensions { diff --git a/src/MultiShop/Client/Extensions/Models/SearchProfileExtensions.cs b/src/MultiShop/Client/Extensions/SearchProfileExtensions.cs similarity index 68% rename from src/MultiShop/Client/Extensions/Models/SearchProfileExtensions.cs rename to src/MultiShop/Client/Extensions/SearchProfileExtensions.cs index 10c9ae8..5f7e109 100644 --- a/src/MultiShop/Client/Extensions/Models/SearchProfileExtensions.cs +++ b/src/MultiShop/Client/Extensions/SearchProfileExtensions.cs @@ -1,6 +1,6 @@ using MultiShop.Shared.Models; -namespace MultiShop.Client.Extensions.Models +namespace MultiShop.Client.Extensions { public static class SearchProfileExtensions { diff --git a/src/MultiShop/Client/Module/ShopModuleLoadContext.cs b/src/MultiShop/Client/Module/ShopModuleLoadContext.cs new file mode 100644 index 0000000..221c407 --- /dev/null +++ b/src/MultiShop/Client/Module/ShopModuleLoadContext.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Runtime.Loader; +using SimpleLogger; + +namespace MultiShop.Client.Module +{ + public class ShopModuleLoadContext : AssemblyLoadContext + { + private IDictionary rawAssemblies; + private Dictionary useCounter; + public IReadOnlyDictionary UseCounter {get => useCounter;} + public ShopModuleLoadContext(IEnumerable> assemblies) + { + this.rawAssemblies = new Dictionary(assemblies); + useCounter = new Dictionary(); + } + + protected override Assembly Load(AssemblyName assemblyName) + { + Logger.Log("ShopModuleLoadContext is attempting to load assembly: " + assemblyName.FullName, LogLevel.Debug); + if (!rawAssemblies.ContainsKey(assemblyName.FullName)) return null; + + useCounter[assemblyName.FullName] = useCounter.GetValueOrDefault(assemblyName.FullName) + 1; + + using (MemoryStream stream = new MemoryStream(rawAssemblies[assemblyName.FullName])) + { + return LoadFromStream(stream); + } + } + + protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) + { + throw new NotImplementedException($"Cannot load unmanaged dll \"{unmanagedDllName}\"."); + } + } +} \ No newline at end of file diff --git a/src/MultiShop/Client/Pages/Search.razor b/src/MultiShop/Client/Pages/Search.razor index 027bca2..5494b71 100644 --- a/src/MultiShop/Client/Pages/Search.razor +++ b/src/MultiShop/Client/Pages/Search.razor @@ -1,6 +1,6 @@ @page "/search/{Query?}" -@using MultiShop.Client.Extensions.Models +@using MultiShop.Client.Extensions
diff --git a/src/MultiShop/Client/Pages/Search.razor.cs b/src/MultiShop/Client/Pages/Search.razor.cs index 1674874..7fbc81a 100644 --- a/src/MultiShop/Client/Pages/Search.razor.cs +++ b/src/MultiShop/Client/Pages/Search.razor.cs @@ -6,7 +6,7 @@ using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Authorization; -using MultiShop.Client.Extensions.Models; +using MultiShop.Client.Extensions; using MultiShop.Client.Listing; using MultiShop.Shared.Models; using MultiShop.Shop.Framework; diff --git a/src/MultiShop/Server/Controllers/ShopModuleController.cs b/src/MultiShop/Server/Controllers/ShopModuleController.cs new file mode 100644 index 0000000..5be2c2e --- /dev/null +++ b/src/MultiShop/Server/Controllers/ShopModuleController.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using MultiShop.Server.Options; +using SimpleLogger; + +namespace MultiShop.Server.Controllers +{ + [ApiController] + [Route("[controller]")] + public class ShopModuleController : ControllerBase + { + private readonly IConfiguration configuration; + private IDictionary shopModules; + private IDictionary shopModuleDependencies; + + + public ShopModuleController(IConfiguration configuration) + { + this.configuration = configuration; + this.shopModules = new Dictionary(); + this.shopModuleDependencies = new Dictionary(); + + ShopModulesOptions options = configuration.GetSection(ShopModulesOptions.ShopModules).Get(); + foreach (string file in Directory.EnumerateFiles(options.Directory)) + { + try + { + AssemblyName assemblyName = AssemblyName.GetAssemblyName(file); + shopModules.Add(assemblyName.FullName, System.IO.File.ReadAllBytes(file)); + } + catch (BadImageFormatException e) { + Logger.Log($"\"{e.FileName}\" is not a valid assembly. Ignoring.", LogLevel.Warning); + } + catch (ArgumentException) { + Logger.Log($"\"{Path.GetFileName(file)}\" has the same full name as another assembly. Ignoring this one.", LogLevel.Warning); + } + } + + foreach (string file in Directory.EnumerateFiles(Path.Join(options.Directory, "dependencies"))) + { + try + { + AssemblyName assemblyName = AssemblyName.GetAssemblyName(file); + shopModuleDependencies.Add(assemblyName.FullName, System.IO.File.ReadAllBytes(file)); + } + catch (BadImageFormatException e) { + Logger.Log($"\"{e.FileName}\" is not a valid assembly. Ignoring.", LogLevel.Warning); + } + catch (ArgumentException) { + Logger.Log($"\"{Path.GetFileName(file)}\" has the same full name as another assembly. Ignoring this one.", LogLevel.Warning); + } + } + } + + [HttpGet] + [Route("Modules")] + public IActionResult GetShopModuleNames() { + return Ok(shopModules.Keys); + } + + [HttpGet] + [Route("Modules/{shopModuleName}")] + public IActionResult GetModule(string shopModuleName) { + ShopModulesOptions options = configuration.GetSection(ShopModulesOptions.ShopModules).Get(); + if (!shopModules.ContainsKey(shopModuleName)) return NotFound(); + if (options.Disabled != null && options.Disabled.Contains(shopModuleName)) return Forbid(); + return File(shopModules[shopModuleName], "application/module-dll"); + } + + [HttpGet] + [Route("Dependencies")] + public IActionResult GetDependencyNames() { + return Ok(shopModuleDependencies.Keys); + } + + [HttpGet] + [Route("Dependencies/{dependencyName}")] + public IActionResult GetDependency(string dependencyName) { + ShopModulesOptions options = configuration.GetSection(ShopModulesOptions.ShopModules).Get(); + if (!shopModuleDependencies.ContainsKey(dependencyName)) return NotFound(); + return File(shopModuleDependencies[dependencyName], "application/module-dep-dll"); + } + } +} \ No newline at end of file diff --git a/src/MultiShop/Server/Controllers/ShopModulesController.cs b/src/MultiShop/Server/Controllers/ShopModulesController.cs deleted file mode 100644 index 30976c0..0000000 --- a/src/MultiShop/Server/Controllers/ShopModulesController.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; -using MultiShop.Server.Options; - -namespace MultiShop.Server.Controllers -{ - [ApiController] - [Route("[controller]")] - public class ShopModulesController : ControllerBase - { - private readonly IConfiguration configuration; - private IDictionary shopAssemblyData; - - - - public ShopModulesController(IConfiguration configuration) - { - this.configuration = configuration; - this.shopAssemblyData = new Dictionary(); - } - - public IActionResult GetShopModuleNames() { - List moduleNames = new List(); - ShopOptions options = configuration.GetSection(ShopOptions.Shop).Get(); - foreach (string file in Directory.EnumerateFiles(options.Directory)) - { - if (Path.GetExtension(file).ToLower().Equals(".dll") && !(options.Disabled != null && options.Disabled.Contains(Path.GetFileNameWithoutExtension(file)))) { - moduleNames.Add(Path.GetFileNameWithoutExtension(file)); - } - } - return Ok(moduleNames); - } - - - [HttpGet] - [Route("{shopModuleName}")] - public IActionResult GetModule(string shopModuleName) { - ShopOptions options = configuration.GetSection(ShopOptions.Shop).Get(); - string shopPath = Path.Join(options.Directory, shopModuleName); - shopPath += ".dll"; - if (!System.IO.File.Exists(shopPath)) return NotFound(); - if (options.Disabled != null && options.Disabled.Contains(shopModuleName)) return Forbid(); - return File(new FileStream(shopPath, FileMode.Open), "application/shop-dll"); - } - } -} \ No newline at end of file diff --git a/src/MultiShop/Server/Options/ShopsOptions.cs b/src/MultiShop/Server/Options/ShopModulesOptions.cs similarity index 78% rename from src/MultiShop/Server/Options/ShopsOptions.cs rename to src/MultiShop/Server/Options/ShopModulesOptions.cs index 894ee64..531f264 100644 --- a/src/MultiShop/Server/Options/ShopsOptions.cs +++ b/src/MultiShop/Server/Options/ShopModulesOptions.cs @@ -3,9 +3,9 @@ using System.Collections.Generic; namespace MultiShop.Server.Options { //https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-5.0#bind-hierarchical-configuration-data-using-the-options-pattern - public class ShopOptions + public class ShopModulesOptions { - public const string Shop = "Shops"; + public const string ShopModules = "ShopModules"; public string Directory { get; set; } public IList Disabled { get; set; } } diff --git a/src/MultiShop/Server/appsettings.json b/src/MultiShop/Server/appsettings.json index 7b1030b..2547378 100644 --- a/src/MultiShop/Server/appsettings.json +++ b/src/MultiShop/Server/appsettings.json @@ -1,5 +1,5 @@ { - "Shops": { + "ShopModules": { "Directory": "modules", "Disabled": [ ] diff --git a/src/MultiShop/Server/modules/HtmlAgilityPack.dll b/src/MultiShop/Server/modules/dependencies/HtmlAgilityPack.dll similarity index 100% rename from src/MultiShop/Server/modules/HtmlAgilityPack.dll rename to src/MultiShop/Server/modules/dependencies/HtmlAgilityPack.dll diff --git a/src/MultiShop/Server/modules/SimpleLogger.dll b/src/MultiShop/Server/modules/dependencies/SimpleLogger.dll similarity index 100% rename from src/MultiShop/Server/modules/SimpleLogger.dll rename to src/MultiShop/Server/modules/dependencies/SimpleLogger.dll From f459cbdfda787eed9c8cb1f0d831ce2ca8db2433 Mon Sep 17 00:00:00 2001 From: Harrison Deng Date: Wed, 26 May 2021 15:10:17 -0500 Subject: [PATCH 030/167] Switched server and client logging to built in logging system. --- src/MultiShop/Client/App.razor.cs | 33 ++++++++++-------- .../Client/Module/ShopModuleLoadContext.cs | 8 +++-- src/MultiShop/Client/MultiShop.Client.csproj | 2 +- src/MultiShop/Client/Pages/Info.razor | 9 ----- src/MultiShop/Client/Pages/Info.razor.cs | 12 +++++++ src/MultiShop/Client/Pages/Search.razor.cs | 21 ++++++----- src/MultiShop/Client/Program.cs | 11 +++--- .../Client/Shared/DragAndDropList.razor | 1 - src/MultiShop/Client/_Imports.razor | 3 +- .../wwwroot/appsettings.Development.json | 10 ++++++ src/MultiShop/Client/wwwroot/appsettings.json | 9 +++++ src/MultiShop/Client/wwwroot/favicon.ico | Bin 5430 -> 0 bytes src/MultiShop/Client/wwwroot/index.html | 2 +- .../Controllers/ShopModuleController.cs | 15 ++++---- .../Server/appsettings.Development.json | 3 +- 15 files changed, 83 insertions(+), 56 deletions(-) create mode 100644 src/MultiShop/Client/Pages/Info.razor.cs create mode 100644 src/MultiShop/Client/wwwroot/appsettings.Development.json create mode 100644 src/MultiShop/Client/wwwroot/appsettings.json delete mode 100644 src/MultiShop/Client/wwwroot/favicon.ico diff --git a/src/MultiShop/Client/App.razor.cs b/src/MultiShop/Client/App.razor.cs index e0d1749..8202be7 100644 --- a/src/MultiShop/Client/App.razor.cs +++ b/src/MultiShop/Client/App.razor.cs @@ -5,14 +5,18 @@ using System.Net.Http; using System.Net.Http.Json; using System.Reflection; using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Logging; using MultiShop.Client.Module; using MultiShop.Shop.Framework; -using SimpleLogger; namespace MultiShop.Client { public partial class App { + [Inject] + private ILogger Logger {get; set; } + private bool modulesLoaded = false; private Dictionary shops = new Dictionary(); @@ -31,16 +35,16 @@ namespace MultiShop.Client string[] dependencyNames = await http.GetFromJsonAsync("ShopModule/Dependencies"); Dictionary, string> downloadTasks = new Dictionary, string>(); - Logger.Log("Beginning to download shop modules..."); + Logger.LogInformation("Beginning to download shop modules..."); foreach (string moduleName in moduleNames) { - Logger.Log($"Downloading shop: {moduleName}", LogLevel.Debug); + Logger.LogDebug($"Downloading shop: {moduleName}"); downloadTasks.Add(http.GetByteArrayAsync("shopModule/Modules/" + moduleName), moduleName); } - Logger.Log("Beginning to download shop module dependencies..."); + Logger.LogInformation("Beginning to download shop module dependencies..."); foreach (string depName in dependencyNames) { - Logger.Log($"Downloading shop module dependency: {depName}", LogLevel.Debug); + Logger.LogDebug($"Downloading shop module dependency: {depName}"); downloadTasks.Add(http.GetByteArrayAsync("ShopModule/Dependencies/" + depName), depName); } @@ -48,16 +52,16 @@ namespace MultiShop.Client { Task downloadTask = await Task.WhenAny(downloadTasks.Keys); assemblyData.Add(downloadTasks[downloadTask], await downloadTask); - Logger.Log($"Shop module \"{downloadTasks[downloadTask]}\" completed downloading.", LogLevel.Debug); + Logger.LogDebug($"Shop module \"{downloadTasks[downloadTask]}\" completed downloading."); downloadTasks.Remove(downloadTask); } - Logger.Log($"Downloaded {assemblyData.Count} assemblies in total."); + Logger.LogInformation($"Downloaded {assemblyData.Count} assemblies in total."); ShopModuleLoadContext context = new ShopModuleLoadContext(assemblyData); - Logger.Log("Beginning to load shop modules."); + Logger.LogInformation("Beginning to load shop modules."); foreach (string moduleName in moduleNames) { - Logger.Log($"Attempting to load shop module: \"{moduleName}\"", LogLevel.Debug); + Logger.LogDebug($"Attempting to load shop module: \"{moduleName}\""); Assembly moduleAssembly = context.LoadFromAssemblyName(new AssemblyName(moduleName)); bool shopLoaded = false; foreach (Type type in moduleAssembly.GetTypes()) @@ -68,22 +72,22 @@ namespace MultiShop.Client shopLoaded = true; shop.Initialize(); shops.Add(shop.ShopName, shop); - Logger.Log($"Added shop: {shop.ShopName}", LogLevel.Debug); + Logger.LogDebug($"Added shop: {shop.ShopName}"); } } } if (!shopLoaded) { - Logger.Log($"Module \"{moduleName}\" was reported to be a shop module, but did not contain a shop interface. Please report this to the site administrator.", LogLevel.Warning); + Logger.LogWarning($"Module \"{moduleName}\" was reported to be a shop module, but did not contain a shop interface. Please report this to the site administrator."); } } - Logger.Log($"Shop module loading complete. Loaded a total of {shops.Count} shops."); + Logger.LogInformation($"Shop module loading complete. Loaded a total of {shops.Count} shops."); modulesLoaded = true; foreach (string assemblyName in context.UseCounter.Keys) { int usage = context.UseCounter[assemblyName]; - Logger.Log($"\"{assemblyName}\" was used {usage} times.", LogLevel.Debug); + Logger.LogDebug($"\"{assemblyName}\" was used {usage} times."); if (usage <= 0) { - Logger.Log($"\"{assemblyName}\" was not used at all.", LogLevel.Warning); + Logger.LogWarning($"\"{assemblyName}\" was not used. Please report this to the site administrator."); } } } @@ -93,7 +97,6 @@ namespace MultiShop.Client foreach (string name in shops.Keys) { shops[name].Dispose(); - Logger.Log($"Ending lifetime of shop module for \"{name}\".", LogLevel.Debug); } } } diff --git a/src/MultiShop/Client/Module/ShopModuleLoadContext.cs b/src/MultiShop/Client/Module/ShopModuleLoadContext.cs index 221c407..9be97ab 100644 --- a/src/MultiShop/Client/Module/ShopModuleLoadContext.cs +++ b/src/MultiShop/Client/Module/ShopModuleLoadContext.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.IO; using System.Reflection; using System.Runtime.Loader; -using SimpleLogger; namespace MultiShop.Client.Module { @@ -16,14 +15,17 @@ namespace MultiShop.Client.Module { this.rawAssemblies = new Dictionary(assemblies); useCounter = new Dictionary(); + foreach (string name in rawAssemblies.Keys) + { + useCounter[name] = 0; + } } protected override Assembly Load(AssemblyName assemblyName) { - Logger.Log("ShopModuleLoadContext is attempting to load assembly: " + assemblyName.FullName, LogLevel.Debug); if (!rawAssemblies.ContainsKey(assemblyName.FullName)) return null; - useCounter[assemblyName.FullName] = useCounter.GetValueOrDefault(assemblyName.FullName) + 1; + useCounter[assemblyName.FullName] += 1; using (MemoryStream stream = new MemoryStream(rawAssemblies[assemblyName.FullName])) { diff --git a/src/MultiShop/Client/MultiShop.Client.csproj b/src/MultiShop/Client/MultiShop.Client.csproj index 7838df0..ff7b48b 100644 --- a/src/MultiShop/Client/MultiShop.Client.csproj +++ b/src/MultiShop/Client/MultiShop.Client.csproj @@ -9,13 +9,13 @@ + - diff --git a/src/MultiShop/Client/Pages/Info.razor b/src/MultiShop/Client/Pages/Info.razor index 10848e6..c9d4544 100644 --- a/src/MultiShop/Client/Pages/Info.razor +++ b/src/MultiShop/Client/Pages/Info.razor @@ -1,11 +1,2 @@ @page "/info" -@using Shop.Framework -
- -
- -@code { - [CascadingParameter(Name = "Shops")] - public Dictionary Shops { get; set; } -} \ No newline at end of file diff --git a/src/MultiShop/Client/Pages/Info.razor.cs b/src/MultiShop/Client/Pages/Info.razor.cs new file mode 100644 index 0000000..d53bc88 --- /dev/null +++ b/src/MultiShop/Client/Pages/Info.razor.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Components; +using MultiShop.Shop.Framework; + +namespace MultiShop.Client.Pages +{ + public partial class Info + { + [CascadingParameter(Name = "Shops")] + public Dictionary Shops { get; set; } + } +} \ No newline at end of file diff --git a/src/MultiShop/Client/Pages/Search.razor.cs b/src/MultiShop/Client/Pages/Search.razor.cs index 7fbc81a..b349776 100644 --- a/src/MultiShop/Client/Pages/Search.razor.cs +++ b/src/MultiShop/Client/Pages/Search.razor.cs @@ -2,20 +2,21 @@ using System; using System.Collections.Generic; using System.Net.Http; using System.Net.Http.Json; -using System.Text.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.Extensions.Logging; using MultiShop.Client.Extensions; using MultiShop.Client.Listing; using MultiShop.Shared.Models; using MultiShop.Shop.Framework; -using SimpleLogger; namespace MultiShop.Client.Pages { public partial class Search : IAsyncDisposable { + [Inject] + private ILogger Logger { get; set; } [CascadingParameter] Task AuthenticationStateTask { get; set; } @@ -51,14 +52,12 @@ namespace MultiShop.Client.Pages }; if (authState.User.Identity.IsAuthenticated) { - Logger.Log($"User \"{authState.User.Identity.Name}\" is authenticated. Checking for saved profiles.", LogLevel.Debug); + Logger.LogDebug($"User \"{authState.User.Identity.Name}\" is authenticated. Checking for saved profiles."); HttpResponseMessage searchProfileResponse = await Http.GetAsync("Profile/Search"); if (searchProfileResponse.IsSuccessStatusCode) { activeSearchProfile = await searchProfileResponse.Content.ReadFromJsonAsync(); - Logger.Log("Received: " + await searchProfileResponse.Content.ReadAsStringAsync()); - Logger.Log("Serialized then deserialized: " + JsonSerializer.Serialize(activeSearchProfile)); } else { - Logger.Log("Could not load search profile from server. Using default.", LogLevel.Warning); + Logger.LogWarning("Could not load search profile from server. Using default."); activeSearchProfile = new SearchProfile(); } @@ -66,7 +65,7 @@ namespace MultiShop.Client.Pages if (resultsProfileResponse.IsSuccessStatusCode) { activeResultsProfile = await resultsProfileResponse.Content.ReadFromJsonAsync(); } else { - Logger.Log("Could not load results profile from server.", LogLevel.Debug); + Logger.LogWarning("Could not load results profile from server. Using default."); activeResultsProfile = new ResultsProfile(); } } else { @@ -90,7 +89,7 @@ namespace MultiShop.Client.Pages if (string.IsNullOrWhiteSpace(query)) return; if (status.Searching) return; status.Searching = true; - Logger.Log($"Received search request for \"{query}\".", LogLevel.Debug); + Logger.LogDebug($"Received search request for \"{query}\"."); resultsChecked = 0; listings.Clear(); Dictionary> greatest = new Dictionary= activeSearchProfile.MaxResults) break; } - Logger.Log($"\"{shopName}\" has completed. There are {listings.Count} results in total.", LogLevel.Debug); + Logger.LogDebug($"\"{shopName}\" has completed. There are {listings.Count} results in total."); } else { - Logger.Log($"Skipping {shopName} since it's disabled."); + Logger.LogDebug($"Skipping {shopName} since it's disabled."); } } status.Searching = false; diff --git a/src/MultiShop/Client/Program.cs b/src/MultiShop/Client/Program.cs index b579d30..f4fea17 100644 --- a/src/MultiShop/Client/Program.cs +++ b/src/MultiShop/Client/Program.cs @@ -2,13 +2,12 @@ using System; using System.Net.Http; using System.Collections.Generic; using System.Threading.Tasks; -using System.Text; using Microsoft.AspNetCore.Components.WebAssembly.Authentication; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using SimpleLogger; using System.Net.Http.Json; +using Microsoft.Extensions.Logging; namespace MultiShop.Client { @@ -16,18 +15,17 @@ namespace MultiShop.Client { public static async Task Main(string[] args) { - Logger.AddLogListener(new ConsoleLogReceiver() {Level = LogLevel.Debug}); var builder = WebAssemblyHostBuilder.CreateDefault(args); + builder.Logging.AddConfiguration(builder.Configuration.GetSection("Logging")); + builder.RootComponents.Add("#app"); builder.Services.AddHttpClient("MultiShop.ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)).AddHttpMessageHandler(); - builder.Services.AddScoped(sp => sp.GetRequiredService().CreateClient("MultiShop.ServerAPI")); + Action configureClient = client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress); - builder.Services.AddHttpClient("Public-MultiShop.ServerAPI", configureClient); - IReadOnlyDictionary webApiConfig = null; using (HttpClient client = new HttpClient()) { @@ -37,6 +35,7 @@ namespace MultiShop.Client builder.Configuration.AddInMemoryCollection(webApiConfig); + builder.Services.AddApiAuthorization(); await builder.Build().RunAsync(); diff --git a/src/MultiShop/Client/Shared/DragAndDropList.razor b/src/MultiShop/Client/Shared/DragAndDropList.razor index 59a776f..d76c035 100644 --- a/src/MultiShop/Client/Shared/DragAndDropList.razor +++ b/src/MultiShop/Client/Shared/DragAndDropList.razor @@ -1,4 +1,3 @@ -@using SimpleLogger @typeparam TItem @inject IJSRuntime JS diff --git a/src/MultiShop/Client/_Imports.razor b/src/MultiShop/Client/_Imports.razor index cd5796e..45da351 100644 --- a/src/MultiShop/Client/_Imports.razor +++ b/src/MultiShop/Client/_Imports.razor @@ -9,5 +9,4 @@ @using Microsoft.JSInterop @using MultiShop.Client @using MultiShop.Client.Shared -@using Shop.Framework -@using SimpleLogger \ No newline at end of file +@using MultiShop.Shop.Framework \ No newline at end of file diff --git a/src/MultiShop/Client/wwwroot/appsettings.Development.json b/src/MultiShop/Client/wwwroot/appsettings.Development.json new file mode 100644 index 0000000..974c736 --- /dev/null +++ b/src/MultiShop/Client/wwwroot/appsettings.Development.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information", + "MultiShop": "Debug" + } + } +} \ No newline at end of file diff --git a/src/MultiShop/Client/wwwroot/appsettings.json b/src/MultiShop/Client/wwwroot/appsettings.json new file mode 100644 index 0000000..1739421 --- /dev/null +++ b/src/MultiShop/Client/wwwroot/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} \ No newline at end of file diff --git a/src/MultiShop/Client/wwwroot/favicon.ico b/src/MultiShop/Client/wwwroot/favicon.ico deleted file mode 100644 index 63e859b476eff5055e0e557aaa151ca8223fbeef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5430 zcmc&&Yj2xp8Fqnv;>&(QB_ve7>^E#o2mu=cO~A%R>DU-_hfbSRv1t;m7zJ_AMrntN zy0+^f&8be>q&YYzH%(88lQ?#KwiCzaCO*ZEo%j&v;<}&Lj_stKTKK>#U3nin@AF>w zb3ONSAFR{u(S1d?cdw53y}Gt1b-Hirbh;;bm(Rcbnoc*%@jiaXM|4jU^1WO~`TYZ~ zC-~jh9~b-f?fX`DmwvcguQzn*uV}c^Vd&~?H|RUs4Epv~gTAfR(B0lT&?RWQOtduM z^1vUD9{HQsW!{a9|0crA34m7Z6lpG^}f6f?={zD+ zXAzk^i^aKN_}s2$eX81wjSMONE#WVdzf|MT)Ap*}Vsn!XbvsI#6o&ij{87^d%$|A{ z=F{KB%)g%@z76yBzbb7seW**Ju8r4e*Z3PWNX3_tTDgzZatz7)Q6ytwB%@&@A|XT; zecM`Snxx5po$C)%yCP!KEtos~eOS)@2=kX-RIm)4glMCoagTEFxrBeSX%Euz734Fk z%7)x(k~T!@Hbg_37NSQL!vlTBXoURSzt~I**Zw`&F24fH*&kx=%nvZv|49SC*daD( zIw<~%#=lk8{2-l(BcIjy^Q$Q&m#KlWL9?UG{b8@qhlD z;umc+6p%|NsAT~0@DgV4-NKgQuWPWrmPIK&&XhV&n%`{l zOl^bbWYjQNuVXTXESO)@|iUKVmErPUDfz2Wh`4dF@OFiaCW|d`3paV^@|r^8T_ZxM)Z+$p5qx# z#K=z@%;aBPO=C4JNNGqVv6@UGolIz;KZsAro``Rz8X%vq_gpi^qEV&evgHb_=Y9-l z`)imdx0UC>GWZYj)3+3aKh?zVb}=@%oNzg7a8%kfVl)SV-Amp1Okw&+hEZ3|v(k8vRjXW9?ih`&FFM zV$~{j3IzhtcXk?Mu_!12;=+I7XK-IR2>Yd%VB^?oI9c^E&Chb&&je$NV0P-R;ujkP z;cbLCCPEF6|22NDj=S`F^2e~XwT1ZnRX8ra0#DaFa9-X|8(xNW_+JhD75WnSd7cxo z2>I_J5{c|WPfrgl7E2R)^c}F7ry()Z>$Jhk9CzZxiPKL#_0%`&{MX>P_%b~Dx0D^S z7xP1(DQ!d_Icpk!RN3I1w@~|O1ru#CO==h#9M~S4Chx*@?=EKUPGBv$tmU+7Zs_al z`!jR?6T&Z7(%uVq>#yLu`abWk!FBlnY{RFNHlj~6zh*;@u}+}viRKsD`IIxN#R-X3 z@vxu#EA_m}I503U(8Qmx^}u;)KfGP`O9E1H1Q|xeeksX8jC%@!{YT1)!lWgO=+Y3*jr=iSxvOW1}^HSy=y){tOMQJ@an>sOl4FYniE z;GOxd7AqxZNbYFNqobpv&HVO$c-w!Y*6r;$2oJ~h(a#(Bp<-)dg*mNigX~9rPqcHv z^;c*|Md?tD)$y?6FO$DWl$jUGV`F1G_^E&E>sY*YnA~ruv3=z9F8&&~Xpm<<75?N3 z>x~`I&M9q)O1=zWZHN9hZWx>RQ}zLP+iL57Q)%&_^$Sme^^G7;e-P~CR?kqU#Io#( z(nH1Wn*Ig)|M>WLGrxoU?FZrS`4GO&w;+39A3f8w{{Q7eg|$+dIlNFPAe+tN=FOYU z{A&Fg|H73+w1IK(W=j*L>JQgz$g0 z7JpKXLHIh}#$wm|N`s}o-@|L_`>*(gTQ~)wr3Eap7g%PVNisKw82im;Gdv#85x#s+ zoqqtnwu4ycd>cOQgRh-=aEJbnvVK`}ja%+FZx}&ehtX)n(9nVfe4{mn0bgijUbNr7Tf5X^$*{qh2%`?--%+sbSrjE^;1e3>% zqa%jdY16{Y)a1hSy*mr0JGU05Z%=qlx5vGvTjSpTt6k%nR06q}1DU`SQh_ZAeJ}A@`hL~xvv05U?0%=spP`R>dk?cOWM9^KNb7B?xjex>OZo%JMQQ1Q zB|q@}8RiP@DWn-(fB;phPaIOP2Yp)XN3-Fsn)S3w($4&+p8f5W_f%gac}QvmkHfCj$2=!t`boCvQ zCW;&Dto=f8v##}dy^wg3VNaBy&kCe3N;1|@n@pUaMPT?(aJ9b*(gJ28$}(2qFt$H~u5z94xcIQkcOI++)*exzbrk?WOOOf*|%k5#KV zL=&ky3)Eirv$wbRJ2F2s_ILQY--D~~7>^f}W|Aw^e7inXr#WLI{@h`0|jHud2Y~cI~Yn{r_kU^Vo{1gja
- Loading app... + Loading...
diff --git a/src/MultiShop/Server/Controllers/ShopModuleController.cs b/src/MultiShop/Server/Controllers/ShopModuleController.cs index 5be2c2e..c936da9 100644 --- a/src/MultiShop/Server/Controllers/ShopModuleController.cs +++ b/src/MultiShop/Server/Controllers/ShopModuleController.cs @@ -2,10 +2,11 @@ using System; using System.Collections.Generic; using System.IO; using System.Reflection; +using Castle.Core.Logging; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; using MultiShop.Server.Options; -using SimpleLogger; namespace MultiShop.Server.Controllers { @@ -13,13 +14,15 @@ namespace MultiShop.Server.Controllers [Route("[controller]")] public class ShopModuleController : ControllerBase { + private readonly ILogger logger; private readonly IConfiguration configuration; private IDictionary shopModules; private IDictionary shopModuleDependencies; - public ShopModuleController(IConfiguration configuration) + public ShopModuleController(IConfiguration configuration, ILogger logger) { + this.logger = logger; this.configuration = configuration; this.shopModules = new Dictionary(); this.shopModuleDependencies = new Dictionary(); @@ -33,10 +36,10 @@ namespace MultiShop.Server.Controllers shopModules.Add(assemblyName.FullName, System.IO.File.ReadAllBytes(file)); } catch (BadImageFormatException e) { - Logger.Log($"\"{e.FileName}\" is not a valid assembly. Ignoring.", LogLevel.Warning); + logger.LogWarning($"\"{e.FileName}\" is not a valid assembly. Ignoring."); } catch (ArgumentException) { - Logger.Log($"\"{Path.GetFileName(file)}\" has the same full name as another assembly. Ignoring this one.", LogLevel.Warning); + logger.LogWarning($"\"{Path.GetFileName(file)}\" has the same full name as another assembly. Ignoring this one."); } } @@ -48,10 +51,10 @@ namespace MultiShop.Server.Controllers shopModuleDependencies.Add(assemblyName.FullName, System.IO.File.ReadAllBytes(file)); } catch (BadImageFormatException e) { - Logger.Log($"\"{e.FileName}\" is not a valid assembly. Ignoring.", LogLevel.Warning); + logger.LogWarning($"\"{e.FileName}\" is not a valid assembly. Ignoring."); } catch (ArgumentException) { - Logger.Log($"\"{Path.GetFileName(file)}\" has the same full name as another assembly. Ignoring this one.", LogLevel.Warning); + logger.LogWarning($"\"{Path.GetFileName(file)}\" has the same full name as another assembly. Ignoring this one."); } } } diff --git a/src/MultiShop/Server/appsettings.Development.json b/src/MultiShop/Server/appsettings.Development.json index 3203254..c1a1986 100644 --- a/src/MultiShop/Server/appsettings.Development.json +++ b/src/MultiShop/Server/appsettings.Development.json @@ -3,7 +3,8 @@ "LogLevel": { "Default": "Information", "Microsoft": "Warning", - "Microsoft.Hosting.Lifetime": "Information" + "Microsoft.Hosting.Lifetime": "Information", + "MultiShop": "Debug" } }, "IdentityServer": { From 3611e4be34acd1cc9a812ad1ffa9fc8285fa262a Mon Sep 17 00:00:00 2001 From: Harrison Deng Date: Wed, 26 May 2021 15:12:30 -0500 Subject: [PATCH 031/167] Updated database reset script to work from any directory. --- scripts/reset_db.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/scripts/reset_db.py b/scripts/reset_db.py index 1e24156..b2b925d 100644 --- a/scripts/reset_db.py +++ b/scripts/reset_db.py @@ -1,13 +1,16 @@ import os import shutil -SERVER_DIR = "../src/MultiShop/Server" + +SERVER_DIR = "src/MultiShop/Server" DATA_DIR = "Data" DB_MIGRATE_CMD = "dotnet ef migrations add InitialCreate -o {0}" DB_UPDATE_CMD = "dotnet ef database update" -print("Changing to database migrations directory.") +os.chdir(os.path.dirname(os.path.realpath(__file__))) +os.chdir("..") os.chdir(SERVER_DIR) +print("Working in: " + os.getcwd()) migrationsDir = os.path.join(DATA_DIR, "Migrations") From 2d1f599bbf23d668355716da67b33268fa879596 Mon Sep 17 00:00:00 2001 From: Harrison Deng Date: Wed, 26 May 2021 16:55:40 -0500 Subject: [PATCH 032/167] Decided to remove info page. --- src/MultiShop/Client/Pages/Info.razor | 2 -- src/MultiShop/Client/Pages/Info.razor.cs | 12 ------------ 2 files changed, 14 deletions(-) delete mode 100644 src/MultiShop/Client/Pages/Info.razor delete mode 100644 src/MultiShop/Client/Pages/Info.razor.cs diff --git a/src/MultiShop/Client/Pages/Info.razor b/src/MultiShop/Client/Pages/Info.razor deleted file mode 100644 index c9d4544..0000000 --- a/src/MultiShop/Client/Pages/Info.razor +++ /dev/null @@ -1,2 +0,0 @@ -@page "/info" - diff --git a/src/MultiShop/Client/Pages/Info.razor.cs b/src/MultiShop/Client/Pages/Info.razor.cs deleted file mode 100644 index d53bc88..0000000 --- a/src/MultiShop/Client/Pages/Info.razor.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections.Generic; -using Microsoft.AspNetCore.Components; -using MultiShop.Shop.Framework; - -namespace MultiShop.Client.Pages -{ - public partial class Info - { - [CascadingParameter(Name = "Shops")] - public Dictionary Shops { get; set; } - } -} \ No newline at end of file From 3c63ebc6135c4a7c1a1ac42d500c4e1a481f5984 Mon Sep 17 00:00:00 2001 From: Harrison Deng Date: Wed, 26 May 2021 16:57:45 -0500 Subject: [PATCH 033/167] Moved search bar into separate component and fixed results display. --- src/MultiShop/Client/Listing/ListingView.cs | 5 --- src/MultiShop/Client/Listing/TableView.razor | 4 -- src/MultiShop/Client/Pages/Search.razor | 18 +++++--- src/MultiShop/Client/Pages/Search.razor.cs | 43 ++++++++++++-------- src/MultiShop/Client/Shared/SearchBar.razor | 26 ++++++++++++ 5 files changed, 64 insertions(+), 32 deletions(-) create mode 100644 src/MultiShop/Client/Shared/SearchBar.razor diff --git a/src/MultiShop/Client/Listing/ListingView.cs b/src/MultiShop/Client/Listing/ListingView.cs index bbe13ec..2a01f08 100644 --- a/src/MultiShop/Client/Listing/ListingView.cs +++ b/src/MultiShop/Client/Listing/ListingView.cs @@ -17,10 +17,5 @@ namespace MultiShop.Client.Listing public Search.Status Status { get; set; } private protected abstract string GetCategoryTag(ResultsProfile.Category category); - - public ListingView(Search.Status status) - { - this.Status = status; - } } } \ No newline at end of file diff --git a/src/MultiShop/Client/Listing/TableView.razor b/src/MultiShop/Client/Listing/TableView.razor index 0bcbe02..5c12b08 100644 --- a/src/MultiShop/Client/Listing/TableView.razor +++ b/src/MultiShop/Client/Listing/TableView.razor @@ -75,10 +75,6 @@ @code { public override Views View => Views.Table; - public TableView(Search.Status status) : base(status) - { - } - private protected override string GetCategoryTag(ResultsProfile.Category c) { switch (c) diff --git a/src/MultiShop/Client/Pages/Search.razor b/src/MultiShop/Client/Pages/Search.razor index 5494b71..9aba2a9 100644 --- a/src/MultiShop/Client/Pages/Search.razor +++ b/src/MultiShop/Client/Pages/Search.razor @@ -1,14 +1,15 @@ @page "/search/{Query?}" @using MultiShop.Client.Extensions +@using MultiShop.Client.Listing
- -
- - -
+ + + + +
@if (status.SearchConfiguring) { @@ -238,7 +239,12 @@
@if (listings.Count > 0) { - @listingViews[CurrentView] + @switch (CurrentView) + { + case Views.Table: + + break; + } }
diff --git a/src/MultiShop/Client/Pages/Search.razor.cs b/src/MultiShop/Client/Pages/Search.razor.cs index b349776..0fe2b72 100644 --- a/src/MultiShop/Client/Pages/Search.razor.cs +++ b/src/MultiShop/Client/Pages/Search.razor.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.Extensions.Logging; +using MultiShop.Client.Shared; using MultiShop.Client.Extensions; using MultiShop.Client.Listing; using MultiShop.Shared.Models; @@ -17,6 +18,10 @@ namespace MultiShop.Client.Pages { [Inject] private ILogger Logger { get; set; } + + [Inject] + private HttpClient Http { get; set; } + [CascadingParameter] Task AuthenticationStateTask { get; set; } @@ -26,13 +31,10 @@ namespace MultiShop.Client.Pages [Parameter] public string Query { get; set; } - [Inject] - private HttpClient Http { get; set; } + private SearchBar searchBar; private Status status = new Status(); - private Dictionary listingViews; - private Views CurrentView = Views.Table; private SearchProfile activeSearchProfile; @@ -46,11 +48,7 @@ namespace MultiShop.Client.Pages await base.OnInitializedAsync(); AuthenticationState authState = await AuthenticationStateTask; - - listingViews = new Dictionary() { - {Views.Table, new TableView(status)} - }; - + if (authState.User.Identity.IsAuthenticated) { Logger.LogDebug($"User \"{authState.User.Identity.Name}\" is authenticated. Checking for saved profiles."); HttpResponseMessage searchProfileResponse = await Http.GetAsync("Profile/Search"); @@ -84,10 +82,21 @@ namespace MultiShop.Client.Pages } } + protected override async Task OnAfterRenderAsync(bool firstRender) { + await base.OnAfterRenderAsync(firstRender); + if (firstRender) { + searchBar.Query = Query; + searchBar.Searching = true; + await PerformSearch(Query); + searchBar.Searching = false; + } + } + private async Task PerformSearch(string query) { if (string.IsNullOrWhiteSpace(query)) return; if (status.Searching) return; + SearchProfile searchProfile = activeSearchProfile.DeepCopy(); status.Searching = true; Logger.LogDebug($"Received search request for \"{query}\"."); resultsChecked = 0; @@ -96,10 +105,10 @@ namespace MultiShop.Client.Pages List>(); foreach (string shopName in Shops.Keys) { - if (activeSearchProfile.ShopStates[shopName]) + if (searchProfile.ShopStates[shopName]) { Logger.LogDebug($"Querying \"{shopName}\" for products."); - Shops[shopName].SetupSession(query, activeSearchProfile.Currency); + Shops[shopName].SetupSession(query, searchProfile.Currency); int shopViableResults = 0; await foreach (ProductListing listing in Shops[shopName]) { @@ -111,12 +120,12 @@ namespace MultiShop.Client.Pages } - if (listing.Shipping == null && !activeSearchProfile.KeepUnknownShipping || (activeSearchProfile.EnableMaxShippingFee && listing.Shipping > activeSearchProfile.MaxShippingFee)) continue; + if (listing.Shipping == null && !searchProfile.KeepUnknownShipping || (searchProfile.EnableMaxShippingFee && listing.Shipping > searchProfile.MaxShippingFee)) continue; float shippingDifference = listing.Shipping != null ? listing.Shipping.Value : 0; - if (!(listing.LowerPrice + shippingDifference >= activeSearchProfile.LowerPrice && (!activeSearchProfile.EnableUpperPrice || listing.UpperPrice + shippingDifference <= activeSearchProfile.UpperPrice))) continue; - if ((listing.Rating == null && !activeSearchProfile.KeepUnrated) && activeSearchProfile.MinRating > (listing.Rating == null ? 0 : listing.Rating)) continue; - if ((listing.PurchaseCount == null && !activeSearchProfile.KeepUnknownPurchaseCount) || activeSearchProfile.MinPurchases > (listing.PurchaseCount == null ? 0 : listing.PurchaseCount)) continue; - if ((listing.ReviewCount == null && !activeSearchProfile.KeepUnknownRatingCount) || activeSearchProfile.MinReviews > (listing.ReviewCount == null ? 0 : listing.ReviewCount)) continue; + if (!(listing.LowerPrice + shippingDifference >= searchProfile.LowerPrice && (!searchProfile.EnableUpperPrice || listing.UpperPrice + shippingDifference <= searchProfile.UpperPrice))) continue; + if ((listing.Rating == null && !searchProfile.KeepUnrated) && searchProfile.MinRating > (listing.Rating == null ? 0 : listing.Rating)) continue; + if ((listing.PurchaseCount == null && !searchProfile.KeepUnknownPurchaseCount) || searchProfile.MinPurchases > (listing.PurchaseCount == null ? 0 : listing.PurchaseCount)) continue; + if ((listing.ReviewCount == null && !searchProfile.KeepUnknownRatingCount) || searchProfile.MinReviews > (listing.ReviewCount == null ? 0 : listing.ReviewCount)) continue; ProductListingInfo info = new ProductListingInfo(listing, shopName); listings.Add(info); @@ -142,7 +151,7 @@ namespace MultiShop.Client.Pages } shopViableResults += 1; - if (shopViableResults >= activeSearchProfile.MaxResults) break; + if (shopViableResults >= searchProfile.MaxResults) break; } Logger.LogDebug($"\"{shopName}\" has completed. There are {listings.Count} results in total."); } diff --git a/src/MultiShop/Client/Shared/SearchBar.razor b/src/MultiShop/Client/Shared/SearchBar.razor new file mode 100644 index 0000000..aa21f8c --- /dev/null +++ b/src/MultiShop/Client/Shared/SearchBar.razor @@ -0,0 +1,26 @@ + +
+ @Append + +
+ +@code { + [Parameter] + public RenderFragment Append { get; set; } + + [Parameter] + public string SearchPlaceholder { get; set; } + + public string Query { get; set; } + + [Parameter] + public EventCallback OnSearchRequested { get; set; } + + public bool Searching { get; set; } + public async Task Search() + { + Searching = true; + await OnSearchRequested.InvokeAsync(Query); + Searching = false; + } +} \ No newline at end of file From b311206ff134ef2c825535532d65609683cc296e Mon Sep 17 00:00:00 2001 From: Harrison Deng Date: Wed, 26 May 2021 17:42:55 -0500 Subject: [PATCH 034/167] Moved implement and inject notation to partial class. --- src/MultiShop/Client/App.razor | 7 +------ src/MultiShop/Client/App.razor.cs | 5 ++++- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/MultiShop/Client/App.razor b/src/MultiShop/Client/App.razor index 826065d..7c5979f 100644 --- a/src/MultiShop/Client/App.razor +++ b/src/MultiShop/Client/App.razor @@ -1,9 +1,4 @@ -@using System.Reflection -@inject IHttpClientFactory HttpFactory -@implements IDisposable - - - + @if (modulesLoaded) diff --git a/src/MultiShop/Client/App.razor.cs b/src/MultiShop/Client/App.razor.cs index 8202be7..25aae35 100644 --- a/src/MultiShop/Client/App.razor.cs +++ b/src/MultiShop/Client/App.razor.cs @@ -12,8 +12,11 @@ using MultiShop.Shop.Framework; namespace MultiShop.Client { - public partial class App + public partial class App : IDisposable { + [Inject] + private IHttpClientFactory HttpFactory { get; set; } + [Inject] private ILogger Logger {get; set; } From 7d4be012cdd2838e15e01829c45421b10df3aab1 Mon Sep 17 00:00:00 2001 From: Harrison Deng Date: Mon, 31 May 2021 16:39:52 -0500 Subject: [PATCH 035/167] Implemented application profile and dark mode. Added a specific cascading dependencies component. Added some content to main page. Added configuration page and persistence for it. Code restructured (moved some code into separate components). --- src/MultiShop/Client/App.razor | 48 +++++---- .../Client/Extensions/UIProfileExtensions.cs | 22 +++++ .../ShopModuleLoader.cs} | 84 ++++++++-------- .../Client/Pages/Authentication.razor | 16 ++- .../Client/Pages/Configuration.razor | 55 +++++++++++ .../Client/Pages/Configuration.razor.cs | 67 +++++++++++++ .../Client/Pages/Configuration.razor.css | 48 +++++++++ src/MultiShop/Client/Pages/Index.razor | 14 ++- src/MultiShop/Client/Pages/Search.razor.cs | 55 ++++++++--- src/MultiShop/Client/Program.cs | 13 ++- .../Services/LayoutStateChangeNotifier.cs | 13 +++ .../Client/Shared/AuthenticationDisplay.razor | 27 +++++ .../Client/Shared/CascadingDependencies.razor | 14 +++ .../Shared/CascadingDependencies.razor.cs | 93 ++++++++++++++++++ .../Shared/HorizontalNavMenuTemplate.razor | 85 ++++++++++++++++ .../Client/Shared/HorizontalSiteNav.razor | 42 ++++++++ src/MultiShop/Client/Shared/MainLayout.razor | 55 +++++++++-- .../Client/Shared/MainLayout.razor.css | 15 --- src/MultiShop/Client/Shared/NavMenu.razor | 74 -------------- src/MultiShop/Client/wwwroot/css/app.css | 22 +++++ .../Client/wwwroot/{ => images}/100x100.png | Bin src/MultiShop/Client/wwwroot/index.html | 6 +- .../Server/Controllers/ProfileController.cs | 30 +++++- ... 20210531175621_InitialCreate.Designer.cs} | 38 ++++++- ...ate.cs => 20210531175621_InitialCreate.cs} | 31 ++++++ .../ApplicationDbContextModelSnapshot.cs | 36 +++++++ .../Server/Models/ApplicationUser.cs | 3 + .../Shared/Models/ApplicationProfile.cs | 15 +++ src/MultiShop/Shared/Models/SearchProfile.cs | 8 +- 29 files changed, 836 insertions(+), 193 deletions(-) create mode 100644 src/MultiShop/Client/Extensions/UIProfileExtensions.cs rename src/MultiShop/Client/{App.razor.cs => Module/ShopModuleLoader.cs} (57%) create mode 100644 src/MultiShop/Client/Pages/Configuration.razor create mode 100644 src/MultiShop/Client/Pages/Configuration.razor.cs create mode 100644 src/MultiShop/Client/Pages/Configuration.razor.css create mode 100644 src/MultiShop/Client/Services/LayoutStateChangeNotifier.cs create mode 100644 src/MultiShop/Client/Shared/AuthenticationDisplay.razor create mode 100644 src/MultiShop/Client/Shared/CascadingDependencies.razor create mode 100644 src/MultiShop/Client/Shared/CascadingDependencies.razor.cs create mode 100644 src/MultiShop/Client/Shared/HorizontalNavMenuTemplate.razor create mode 100644 src/MultiShop/Client/Shared/HorizontalSiteNav.razor delete mode 100644 src/MultiShop/Client/Shared/NavMenu.razor rename src/MultiShop/Client/wwwroot/{ => images}/100x100.png (100%) rename src/MultiShop/Server/Data/Migrations/{20210525224658_InitialCreate.Designer.cs => 20210531175621_InitialCreate.Designer.cs} (92%) rename src/MultiShop/Server/Data/Migrations/{20210525224658_InitialCreate.cs => 20210531175621_InitialCreate.cs} (92%) create mode 100644 src/MultiShop/Shared/Models/ApplicationProfile.cs diff --git a/src/MultiShop/Client/App.razor b/src/MultiShop/Client/App.razor index 7c5979f..426024b 100644 --- a/src/MultiShop/Client/App.razor +++ b/src/MultiShop/Client/App.razor @@ -1,9 +1,8 @@  - - - @if (modulesLoaded) - { - + + + + @if (!authState.User.Identity.IsAuthenticated) @@ -16,26 +15,25 @@ } - - } - else - { -
-
-
- Loading... -
-
-
- Downloading shop modules... + + + +

Sorry, there's nothing at this address.

+
+
+ + + +
+
+
+ Loading...
- } - - - -

Sorry, there's nothing at this address.

-
-
- +
+ Loading @Status... +
+
+
+ \ No newline at end of file diff --git a/src/MultiShop/Client/Extensions/UIProfileExtensions.cs b/src/MultiShop/Client/Extensions/UIProfileExtensions.cs new file mode 100644 index 0000000..1b8883a --- /dev/null +++ b/src/MultiShop/Client/Extensions/UIProfileExtensions.cs @@ -0,0 +1,22 @@ +using MultiShop.Shared.Models; + +namespace MultiShop.Client.Extensions +{ + public static class applicationProfileExtensions + { + public static string GetButtonCssClass(this ApplicationProfile applicationProfile, string otherClasses = "", bool outline = false) { + if (outline) { + return otherClasses + (applicationProfile.DarkMode ? " btn btn-outline-light" : " btn btn-outline-dark"); + } + return otherClasses + (applicationProfile.DarkMode ? " btn btn-light" : " btn btn-dark"); + } + + public static string GetPageCssClass(this ApplicationProfile applicationProfile, string otherClasses = "") { + return otherClasses + (applicationProfile.DarkMode ? " text-white bg-dark" : " text-dark bg-white"); + } + + public static string GetNavCssClass(this ApplicationProfile applicationProfile, string otherClasses = "") { + return otherClasses + (applicationProfile.DarkMode ? " navbar-dark bg-dark" : " navbar-light bg-light"); + } + } +} \ No newline at end of file diff --git a/src/MultiShop/Client/App.razor.cs b/src/MultiShop/Client/Module/ShopModuleLoader.cs similarity index 57% rename from src/MultiShop/Client/App.razor.cs rename to src/MultiShop/Client/Module/ShopModuleLoader.cs index 25aae35..21c2342 100644 --- a/src/MultiShop/Client/App.razor.cs +++ b/src/MultiShop/Client/Module/ShopModuleLoader.cs @@ -1,53 +1,48 @@ using System; +using System.Collections; using System.Collections.Generic; -using System.IO; using System.Net.Http; using System.Net.Http.Json; using System.Reflection; using System.Threading.Tasks; -using Microsoft.AspNetCore.Components; using Microsoft.Extensions.Logging; -using MultiShop.Client.Module; using MultiShop.Shop.Framework; -namespace MultiShop.Client +namespace MultiShop.Client.Module { - public partial class App : IDisposable + public class ShopModuleLoader { - [Inject] - private IHttpClientFactory HttpFactory { get; set; } - - [Inject] - private ILogger Logger {get; set; } + private HttpClient http; + private ILogger logger; + private string[] moduleNames; + private string[] dependencyNames; - private bool modulesLoaded = false; - - private Dictionary shops = new Dictionary(); - protected override async Task OnInitializedAsync() + public ShopModuleLoader(HttpClient http, ILogger logger) { - await base.OnInitializedAsync(); - await DownloadShopModules(); + this.http = http; + this.logger = logger; } - private async Task DownloadShopModules() - { - HttpClient http = HttpFactory.CreateClient("Public-MultiShop.ServerAPI"); + private async Task DownloadAssembliesList() { + moduleNames = await http.GetFromJsonAsync("ShopModule/Modules"); + dependencyNames = await http.GetFromJsonAsync("ShopModule/Dependencies"); + } + + private async Task> DownloadShopModuleAssemblies() { Dictionary assemblyData = new Dictionary(); - string[] moduleNames = await http.GetFromJsonAsync("ShopModule/Modules"); - string[] dependencyNames = await http.GetFromJsonAsync("ShopModule/Dependencies"); Dictionary, string> downloadTasks = new Dictionary, string>(); - Logger.LogInformation("Beginning to download shop modules..."); + logger.LogInformation("Beginning to download shop modules..."); foreach (string moduleName in moduleNames) { - Logger.LogDebug($"Downloading shop: {moduleName}"); + logger.LogDebug($"Downloading shop: {moduleName}"); downloadTasks.Add(http.GetByteArrayAsync("shopModule/Modules/" + moduleName), moduleName); } - Logger.LogInformation("Beginning to download shop module dependencies..."); + logger.LogInformation("Beginning to download shop module dependencies..."); foreach (string depName in dependencyNames) { - Logger.LogDebug($"Downloading shop module dependency: {depName}"); + logger.LogDebug($"Downloading shop module dependency: {depName}"); downloadTasks.Add(http.GetByteArrayAsync("ShopModule/Dependencies/" + depName), depName); } @@ -55,16 +50,26 @@ namespace MultiShop.Client { Task downloadTask = await Task.WhenAny(downloadTasks.Keys); assemblyData.Add(downloadTasks[downloadTask], await downloadTask); - Logger.LogDebug($"Shop module \"{downloadTasks[downloadTask]}\" completed downloading."); + logger.LogDebug($"Shop module \"{downloadTasks[downloadTask]}\" completed downloading."); downloadTasks.Remove(downloadTask); } - Logger.LogInformation($"Downloaded {assemblyData.Count} assemblies in total."); + logger.LogInformation($"Downloaded {assemblyData.Count} assemblies in total."); + return assemblyData; + } + + public async Task> GetShops() + { + await DownloadAssembliesList(); + + Dictionary shops = new Dictionary(); + + IReadOnlyDictionary assemblyData = await DownloadShopModuleAssemblies(); ShopModuleLoadContext context = new ShopModuleLoadContext(assemblyData); - Logger.LogInformation("Beginning to load shop modules."); + logger.LogInformation("Beginning to load shop modules."); foreach (string moduleName in moduleNames) { - Logger.LogDebug($"Attempting to load shop module: \"{moduleName}\""); + logger.LogDebug($"Attempting to load shop module: \"{moduleName}\""); Assembly moduleAssembly = context.LoadFromAssemblyName(new AssemblyName(moduleName)); bool shopLoaded = false; foreach (Type type in moduleAssembly.GetTypes()) @@ -75,32 +80,25 @@ namespace MultiShop.Client shopLoaded = true; shop.Initialize(); shops.Add(shop.ShopName, shop); - Logger.LogDebug($"Added shop: {shop.ShopName}"); + logger.LogDebug($"Added shop: {shop.ShopName}"); } } } if (!shopLoaded) { - Logger.LogWarning($"Module \"{moduleName}\" was reported to be a shop module, but did not contain a shop interface. Please report this to the site administrator."); + logger.LogWarning($"Module \"{moduleName}\" was reported to be a shop module, but did not contain a shop interface. Please report this to the site administrator."); } } - Logger.LogInformation($"Shop module loading complete. Loaded a total of {shops.Count} shops."); - modulesLoaded = true; + logger.LogInformation($"Shop module loading complete. Loaded a total of {shops.Count} shops."); foreach (string assemblyName in context.UseCounter.Keys) { int usage = context.UseCounter[assemblyName]; - Logger.LogDebug($"\"{assemblyName}\" was used {usage} times."); + logger.LogDebug($"\"{assemblyName}\" was used {usage} times."); if (usage <= 0) { - Logger.LogWarning($"\"{assemblyName}\" was not used. Please report this to the site administrator."); + logger.LogWarning($"\"{assemblyName}\" was not used. Please report this to the site administrator."); } } - } - - public void Dispose() - { - foreach (string name in shops.Keys) - { - shops[name].Dispose(); - } + + return shops; } } } \ No newline at end of file diff --git a/src/MultiShop/Client/Pages/Authentication.razor b/src/MultiShop/Client/Pages/Authentication.razor index d201654..8031540 100644 --- a/src/MultiShop/Client/Pages/Authentication.razor +++ b/src/MultiShop/Client/Pages/Authentication.razor @@ -5,7 +5,7 @@ -
+
Loading... @@ -17,7 +17,7 @@
-
+
Loading... @@ -28,6 +28,18 @@
+ +
+
+
+ Loading... +
+
+
+ Hang on just a sec. We're logging you in! +
+
+
@code{ diff --git a/src/MultiShop/Client/Pages/Configuration.razor b/src/MultiShop/Client/Pages/Configuration.razor new file mode 100644 index 0000000..e1f82ee --- /dev/null +++ b/src/MultiShop/Client/Pages/Configuration.razor @@ -0,0 +1,55 @@ +@using MultiShop.Client.Extensions +@page "/configure" + +
+ +
+ @switch (currentSection) + { + case Section.Opening: +

Configuration

+ For all your control-asserting needs. +

You can change how the app looks and operates. These changes will actually be saved to your account if you're logged in so your changes will be with you across different devices! Otherwise, we'll just stash them in cookies. Also know for your convenience, each option is followed by a short description of what changing that option will do. Get started by selecting a section!

+ break; + case Section.UI: +

UI

+
+
+ + +
+

Changes the UI to a dark theme. Pretty self-explanatory.

+
+ break; + case Section.Search: +

Search

+
+
+ + +
+

We will store results from commonly searched queries to reproduce repeated searches faster. to make sure prices are relevant, queries older than a few minutes will be removed.

+
+
+ + +
+ break; + } +
+
+ diff --git a/src/MultiShop/Client/Pages/Configuration.razor.cs b/src/MultiShop/Client/Pages/Configuration.razor.cs new file mode 100644 index 0000000..5f630ab --- /dev/null +++ b/src/MultiShop/Client/Pages/Configuration.razor.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Json; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.Extensions.Logging; +using MultiShop.Client.Services; +using MultiShop.Shared.Models; + +namespace MultiShop.Client.Pages +{ + public partial class Configuration : IAsyncDisposable + { + [Inject] + private ILogger Logger { get; set; } + + [Inject] + private LayoutStateChangeNotifier LayoutStateChangeNotifier { get; set; } + + [Inject] + private HttpClient Http { get; set; } + + [CascadingParameter] + private Task AuthenticationStateTask { get; set; } + + [CascadingParameter(Name = "ApplicationProfile")] + private ApplicationProfile ApplicationProfile { get; set; } + + private bool collapseNavMenu; + private string NavMenuCssClass => (collapseNavMenu ? "collapse" : ""); + + private enum Section + { + Opening, UI, Search + } + + private Section currentSection = Section.Opening; + + private Dictionary sectionNames = new Dictionary() { + {Section.Opening, "Info"}, + {Section.UI, "UI"}, + {Section.Search, "Search"} + }; + + private List
sectionOrder = new List
() { + Section.Opening, + Section.UI, + Section.Search + }; + + private string GetNavItemCssClass(Section section) + { + return "nav-item" + ((section == currentSection) ? " active" : null); + } + + public async ValueTask DisposeAsync() + { + AuthenticationState authenticationState = await AuthenticationStateTask; + if (authenticationState.User.Identity.IsAuthenticated) { + Logger.LogDebug($"User is authenticated. Attempting to save configuration to server."); + await Http.PutAsJsonAsync("Profile/Application", ApplicationProfile); + } + } + } +} \ No newline at end of file diff --git a/src/MultiShop/Client/Pages/Configuration.razor.css b/src/MultiShop/Client/Pages/Configuration.razor.css new file mode 100644 index 0000000..fece144 --- /dev/null +++ b/src/MultiShop/Client/Pages/Configuration.razor.css @@ -0,0 +1,48 @@ +nav { + position: static; + padding: 0.5rem; + height: fit-content; +} + +nav .nav-toggler { + display: none; + border-style: none; + padding: 0px; + background-color: transparent; +} + +.collapse { + display: block; +} + +.navbar-light .nav-item.active .btn { + color: black; +} + +.nav .nav-item .btn { + color: gray; +} + +.navbar-dark .nav-item.active .btn { + color: white; +} + +@media (max-width: 50rem) { + nav .nav-toggler { + display: block; + } + + nav { + position: fixed; + padding: 1rem; + bottom: 1rem; + left: 1rem; + border-radius: 12px; + } + + .collapse { + display: none; + } + + +} \ No newline at end of file diff --git a/src/MultiShop/Client/Pages/Index.razor b/src/MultiShop/Client/Pages/Index.razor index 032f142..48640f6 100644 --- a/src/MultiShop/Client/Pages/Index.razor +++ b/src/MultiShop/Client/Pages/Index.razor @@ -1,4 +1,14 @@ @page "/" -@* TODO: Add main page content.*@ -

Welcome to MultiShop!

\ No newline at end of file +
+

Welcome to MultiShop!

+
+
+

What is MultiShop?

+

MultiShop is an app that allows users to easily check multiple online retailers, primarily for electronic components. On request, the app searches, aggregates and sorts all the most important information about the products it finds while searching through sites for you! MultiShop is intended to be a small light-weight app the runs on the clients computer. Therefore, all actions and processes are actually reliant on your computer. Additionally, MultiShop does not crawl sites and save their products like a typical search engine. To provide the latest prices, all searches are performed (near) live on command.

+
+ +
+

How does it work?

+

MultiShop is a simple program that takes care of looking through product listings for you. That's all. As all tasks are executed locally on your computer MultiShop is like any other program, except that it is transient, and automatically loaded when you visit this website. The shop has a variety of modules meant to search different shops by essentially querying the shop as the user would normally, and then scanning the results.

+
diff --git a/src/MultiShop/Client/Pages/Search.razor.cs b/src/MultiShop/Client/Pages/Search.razor.cs index 0fe2b72..da5a9da 100644 --- a/src/MultiShop/Client/Pages/Search.razor.cs +++ b/src/MultiShop/Client/Pages/Search.razor.cs @@ -6,9 +6,10 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.Extensions.Logging; -using MultiShop.Client.Shared; using MultiShop.Client.Extensions; using MultiShop.Client.Listing; +using MultiShop.Client.Services; +using MultiShop.Client.Shared; using MultiShop.Shared.Models; using MultiShop.Shop.Framework; @@ -16,6 +17,9 @@ namespace MultiShop.Client.Pages { public partial class Search : IAsyncDisposable { + [Inject] + private LayoutStateChangeNotifier LayoutStateChangeNotifier { get; set; } + [Inject] private ILogger Logger { get; set; } @@ -23,10 +27,10 @@ namespace MultiShop.Client.Pages private HttpClient Http { get; set; } [CascadingParameter] - Task AuthenticationStateTask { get; set; } + private Task AuthenticationStateTask { get; set; } [CascadingParameter(Name = "Shops")] - public Dictionary Shops { get; set; } + public IReadOnlyDictionary Shops { get; set; } [Parameter] public string Query { get; set; } @@ -43,30 +47,45 @@ namespace MultiShop.Client.Pages private List listings = new List(); private int resultsChecked = 0; + protected override void OnInitialized() + { + base.OnInitialized(); + LayoutStateChangeNotifier.Notify += UpdateState; + } + protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); AuthenticationState authState = await AuthenticationStateTask; - - if (authState.User.Identity.IsAuthenticated) { + + if (authState.User.Identity.IsAuthenticated) + { Logger.LogDebug($"User \"{authState.User.Identity.Name}\" is authenticated. Checking for saved profiles."); HttpResponseMessage searchProfileResponse = await Http.GetAsync("Profile/Search"); - if (searchProfileResponse.IsSuccessStatusCode) { + if (searchProfileResponse.IsSuccessStatusCode) + { activeSearchProfile = await searchProfileResponse.Content.ReadFromJsonAsync(); - } else { + } + else + { Logger.LogWarning("Could not load search profile from server. Using default."); activeSearchProfile = new SearchProfile(); } HttpResponseMessage resultsProfileResponse = await Http.GetAsync("Profile/Results"); - if (resultsProfileResponse.IsSuccessStatusCode) { + if (resultsProfileResponse.IsSuccessStatusCode) + { activeResultsProfile = await resultsProfileResponse.Content.ReadFromJsonAsync(); - } else { + } + else + { Logger.LogWarning("Could not load results profile from server. Using default."); activeResultsProfile = new ResultsProfile(); } - } else { + } + else + { activeSearchProfile = new SearchProfile(); activeResultsProfile = new ResultsProfile(); } @@ -82,9 +101,11 @@ namespace MultiShop.Client.Pages } } - protected override async Task OnAfterRenderAsync(bool firstRender) { + protected override async Task OnAfterRenderAsync(bool firstRender) + { await base.OnAfterRenderAsync(firstRender); - if (firstRender) { + if (firstRender) + { searchBar.Query = Query; searchBar.Searching = true; await PerformSearch(Query); @@ -247,13 +268,21 @@ namespace MultiShop.Client.Pages StateHasChanged(); } + private async Task UpdateState() { + await InvokeAsync(() => { + StateHasChanged(); + }); + } + public async ValueTask DisposeAsync() { AuthenticationState authState = await AuthenticationStateTask; - if (authState.User.Identity.IsAuthenticated) { + if (authState.User.Identity.IsAuthenticated) + { await Http.PutAsJsonAsync("Profile/Search", activeSearchProfile); await Http.PutAsJsonAsync("Profile/Results", activeResultsProfile); } + LayoutStateChangeNotifier.Notify -= UpdateState; } public class Status diff --git a/src/MultiShop/Client/Program.cs b/src/MultiShop/Client/Program.cs index f4fea17..062f231 100644 --- a/src/MultiShop/Client/Program.cs +++ b/src/MultiShop/Client/Program.cs @@ -8,6 +8,9 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using System.Net.Http.Json; using Microsoft.Extensions.Logging; +using MultiShop.Client.Module; +using MultiShop.Shop.Framework; +using MultiShop.Client.Services; namespace MultiShop.Client { @@ -15,7 +18,9 @@ namespace MultiShop.Client { public static async Task Main(string[] args) { + var builder = WebAssemblyHostBuilder.CreateDefault(args); + builder.Logging.AddConfiguration(builder.Configuration.GetSection("Logging")); builder.RootComponents.Add("#app"); @@ -26,15 +31,13 @@ namespace MultiShop.Client Action configureClient = client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress); builder.Services.AddHttpClient("Public-MultiShop.ServerAPI", configureClient); - IReadOnlyDictionary webApiConfig = null; using (HttpClient client = new HttpClient()) { - configureClient.Invoke(client); - webApiConfig = await client.GetFromJsonAsync>("PublicApiSettings"); + configureClient.Invoke(client); + builder.Configuration.AddInMemoryCollection(await client.GetFromJsonAsync>("PublicApiSettings")); } - builder.Configuration.AddInMemoryCollection(webApiConfig); - + builder.Services.AddSingleton(); builder.Services.AddApiAuthorization(); diff --git a/src/MultiShop/Client/Services/LayoutStateChangeNotifier.cs b/src/MultiShop/Client/Services/LayoutStateChangeNotifier.cs new file mode 100644 index 0000000..fe0456b --- /dev/null +++ b/src/MultiShop/Client/Services/LayoutStateChangeNotifier.cs @@ -0,0 +1,13 @@ +using System; +using System.Threading.Tasks; + +namespace MultiShop.Client.Services +{ + public class LayoutStateChangeNotifier + { + public event Func Notify; + public async Task LayoutHasChanged() { + await Notify?.Invoke(); + } + } +} \ No newline at end of file diff --git a/src/MultiShop/Client/Shared/AuthenticationDisplay.razor b/src/MultiShop/Client/Shared/AuthenticationDisplay.razor new file mode 100644 index 0000000..805f1d7 --- /dev/null +++ b/src/MultiShop/Client/Shared/AuthenticationDisplay.razor @@ -0,0 +1,27 @@ +@using Microsoft.Extensions.Configuration +@using Microsoft.AspNetCore.Components.WebAssembly.Authentication +@inject NavigationManager Navigation +@inject SignOutSessionStateManager SignOutManager +@inject IConfiguration Configuration + + + + Hello, @auth.User.Identity.Name! + + + + @if (Configuration["IdentityServer:Registration"].Equals("enabled")) + { + Register + } + Log in + + + +@code { + private async Task BeginSignOut(MouseEventArgs args) + { + await SignOutManager.SetSignOutState(); + Navigation.NavigateTo("authentication/logout"); + } +} \ No newline at end of file diff --git a/src/MultiShop/Client/Shared/CascadingDependencies.razor b/src/MultiShop/Client/Shared/CascadingDependencies.razor new file mode 100644 index 0000000..27f77a6 --- /dev/null +++ b/src/MultiShop/Client/Shared/CascadingDependencies.razor @@ -0,0 +1,14 @@ +@using Microsoft.Extensions.Logging +@using Module +@using MultiShop.Shared.Models + +@if (loadingDisplay == null) +{ + + + @Content + + +} else { + @LoadingContent(loadingDisplay) +} \ No newline at end of file diff --git a/src/MultiShop/Client/Shared/CascadingDependencies.razor.cs b/src/MultiShop/Client/Shared/CascadingDependencies.razor.cs new file mode 100644 index 0000000..7be6426 --- /dev/null +++ b/src/MultiShop/Client/Shared/CascadingDependencies.razor.cs @@ -0,0 +1,93 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Json; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.Extensions.Logging; +using MultiShop.Client.Module; +using MultiShop.Shared.Models; +using MultiShop.Shop.Framework; + +namespace MultiShop.Client.Shared +{ + public partial class CascadingDependencies : IDisposable + { + [Inject] + private ILogger Logger { get; set; } + + [Inject] + private IHttpClientFactory HttpClientFactory { get; set; } + + [CascadingParameter] + private Task AuthenticationStateTask { get; set; } + + [Parameter] + public RenderFragment LoadingContent { get; set; } + + [Parameter] + public RenderFragment Content { get; set; } + + private bool disposedValue; + + private string loadingDisplay; + + private IReadOnlyDictionary shops; + + private ApplicationProfile applicationProfile; + + protected override async Task OnInitializedAsync() + { + loadingDisplay = ""; + await base.OnInitializedAsync(); + await DownloadShops(HttpClientFactory.CreateClient("Public-MultiShop.ServerAPI")); + await DownloadApplicationProfile(HttpClientFactory.CreateClient("MultiShop.ServerAPI")); + loadingDisplay = null; + } + + private async Task DownloadShops(HttpClient http) + { + loadingDisplay = "shops"; + ShopModuleLoader loader = new ShopModuleLoader(http, Logger); + shops = await loader.GetShops(); + } + + private async Task DownloadApplicationProfile(HttpClient http) + { + loadingDisplay = "profile"; + AuthenticationState authState = await AuthenticationStateTask; + if (authState.User.Identity.IsAuthenticated) + { + Logger.LogDebug($"User is logged in. Attempting to fetch application profile."); + HttpResponseMessage response = await http.GetAsync("Profile/Application"); + if (response.IsSuccessStatusCode) + { + applicationProfile = await response.Content.ReadFromJsonAsync(); + } + } + if (applicationProfile == null) applicationProfile = new ApplicationProfile(); + } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + foreach (string shopName in shops.Keys) + { + shops[shopName].Dispose(); + } + } + disposedValue = true; + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} \ No newline at end of file diff --git a/src/MultiShop/Client/Shared/HorizontalNavMenuTemplate.razor b/src/MultiShop/Client/Shared/HorizontalNavMenuTemplate.razor new file mode 100644 index 0000000..fe67739 --- /dev/null +++ b/src/MultiShop/Client/Shared/HorizontalNavMenuTemplate.razor @@ -0,0 +1,85 @@ +@using MultiShop.Client.Extensions +@using MultiShop.Shared.Models +@using MultiShop.Client.Services +@implements IDisposable +@inject LayoutStateChangeNotifier LayoutStateChangeNotifier + + + +@code { + [CascadingParameter(Name = "ApplicationProfile")] + private ApplicationProfile ApplicationProfile { get; set; } + + private bool collapseNavMenu = true; + + private string NavMenuCssClass => (collapseNavMenu ? "collapse " : " ") + "navbar-collapse"; + + [Parameter] + public IList Items { get; set; } + + [Parameter] + public RenderFragment BrandContent { get; set; } + + [Parameter] + public RenderFragment ItemTemplate { get; set; } + + [Parameter] + public RenderFragment LatterContent { get; set; } + private bool disposed; + + private void ToggleNavMenu() + { + collapseNavMenu = !collapseNavMenu; + } + + protected override void OnInitialized() + { + base.OnInitialized(); + LayoutStateChangeNotifier.Notify += UpdateState; + } + + private async Task UpdateState() { + await InvokeAsync(() => { + StateHasChanged(); + }); + } + + protected virtual void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) + { + } + LayoutStateChangeNotifier.Notify -= UpdateState; + disposed = true; + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } +} diff --git a/src/MultiShop/Client/Shared/HorizontalSiteNav.razor b/src/MultiShop/Client/Shared/HorizontalSiteNav.razor new file mode 100644 index 0000000..62a7fa6 --- /dev/null +++ b/src/MultiShop/Client/Shared/HorizontalSiteNav.razor @@ -0,0 +1,42 @@ +@using Microsoft.AspNetCore.Components.WebAssembly.Authentication +@using Microsoft.Extensions.Configuration + +@inject NavigationManager Navigation +@inject SignOutSessionStateManager SignOutManager +@inject IConfiguration Configuration + + + + + MultiShop + + + @PlaceNames[place] + + + + + + +@code { + private bool collapseNavMenu = true; + + private string NavMenuCssClass => (collapseNavMenu ? "collapse " : " ") + "navbar-collapse"; + + private void ToggleNavMenu() + { + collapseNavMenu = !collapseNavMenu; + } + + private Dictionary PlaceNames = new Dictionary() { + {"","Home"}, + {"search", "Search"}, + {"configure", "Options"} + }; + + private List PlaceOrder = new List() { + "", + "search", + "configure" + }; +} diff --git a/src/MultiShop/Client/Shared/MainLayout.razor b/src/MultiShop/Client/Shared/MainLayout.razor index f7def2c..e7c668b 100644 --- a/src/MultiShop/Client/Shared/MainLayout.razor +++ b/src/MultiShop/Client/Shared/MainLayout.razor @@ -1,10 +1,51 @@ +@using MultiShop.Client.Extensions +@using MultiShop.Shared.Models +@using MultiShop.Client.Services @inherits LayoutComponentBase +@implements IDisposable +@inject LayoutStateChangeNotifier LayoutStateChangeNotifier -
- -
-
- @Body -
+
+ +
+ @Body
-
\ No newline at end of file +
+ +@code { + [CascadingParameter(Name = "ApplicationProfile")] + private ApplicationProfile ApplicationProfile { get; set; } + private bool disposed; + + protected override void OnInitialized() + { + base.OnInitialized(); + LayoutStateChangeNotifier.Notify += UpdateState; + } + + private async Task UpdateState() + { + await InvokeAsync(() => + { + StateHasChanged(); + }); + } + + protected virtual void Dispose(bool disposing) + { + if (!disposed) + { + if (disposing) + { + } + LayoutStateChangeNotifier.Notify -= UpdateState; + disposed = true; + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } +} \ No newline at end of file diff --git a/src/MultiShop/Client/Shared/MainLayout.razor.css b/src/MultiShop/Client/Shared/MainLayout.razor.css index b2332aa..b964f02 100644 --- a/src/MultiShop/Client/Shared/MainLayout.razor.css +++ b/src/MultiShop/Client/Shared/MainLayout.razor.css @@ -3,18 +3,3 @@ display: flex; flex-direction: column; } - -.main { - flex: 1; -} - - -@media (max-width: 640.98px) { - .top-row:not(.auth) { - display: none; - } - - .top-row.auth { - justify-content: space-between; - } -} \ No newline at end of file diff --git a/src/MultiShop/Client/Shared/NavMenu.razor b/src/MultiShop/Client/Shared/NavMenu.razor deleted file mode 100644 index 6e3514c..0000000 --- a/src/MultiShop/Client/Shared/NavMenu.razor +++ /dev/null @@ -1,74 +0,0 @@ -@using Microsoft.AspNetCore.Components.Authorization -@using Microsoft.AspNetCore.Components.WebAssembly.Authentication -@using Microsoft.Extensions.Configuration - -@inject NavigationManager Navigation -@inject SignOutSessionStateManager SignOutManager -@inject IConfiguration Configuration - - - - -@code { - private bool collapseNavMenu = true; - - private string NavMenuCssClass => (collapseNavMenu ? "collapse " : " ") + "navbar-collapse"; - - private void ToggleNavMenu() - { - collapseNavMenu = !collapseNavMenu; - } - - private async Task BeginSignOut(MouseEventArgs args) - { - await SignOutManager.SetSignOutState(); - Navigation.NavigateTo("authentication/logout"); - } - -} diff --git a/src/MultiShop/Client/wwwroot/css/app.css b/src/MultiShop/Client/wwwroot/css/app.css index 3c5a10e..5240c8b 100644 --- a/src/MultiShop/Client/wwwroot/css/app.css +++ b/src/MultiShop/Client/wwwroot/css/app.css @@ -4,8 +4,30 @@ html, body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; } +#app { + min-height: 100vh; +} + +.page { + min-height: inherit; +} + +.bg-dark { + background-color: #262626 !important; +} + +nav.bg-dark { + background-color: #1A1A1A !important; +} + .content { padding-top: 1.5rem; + padding-right: 2rem; + padding-left: 2rem; +} + +.bg-dark .card { + background-color: #1F1F1F; } #blazor-error-ui { diff --git a/src/MultiShop/Client/wwwroot/100x100.png b/src/MultiShop/Client/wwwroot/images/100x100.png similarity index 100% rename from src/MultiShop/Client/wwwroot/100x100.png rename to src/MultiShop/Client/wwwroot/images/100x100.png diff --git a/src/MultiShop/Client/wwwroot/index.html b/src/MultiShop/Client/wwwroot/index.html index ae3ab62..162d9ae 100644 --- a/src/MultiShop/Client/wwwroot/index.html +++ b/src/MultiShop/Client/wwwroot/index.html @@ -14,12 +14,12 @@
-
-
+
+
Loading...
-
+
Loading...
diff --git a/src/MultiShop/Server/Controllers/ProfileController.cs b/src/MultiShop/Server/Controllers/ProfileController.cs index 58b2916..8d9cc14 100644 --- a/src/MultiShop/Server/Controllers/ProfileController.cs +++ b/src/MultiShop/Server/Controllers/ProfileController.cs @@ -1,7 +1,10 @@ +using System.Text.Json; using System.Threading.Tasks; +using Castle.Core.Logging; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; using MultiShop.Server.Data; using MultiShop.Server.Models; using MultiShop.Shared.Models; @@ -14,12 +17,14 @@ namespace MultiShop.Server.Controllers [Route("[controller]")] public class ProfileController : ControllerBase { + private ILogger logger; private UserManager userManager; private ApplicationDbContext dbContext; - public ProfileController(UserManager userManager, ApplicationDbContext dbContext) + public ProfileController(UserManager userManager, ApplicationDbContext dbContext, ILogger logger) { this.userManager = userManager; this.dbContext = dbContext; + this.logger = logger; } [HttpGet] @@ -36,6 +41,14 @@ namespace MultiShop.Server.Controllers return Ok(userModel.ResultsProfile); } + [HttpGet] + [Route("Application")] + public async Task GetApplicationProfile() { + ApplicationUser userModel = await userManager.GetUserAsync(User); + logger.LogInformation(JsonSerializer.Serialize(userModel.ApplicationProfile)); + return Ok(userModel.ApplicationProfile); + } + [HttpPut] [Route("Search")] public async Task PutSearchProfile(SearchProfile searchProfile) { @@ -52,12 +65,25 @@ namespace MultiShop.Server.Controllers [Route("Results")] public async Task PutResultsProfile(ResultsProfile resultsProfile) { ApplicationUser userModel = await userManager.GetUserAsync(User); - if (userModel.ResultsProfile.Id != resultsProfile.Id) { + if (userModel.ResultsProfile.Id != resultsProfile.Id || userModel.Id != resultsProfile.ApplicationUserId) { return BadRequest(); } dbContext.Entry(userModel.ResultsProfile).CurrentValues.SetValues(resultsProfile); await userManager.UpdateAsync(userModel); return NoContent(); } + + [HttpPut] + [Route("Application")] + public async Task PutApplicationProfile(ApplicationProfile applicationProfile) { + ApplicationUser userModel = await userManager.GetUserAsync(User); + logger.LogInformation(JsonSerializer.Serialize(applicationProfile)); + if (userModel.ApplicationProfile.Id != applicationProfile.Id || userModel.Id != applicationProfile.ApplicationUserId) { + return BadRequest(); + } + dbContext.Entry(userModel.ApplicationProfile).CurrentValues.SetValues(applicationProfile); + await userManager.UpdateAsync(userModel); + return NoContent(); + } } } \ No newline at end of file diff --git a/src/MultiShop/Server/Data/Migrations/20210525224658_InitialCreate.Designer.cs b/src/MultiShop/Server/Data/Migrations/20210531175621_InitialCreate.Designer.cs similarity index 92% rename from src/MultiShop/Server/Data/Migrations/20210525224658_InitialCreate.Designer.cs rename to src/MultiShop/Server/Data/Migrations/20210531175621_InitialCreate.Designer.cs index a9b3f2c..d26bdb7 100644 --- a/src/MultiShop/Server/Data/Migrations/20210525224658_InitialCreate.Designer.cs +++ b/src/MultiShop/Server/Data/Migrations/20210531175621_InitialCreate.Designer.cs @@ -9,7 +9,7 @@ using MultiShop.Server.Data; namespace MultiShop.Server.Data.Migrations { [DbContext(typeof(ApplicationDbContext))] - [Migration("20210525224658_InitialCreate")] + [Migration("20210531175621_InitialCreate")] partial class InitialCreate { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -317,6 +317,32 @@ namespace MultiShop.Server.Data.Migrations b.ToTable("AspNetUsers"); }); + modelBuilder.Entity("MultiShop.Shared.Models.ApplicationProfile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApplicationUserId") + .HasColumnType("TEXT"); + + b.Property("CacheCommonSearches") + .HasColumnType("INTEGER"); + + b.Property("DarkMode") + .HasColumnType("INTEGER"); + + b.Property("EnableSearchHistory") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId") + .IsUnique(); + + b.ToTable("ApplicationProfile"); + }); + modelBuilder.Entity("MultiShop.Shared.Models.ResultsProfile", b => { b.Property("Id") @@ -452,6 +478,13 @@ namespace MultiShop.Server.Data.Migrations .IsRequired(); }); + modelBuilder.Entity("MultiShop.Shared.Models.ApplicationProfile", b => + { + b.HasOne("MultiShop.Server.Models.ApplicationUser", null) + .WithOne("ApplicationProfile") + .HasForeignKey("MultiShop.Shared.Models.ApplicationProfile", "ApplicationUserId"); + }); + modelBuilder.Entity("MultiShop.Shared.Models.ResultsProfile", b => { b.HasOne("MultiShop.Server.Models.ApplicationUser", null) @@ -468,6 +501,9 @@ namespace MultiShop.Server.Data.Migrations modelBuilder.Entity("MultiShop.Server.Models.ApplicationUser", b => { + b.Navigation("ApplicationProfile") + .IsRequired(); + b.Navigation("ResultsProfile") .IsRequired(); diff --git a/src/MultiShop/Server/Data/Migrations/20210525224658_InitialCreate.cs b/src/MultiShop/Server/Data/Migrations/20210531175621_InitialCreate.cs similarity index 92% rename from src/MultiShop/Server/Data/Migrations/20210525224658_InitialCreate.cs rename to src/MultiShop/Server/Data/Migrations/20210531175621_InitialCreate.cs index 803a8dc..948ce64 100644 --- a/src/MultiShop/Server/Data/Migrations/20210525224658_InitialCreate.cs +++ b/src/MultiShop/Server/Data/Migrations/20210531175621_InitialCreate.cs @@ -106,6 +106,28 @@ namespace MultiShop.Server.Data.Migrations onDelete: ReferentialAction.Cascade); }); + migrationBuilder.CreateTable( + name: "ApplicationProfile", + columns: table => new + { + Id = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + ApplicationUserId = table.Column(type: "TEXT", nullable: true), + DarkMode = table.Column(type: "INTEGER", nullable: false), + CacheCommonSearches = table.Column(type: "INTEGER", nullable: false), + EnableSearchHistory = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApplicationProfile", x => x.Id); + table.ForeignKey( + name: "FK_ApplicationProfile_AspNetUsers_ApplicationUserId", + column: x => x.ApplicationUserId, + principalTable: "AspNetUsers", + principalColumn: "Id", + onDelete: ReferentialAction.Restrict); + }); + migrationBuilder.CreateTable( name: "AspNetUserClaims", columns: table => new @@ -245,6 +267,12 @@ namespace MultiShop.Server.Data.Migrations onDelete: ReferentialAction.Restrict); }); + migrationBuilder.CreateIndex( + name: "IX_ApplicationProfile_ApplicationUserId", + table: "ApplicationProfile", + column: "ApplicationUserId", + unique: true); + migrationBuilder.CreateIndex( name: "IX_AspNetRoleClaims_RoleId", table: "AspNetRoleClaims", @@ -323,6 +351,9 @@ namespace MultiShop.Server.Data.Migrations protected override void Down(MigrationBuilder migrationBuilder) { + migrationBuilder.DropTable( + name: "ApplicationProfile"); + migrationBuilder.DropTable( name: "AspNetRoleClaims"); diff --git a/src/MultiShop/Server/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/src/MultiShop/Server/Data/Migrations/ApplicationDbContextModelSnapshot.cs index 6284e00..40cd8ca 100644 --- a/src/MultiShop/Server/Data/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/src/MultiShop/Server/Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -315,6 +315,32 @@ namespace MultiShop.Server.Data.Migrations b.ToTable("AspNetUsers"); }); + modelBuilder.Entity("MultiShop.Shared.Models.ApplicationProfile", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApplicationUserId") + .HasColumnType("TEXT"); + + b.Property("CacheCommonSearches") + .HasColumnType("INTEGER"); + + b.Property("DarkMode") + .HasColumnType("INTEGER"); + + b.Property("EnableSearchHistory") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("ApplicationUserId") + .IsUnique(); + + b.ToTable("ApplicationProfile"); + }); + modelBuilder.Entity("MultiShop.Shared.Models.ResultsProfile", b => { b.Property("Id") @@ -450,6 +476,13 @@ namespace MultiShop.Server.Data.Migrations .IsRequired(); }); + modelBuilder.Entity("MultiShop.Shared.Models.ApplicationProfile", b => + { + b.HasOne("MultiShop.Server.Models.ApplicationUser", null) + .WithOne("ApplicationProfile") + .HasForeignKey("MultiShop.Shared.Models.ApplicationProfile", "ApplicationUserId"); + }); + modelBuilder.Entity("MultiShop.Shared.Models.ResultsProfile", b => { b.HasOne("MultiShop.Server.Models.ApplicationUser", null) @@ -466,6 +499,9 @@ namespace MultiShop.Server.Data.Migrations modelBuilder.Entity("MultiShop.Server.Models.ApplicationUser", b => { + b.Navigation("ApplicationProfile") + .IsRequired(); + b.Navigation("ResultsProfile") .IsRequired(); diff --git a/src/MultiShop/Server/Models/ApplicationUser.cs b/src/MultiShop/Server/Models/ApplicationUser.cs index 79f3ff3..d5adf4f 100644 --- a/src/MultiShop/Server/Models/ApplicationUser.cs +++ b/src/MultiShop/Server/Models/ApplicationUser.cs @@ -11,5 +11,8 @@ namespace MultiShop.Server.Models [Required] public virtual ResultsProfile ResultsProfile { get; private set; } = new ResultsProfile(); + + [Required] + public virtual ApplicationProfile ApplicationProfile {get; private set; } = new ApplicationProfile(); } } diff --git a/src/MultiShop/Shared/Models/ApplicationProfile.cs b/src/MultiShop/Shared/Models/ApplicationProfile.cs new file mode 100644 index 0000000..205333e --- /dev/null +++ b/src/MultiShop/Shared/Models/ApplicationProfile.cs @@ -0,0 +1,15 @@ +namespace MultiShop.Shared.Models +{ + public class ApplicationProfile + { + public int Id { get; set; } + + public string ApplicationUserId { get; set; } + + public bool DarkMode { get; set; } + + public bool CacheCommonSearches { get; set; } = true; + + public bool EnableSearchHistory { get; set; } = true; + } +} \ No newline at end of file diff --git a/src/MultiShop/Shared/Models/SearchProfile.cs b/src/MultiShop/Shared/Models/SearchProfile.cs index df8f855..f8d085c 100644 --- a/src/MultiShop/Shared/Models/SearchProfile.cs +++ b/src/MultiShop/Shared/Models/SearchProfile.cs @@ -48,7 +48,7 @@ namespace MultiShop.Shared.Models if (EnableMaxShippingFee) _maxShippingFee = value; } } - public bool KeepUnknownShipping { get; set; } + public bool KeepUnknownShipping { get; set; } = true; [Required] public ShopToggler ShopStates { get; set; } = new ShopToggler(); @@ -115,5 +115,11 @@ namespace MultiShop.Shared.Models { return Id; } + + public SearchProfile DeepCopy() { + SearchProfile profile = (SearchProfile)MemberwiseClone(); + profile.ShopStates = ShopStates.Clone(); + return profile; + } } } \ No newline at end of file From 9e3de4b6dcc733bba103a8202899d90c931b57ad Mon Sep 17 00:00:00 2001 From: Harrison Deng Date: Mon, 31 May 2021 19:08:22 -0500 Subject: [PATCH 036/167] Made SearchBar component independent of it's environment. Also added additional attribute capability. --- src/MultiShop/Client/Pages/Search.razor | 2 +- src/MultiShop/Client/Shared/SearchBar.razor | 20 ++++++++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/MultiShop/Client/Pages/Search.razor b/src/MultiShop/Client/Pages/Search.razor index 9aba2a9..fdd68b6 100644 --- a/src/MultiShop/Client/Pages/Search.razor +++ b/src/MultiShop/Client/Pages/Search.razor @@ -4,7 +4,7 @@ @using MultiShop.Client.Listing
-
+
diff --git a/src/MultiShop/Client/Shared/SearchBar.razor b/src/MultiShop/Client/Shared/SearchBar.razor index aa21f8c..807ccac 100644 --- a/src/MultiShop/Client/Shared/SearchBar.razor +++ b/src/MultiShop/Client/Shared/SearchBar.razor @@ -1,10 +1,19 @@ - -
- @Append - +@using System.Text.Json +@using Microsoft.Extensions.Logging +@inject ILogger Logger + +
+ +
+ @Append + +
@code { + [Parameter(CaptureUnmatchedValues = true)] + public IDictionary AdditionalAttributes { get; set; } + [Parameter] public RenderFragment Append { get; set; } @@ -17,6 +26,9 @@ public EventCallback OnSearchRequested { get; set; } public bool Searching { get; set; } + + private string groupClassCss => "input-group " + (AdditionalAttributes != null && AdditionalAttributes.ContainsKey("class") ? AdditionalAttributes["class"] as string : null); + public async Task Search() { Searching = true; From 065d786dd70e349a6d6f641fb9ebdfbf2283afca Mon Sep 17 00:00:00 2001 From: Harrison Deng Date: Mon, 31 May 2021 19:09:11 -0500 Subject: [PATCH 037/167] Updated ToggleableButton to support no additional attributes. --- src/MultiShop/Client/Shared/ToggleableButton.razor | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/MultiShop/Client/Shared/ToggleableButton.razor b/src/MultiShop/Client/Shared/ToggleableButton.razor index 89d332a..7c75896 100644 --- a/src/MultiShop/Client/Shared/ToggleableButton.razor +++ b/src/MultiShop/Client/Shared/ToggleableButton.razor @@ -17,12 +17,5 @@ private bool state; - private string ButtonClasses - { - get - { - IReadOnlyDictionary t = AdditionalAttributes; - return (state ? "active " : "") + (AdditionalAttributes["class"] as string); - } - } + private string ButtonClasses => (state ? "active " : null) + (AdditionalAttributes != null && AdditionalAttributes.ContainsKey("class") ? AdditionalAttributes["class"] as string : null); } \ No newline at end of file From 78006f79d04512fd413863c5de424fe538dbe414 Mon Sep 17 00:00:00 2001 From: Harrison Deng Date: Mon, 31 May 2021 19:09:53 -0500 Subject: [PATCH 038/167] Improved main page. --- src/MultiShop/Client/Pages/Index.razor | 29 +++++++++++++++------- src/MultiShop/Client/Pages/Index.razor.css | 3 +++ src/MultiShop/Client/wwwroot/css/app.css | 13 +++++++--- 3 files changed, 32 insertions(+), 13 deletions(-) create mode 100644 src/MultiShop/Client/Pages/Index.razor.css diff --git a/src/MultiShop/Client/Pages/Index.razor b/src/MultiShop/Client/Pages/Index.razor index 48640f6..b7ca78b 100644 --- a/src/MultiShop/Client/Pages/Index.razor +++ b/src/MultiShop/Client/Pages/Index.razor @@ -1,14 +1,25 @@ @page "/" +@inject NavigationManager NavigationManager -
-

Welcome to MultiShop!

-
-
-

What is MultiShop?

-

MultiShop is an app that allows users to easily check multiple online retailers, primarily for electronic components. On request, the app searches, aggregates and sorts all the most important information about the products it finds while searching through sites for you! MultiShop is intended to be a small light-weight app the runs on the clients computer. Therefore, all actions and processes are actually reliant on your computer. Additionally, MultiShop does not crawl sites and save their products like a typical search engine. To provide the latest prices, all searches are performed (near) live on command.

+
+

MultiShop

+

For all your cross shop searching needs! MultiShop is a simple shopping assistant that searches live for what you need.

+
-
-

How does it work?

-

MultiShop is a simple program that takes care of looking through product listings for you. That's all. As all tasks are executed locally on your computer MultiShop is like any other program, except that it is transient, and automatically loaded when you visit this website. The shop has a variety of modules meant to search different shops by essentially querying the shop as the user would normally, and then scanning the results.

+
+
+

What is MultiShop?

+

MultiShop is an app that allows users to easily check multiple online retailers, primarily for electronic components. On request, the app searches, aggregates and sorts all the most important information about the products it finds while searching through sites for you! MultiShop is intended to be a small light-weight app the runs on the clients computer. Therefore, all actions and processes are actually reliant on your computer. Additionally, MultiShop does not crawl sites and save their products like a typical search engine. To provide the latest prices, all searches are performed (near) live on command.

+
+ +
+
+

How does it work?

+

MultiShop is a simple program that takes care of looking through product listings for you. That's all. As all tasks are executed locally on your computer MultiShop is like any other program, except that it is transient, and automatically loaded when you visit this website. The shop has a variety of modules meant to search different shops by essentially querying the shop as the user would normally, and then scanning the results.

+
+
\ No newline at end of file diff --git a/src/MultiShop/Client/Pages/Index.razor.css b/src/MultiShop/Client/Pages/Index.razor.css new file mode 100644 index 0000000..fad5136 --- /dev/null +++ b/src/MultiShop/Client/Pages/Index.razor.css @@ -0,0 +1,3 @@ +.search { + max-width: 75%; +} \ No newline at end of file diff --git a/src/MultiShop/Client/wwwroot/css/app.css b/src/MultiShop/Client/wwwroot/css/app.css index 5240c8b..f1da7a9 100644 --- a/src/MultiShop/Client/wwwroot/css/app.css +++ b/src/MultiShop/Client/wwwroot/css/app.css @@ -13,22 +13,27 @@ html, body { } .bg-dark { - background-color: #262626 !important; + background-color: #2E2E2E !important; } nav.bg-dark { background-color: #1A1A1A !important; } +.bg-dark .card { + background-color: #1F1F1F; +} + +.bg-dark .jumbotron { + background-color: #262626; +} + .content { padding-top: 1.5rem; padding-right: 2rem; padding-left: 2rem; } -.bg-dark .card { - background-color: #1F1F1F; -} #blazor-error-ui { background: lightyellow; From d57a61d5cace3f7b795c193f4548310b30fd7cf5 Mon Sep 17 00:00:00 2001 From: Harrison Deng Date: Tue, 1 Jun 2021 02:26:12 -0500 Subject: [PATCH 039/167] Removed a generated todo in .gitignore. --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index e33ac71..405baa3 100644 --- a/.gitignore +++ b/.gitignore @@ -150,7 +150,6 @@ publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml -# TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj From 3957d65370342522459710a754089e49152677f1 Mon Sep 17 00:00:00 2001 From: Harrison Deng Date: Wed, 2 Jun 2021 14:41:32 -0500 Subject: [PATCH 040/167] Changed CascadingDependencies to be more modular. --- src/MultiShop/Client/App.razor | 2 +- src/MultiShop/Client/App.razor.cs | 45 ++++++++++ .../Client/Pages/Configuration.razor.cs | 10 ++- src/MultiShop/Client/Pages/Search.razor.cs | 8 +- .../Client/RuntimeDependencyManager.cs | 83 +++++++++++++++++++ .../Client/Shared/CascadingDependencies.razor | 6 +- .../Shared/CascadingDependencies.razor.cs | 65 ++++----------- .../Shared/HorizontalNavMenuTemplate.razor | 10 +-- .../Client/Shared/HorizontalSiteNav.razor | 2 +- src/MultiShop/Client/Shared/MainLayout.razor | 8 +- 10 files changed, 174 insertions(+), 65 deletions(-) create mode 100644 src/MultiShop/Client/App.razor.cs create mode 100644 src/MultiShop/Client/RuntimeDependencyManager.cs diff --git a/src/MultiShop/Client/App.razor b/src/MultiShop/Client/App.razor index 426024b..5b27bc4 100644 --- a/src/MultiShop/Client/App.razor +++ b/src/MultiShop/Client/App.razor @@ -1,5 +1,5 @@  - + diff --git a/src/MultiShop/Client/App.razor.cs b/src/MultiShop/Client/App.razor.cs new file mode 100644 index 0000000..fbfd49c --- /dev/null +++ b/src/MultiShop/Client/App.razor.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Json; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.Extensions.Logging; +using MultiShop.Client.Module; +using MultiShop.Client.Shared; +using MultiShop.Shared.Models; +using MultiShop.Shop.Framework; + +namespace MultiShop.Client +{ + public partial class App + { + private ICollection dependencies = new List(); + + protected override void OnInitialized() + { + base.OnInitialized(); + dependencies.Add(new RuntimeDependencyManager.Dependency(typeof(IReadOnlyDictionary), "Shops", DownloadShops)); + dependencies.Add(new RuntimeDependencyManager.Dependency(typeof(ApplicationProfile), "Application Profile", DownloadApplicationProfile)); + } + + private async ValueTask DownloadShops(HttpClient publicHttp, HttpClient http, AuthenticationState authState, ILogger logger) + { + ShopModuleLoader loader = new ShopModuleLoader(publicHttp, logger); + return await loader.GetShops(); + } + + private async ValueTask DownloadApplicationProfile(HttpClient publicHttp, HttpClient http, AuthenticationState authState, ILogger logger) + { + if (authState.User.Identity.IsAuthenticated) + { + logger.LogDebug($"User is logged in. Attempting to fetch application profile."); + HttpResponseMessage response = await http.GetAsync("Profile/Application"); + if (response.IsSuccessStatusCode) + { + return await response.Content.ReadFromJsonAsync(); + } + } + return new ApplicationProfile(); + } + } +} \ No newline at end of file diff --git a/src/MultiShop/Client/Pages/Configuration.razor.cs b/src/MultiShop/Client/Pages/Configuration.razor.cs index 5f630ab..ced37e5 100644 --- a/src/MultiShop/Client/Pages/Configuration.razor.cs +++ b/src/MultiShop/Client/Pages/Configuration.razor.cs @@ -25,7 +25,9 @@ namespace MultiShop.Client.Pages [CascadingParameter] private Task AuthenticationStateTask { get; set; } - [CascadingParameter(Name = "ApplicationProfile")] + [CascadingParameter(Name = "RuntimeDependencyManager")] + private RuntimeDependencyManager RuntimeDependencyManager { get; set; } + private ApplicationProfile ApplicationProfile { get; set; } private bool collapseNavMenu; @@ -50,6 +52,12 @@ namespace MultiShop.Client.Pages Section.Search }; + protected override void OnInitialized() + { + base.OnInitialized(); + ApplicationProfile = RuntimeDependencyManager.Get(); + } + private string GetNavItemCssClass(Section section) { return "nav-item" + ((section == currentSection) ? " active" : null); diff --git a/src/MultiShop/Client/Pages/Search.razor.cs b/src/MultiShop/Client/Pages/Search.razor.cs index da5a9da..6eb35f9 100644 --- a/src/MultiShop/Client/Pages/Search.razor.cs +++ b/src/MultiShop/Client/Pages/Search.razor.cs @@ -29,8 +29,11 @@ namespace MultiShop.Client.Pages [CascadingParameter] private Task AuthenticationStateTask { get; set; } - [CascadingParameter(Name = "Shops")] - public IReadOnlyDictionary Shops { get; set; } + [CascadingParameter(Name = "RuntimeDependencyManager")] + public RuntimeDependencyManager RuntimeDependencyManager { get; set; } + + private IReadOnlyDictionary Shops { get; set; } + [Parameter] public string Query { get; set; } @@ -51,6 +54,7 @@ namespace MultiShop.Client.Pages { base.OnInitialized(); LayoutStateChangeNotifier.Notify += UpdateState; + Shops = RuntimeDependencyManager.Get>(); } protected override async Task OnInitializedAsync() diff --git a/src/MultiShop/Client/RuntimeDependencyManager.cs b/src/MultiShop/Client/RuntimeDependencyManager.cs new file mode 100644 index 0000000..c906e57 --- /dev/null +++ b/src/MultiShop/Client/RuntimeDependencyManager.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.Extensions.Logging; + +namespace MultiShop.Client +{ + public class RuntimeDependencyManager : IAsyncDisposable + { + private bool disposedValue; + private HttpClient authenticatedHttp; + private HttpClient publicHttp; + private Task authenticationStateTask; + private ILogger logger; + private Dictionary> RuntimeLoadedDependencies = new Dictionary>(); + + public RuntimeDependencyManager(HttpClient publicHttp, HttpClient authenticatedHttp, Task authenticationStateTask, ILogger logger) + { + this.publicHttp = publicHttp; + this.authenticatedHttp = authenticatedHttp; + this.authenticationStateTask = authenticationStateTask; + this.logger = logger; + } + + + public async ValueTask SetupDependency(Dependency dependency) + { + logger.LogDebug($"Setting up dependency of type \"{dependency.Type}\" named \"{dependency.Name}\"."); + Dictionary dependencies = RuntimeLoadedDependencies.GetValueOrDefault(dependency.Type, new Dictionary()); + dependencies.Add(dependency.Name, await dependency.LoadDependency.Invoke(publicHttp, authenticatedHttp, await authenticationStateTask, logger)); + RuntimeLoadedDependencies[dependency.Type] = dependencies; + } + + public T Get(string name = "") { + Type type = typeof(T); + if (!RuntimeLoadedDependencies.ContainsKey(typeof(T))) throw new InvalidOperationException($"No dependency of type {type}."); + if (!RuntimeLoadedDependencies[type].ContainsKey(name)) throw new InvalidOperationException($"No dependency of type {type} with name {name}."); + return (T) RuntimeLoadedDependencies[type][name]; + } + + public async ValueTask DisposeAsync() + { + if (!disposedValue) { + foreach (Dictionary dependencies in RuntimeLoadedDependencies.Values) + { + foreach (object dependency in dependencies.Values) + { + IDisposable disposableDep = dependency as IDisposable; + if (disposableDep != null) { + disposableDep.Dispose(); + } else { + IAsyncDisposable asyncDisposableDep = dependency as IAsyncDisposable; + if (asyncDisposableDep != null) { + await asyncDisposableDep.DisposeAsync(); + } + } + } + } + } + RuntimeLoadedDependencies.Clear(); + disposedValue = true; + } + + + public class Dependency + { + public Type Type { get; } + public string Name { get; } + public string DisplayName { get; } + public Func> LoadDependency { get; } + + public Dependency(Type type, string displayName, Func> LoadDependencyFunc, string name = null) + { + this.Type = type; + this.DisplayName = displayName; + this.Name = name ?? ""; + this.LoadDependency = LoadDependencyFunc; + } + } + } +} \ No newline at end of file diff --git a/src/MultiShop/Client/Shared/CascadingDependencies.razor b/src/MultiShop/Client/Shared/CascadingDependencies.razor index 27f77a6..b5c1bd0 100644 --- a/src/MultiShop/Client/Shared/CascadingDependencies.razor +++ b/src/MultiShop/Client/Shared/CascadingDependencies.razor @@ -4,10 +4,8 @@ @if (loadingDisplay == null) { - - - @Content - + + @Content } else { @LoadingContent(loadingDisplay) diff --git a/src/MultiShop/Client/Shared/CascadingDependencies.razor.cs b/src/MultiShop/Client/Shared/CascadingDependencies.razor.cs index 7be6426..eec2619 100644 --- a/src/MultiShop/Client/Shared/CascadingDependencies.razor.cs +++ b/src/MultiShop/Client/Shared/CascadingDependencies.razor.cs @@ -1,18 +1,16 @@ using System; using System.Collections.Generic; using System.Net.Http; -using System.Net.Http.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.Extensions.Logging; -using MultiShop.Client.Module; using MultiShop.Shared.Models; using MultiShop.Shop.Framework; namespace MultiShop.Client.Shared { - public partial class CascadingDependencies : IDisposable + public partial class CascadingDependencies : IAsyncDisposable { [Inject] private ILogger Logger { get; set; } @@ -29,65 +27,36 @@ namespace MultiShop.Client.Shared [Parameter] public RenderFragment Content { get; set; } + [Parameter] + public ICollection Dependencies { get; set; } + + private RuntimeDependencyManager manager; private bool disposedValue; private string loadingDisplay; - private IReadOnlyDictionary shops; - - private ApplicationProfile applicationProfile; - protected override async Task OnInitializedAsync() { - loadingDisplay = ""; + loadingDisplay = "stuff"; await base.OnInitializedAsync(); - await DownloadShops(HttpClientFactory.CreateClient("Public-MultiShop.ServerAPI")); - await DownloadApplicationProfile(HttpClientFactory.CreateClient("MultiShop.ServerAPI")); + manager = new RuntimeDependencyManager(HttpClientFactory.CreateClient("Public-MultiShop.ServerAPI"), HttpClientFactory.CreateClient("MultiShop.ServerAPI"), AuthenticationStateTask, Logger); + foreach (RuntimeDependencyManager.Dependency dep in Dependencies) + { + loadingDisplay = dep.DisplayName; + await manager.SetupDependency(dep); + } loadingDisplay = null; } - private async Task DownloadShops(HttpClient http) - { - loadingDisplay = "shops"; - ShopModuleLoader loader = new ShopModuleLoader(http, Logger); - shops = await loader.GetShops(); - } + - private async Task DownloadApplicationProfile(HttpClient http) + public async ValueTask DisposeAsync() { - loadingDisplay = "profile"; - AuthenticationState authState = await AuthenticationStateTask; - if (authState.User.Identity.IsAuthenticated) - { - Logger.LogDebug($"User is logged in. Attempting to fetch application profile."); - HttpResponseMessage response = await http.GetAsync("Profile/Application"); - if (response.IsSuccessStatusCode) - { - applicationProfile = await response.Content.ReadFromJsonAsync(); - } + if (!disposedValue) { + await manager.DisposeAsync(); } - if (applicationProfile == null) applicationProfile = new ApplicationProfile(); + disposedValue = true; } - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - foreach (string shopName in shops.Keys) - { - shops[shopName].Dispose(); - } - } - disposedValue = true; - } - } - - public void Dispose() - { - Dispose(disposing: true); - GC.SuppressFinalize(this); - } } } \ No newline at end of file diff --git a/src/MultiShop/Client/Shared/HorizontalNavMenuTemplate.razor b/src/MultiShop/Client/Shared/HorizontalNavMenuTemplate.razor index fe67739..bed498c 100644 --- a/src/MultiShop/Client/Shared/HorizontalNavMenuTemplate.razor +++ b/src/MultiShop/Client/Shared/HorizontalNavMenuTemplate.razor @@ -4,7 +4,7 @@ @implements IDisposable @inject LayoutStateChangeNotifier LayoutStateChangeNotifier -