diff --git a/.gitignore b/.gitignore index ea8c4bf..e06d2f5 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,8 @@ /target + +# WASM build artifacts (generated by docker-compose) +/www/pkg/ + +# Backup files +*.old +*.orig diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 0000000..f5b8ebc --- /dev/null +++ b/Caddyfile @@ -0,0 +1,13 @@ +:8080 { + # Serve static files + root * /app/www + file_server + + # Reverse proxy WebSocket to websockify + reverse_proxy /ws/* websockify:8081 + + # Disable caching for development + header { + Cache-Control "no-cache" + } +} diff --git a/Cargo.lock b/Cargo.lock index 1a8a9ee..88af9e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -126,6 +126,16 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + [[package]] name = "core-foundation-sys" version = "0.8.6" @@ -213,8 +223,12 @@ version = "0.0.0" dependencies = [ "bytes", "either", + "js-sys", "regex", "tracing-mutex", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", ] [[package]] @@ -441,6 +455,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.92" @@ -470,6 +496,16 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" @@ -638,7 +674,12 @@ dependencies = [ "bytes", "chrono", "clap", + "console_error_panic_hook", "crossterm", + "js-sys", "libxotpad", "tracing-mutex", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", ] diff --git a/Cargo.toml b/Cargo.toml index bf18e85..d6a080b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,16 +4,31 @@ version = "0.0.0" edition = "2021" [features] -default = ["x25_org"] +default = ["x25_org", "native"] x25_org = [] +native = ["clap", "crossterm"] +wasm = [] + +[lib] +crate-type = ["cdylib", "rlib"] [workspace] [dependencies] bytes = "1.6.0" chrono = "0.4.38" -clap = { version = "4.5.4", default-features = false, features = ["std", "derive", "usage", "help", "error-context"] } -crossterm = "0.27.0" tracing-mutex = "0.3.0" libxotpad = { path = "libxotpad" } + +# Native dependencies +clap = { version = "4.5.4", default-features = false, features = ["std", "derive", "usage", "help", "error-context"], optional = true } +crossterm = { version = "0.27.0", optional = true } + +# WASM dependencies +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = "0.2" +wasm-bindgen-futures = "0.4" +js-sys = "0.3" +web-sys = { version = "0.3", features = ["WebSocket", "MessageEvent", "BinaryType", "ErrorEvent", "CloseEvent", "console", "Window", "Location"] } +console_error_panic_hook = "0.1" diff --git a/Dockerfile.wasm b/Dockerfile.wasm new file mode 100644 index 0000000..9b09b73 --- /dev/null +++ b/Dockerfile.wasm @@ -0,0 +1,35 @@ +# Build WASM module only +FROM rust:1.75-slim + +# Install wasm-pack and dependencies +RUN apt-get update && apt-get install -y \ + curl \ + pkg-config \ + libssl-dev \ + && rm -rf /var/lib/apt/lists/* + +RUN curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + +# Add wasm target +RUN rustup target add wasm32-unknown-unknown + +# Set working directory +WORKDIR /build + +# Copy source files +COPY Cargo.toml Cargo.lock ./ +COPY src/ ./src/ +COPY libxotpad/ ./libxotpad/ +COPY build-wasm.sh ./ + +# Build WASM module with x25.org DNS support +RUN chmod +x build-wasm.sh && \ + wasm-pack build \ + --target web \ + --out-dir www/pkg \ + --no-typescript \ + --features wasm,x25_org \ + --no-default-features + +# Output will be in /build/www/pkg/ +CMD ["sh", "-c", "cp -r /build/www/pkg/* /output/"] diff --git a/Dockerfile.websockify b/Dockerfile.websockify new file mode 100644 index 0000000..cd0fd4f --- /dev/null +++ b/Dockerfile.websockify @@ -0,0 +1,16 @@ +FROM python:3.11-slim + +# Install websockify +RUN pip3 install --no-cache-dir websockify + +# Create app directory +WORKDIR /app + +# Copy token plugin and websockify wrapper +COPY token_plugin.py websockify_wrapper.py ./ + +# Expose websockify port +EXPOSE 8081 + +# Run via wrapper that adds pong timeout detection +CMD ["python3", "websockify_wrapper.py", "--heartbeat=10", "--token-plugin=token_plugin.TokenPlugin", "0.0.0.0:8081"] diff --git a/build-wasm.sh b/build-wasm.sh new file mode 100755 index 0000000..a1fb47e --- /dev/null +++ b/build-wasm.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# Build script for xotpad WASM target + +set -e + +echo "Building xotpad for WebAssembly..." + +# Check if wasm-pack is installed +if ! command -v wasm-pack &> /dev/null; then + echo "Error: wasm-pack is not installed" + echo "Install it with: cargo install wasm-pack" + exit 1 +fi + +# Build the WASM module with x25.org DNS support +wasm-pack build \ + --target web \ + --out-dir www/pkg \ + --no-typescript \ + --features wasm,x25_org \ + --no-default-features + +echo "✓ WASM build complete!" +echo "" +echo "Output directory: www/pkg/" +echo "" +echo "To test locally, run a web server in the www/ directory:" +echo " cd www && python3 -m http.server 8080" +echo "" +echo "Note: You'll also need a websockify server to relay TCP connections." +echo "See www/WASM_SETUP.md for detailed instructions." diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f0bd150 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,60 @@ +services: + # Build WASM module (runs automatically before caddy starts) + wasm-builder: + build: + context: . + dockerfile: Dockerfile.wasm + volumes: + - ./www/pkg:/output + command: sh -c "cp -r /build/www/pkg/* /output/" + # Exit after copying files + restart: "no" + + # Caddy web server serving static files and proxying WebSocket + caddy: + image: caddy:2-alpine + restart: unless-stopped + container_name: xotpad-caddy + ports: + - "8080:8080" + volumes: + - ./Caddyfile:/etc/caddy/Caddyfile:ro + - ./www:/app/www:ro + - caddy_data:/data + - caddy_config:/config + environment: + - TZ=UTC + depends_on: + wasm-builder: + condition: service_completed_successfully + websockify: + condition: service_healthy + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/"] + interval: 30s + timeout: 3s + retries: 3 + start_period: 5s + + # Websockify for WebSocket to TCP relay + websockify: + build: + context: . + dockerfile: Dockerfile.websockify + restart: unless-stopped + container_name: xotpad-websockify + environment: + - TZ=UTC + - PYTHONPATH=/app + expose: + - "8081" + healthcheck: + test: ["CMD", "python3", "-c", "import socket; s=socket.socket(); s.connect(('127.0.0.1', 8081)); s.close()"] + interval: 30s + timeout: 3s + retries: 3 + start_period: 5s + +volumes: + caddy_data: + caddy_config: diff --git a/libxotpad/Cargo.toml b/libxotpad/Cargo.toml index 8821702..5269edc 100644 --- a/libxotpad/Cargo.toml +++ b/libxotpad/Cargo.toml @@ -3,8 +3,18 @@ name = "libxotpad" version = "0.0.0" edition = "2021" +[features] +default = [] + [dependencies] bytes = "1.6.0" either = "1.10.0" regex = "1.10.4" tracing-mutex = "0.3.0" + +# WASM dependencies +[target.'cfg(target_arch = "wasm32")'.dependencies] +wasm-bindgen = "0.2" +wasm-bindgen-futures = "0.4" +js-sys = "0.3" +web-sys = { version = "0.3", features = ["WebSocket", "MessageEvent", "BinaryType", "ErrorEvent", "CloseEvent", "Window", "Location"] } diff --git a/libxotpad/src/lib.rs b/libxotpad/src/lib.rs index f809f3c..2529844 100644 --- a/libxotpad/src/lib.rs +++ b/libxotpad/src/lib.rs @@ -1,6 +1,11 @@ +#[cfg(not(target_arch = "wasm32"))] pub mod pad; +pub mod stream; pub mod x121; pub mod x25; pub mod x29; pub mod x3; pub mod xot; + +#[cfg(target_arch = "wasm32")] +pub mod wasm_async; diff --git a/libxotpad/src/pad.rs b/libxotpad/src/pad.rs index 3da7c8c..ca35873 100644 --- a/libxotpad/src/pad.rs +++ b/libxotpad/src/pad.rs @@ -14,7 +14,7 @@ use crate::x121::X121Addr; use crate::x25::{Svc, Vc, X25Params}; use crate::x29::{X29CallUserData, X29PadMessage}; use crate::x3::{X3Echo, X3Editing, X3Forward, X3Idle, X3LfInsert, X3ParamError, X3Params}; -use crate::xot::XotLink; +use crate::xot::PlatformXotLink; type SendQueue = (VecDeque, Option); type IndicateMessage = Vec<(u8, Result)>; @@ -199,7 +199,7 @@ impl Pad { } pub fn call( - link: XotLink, + link: PlatformXotLink, channel: u16, addr: &X121Addr, call_data: &[u8], diff --git a/libxotpad/src/stream.rs b/libxotpad/src/stream.rs new file mode 100644 index 0000000..ed3eadd --- /dev/null +++ b/libxotpad/src/stream.rs @@ -0,0 +1,221 @@ +//! Stream abstraction for cross-platform networking. +//! +//! This module provides a trait-based abstraction over network streams +//! to support both native TCP sockets and WebSocket connections in WASM. + +use std::io; + +/// A bidirectional network stream that can send and receive bytes. +#[cfg(not(target_arch = "wasm32"))] +pub trait Stream: Send { + /// Reads data from the stream into the provided buffer. + /// + /// Returns the number of bytes read, or 0 if the stream is closed. + fn read(&mut self, buf: &mut [u8]) -> io::Result; + + /// Writes all data from the buffer to the stream. + fn write_all(&mut self, buf: &[u8]) -> io::Result<()>; + + /// Shuts down the stream for both reading and writing. + fn shutdown(&mut self) -> io::Result<()>; + + /// Sets the TCP_NODELAY option (no-op on non-TCP streams). + fn set_nodelay(&self, nodelay: bool) -> io::Result<()>; +} + +/// A bidirectional network stream that can send and receive bytes. +/// WASM version doesn't require Send since WASM is single-threaded. +#[cfg(target_arch = "wasm32")] +pub trait Stream { + /// Reads data from the stream into the provided buffer. + /// + /// Returns the number of bytes read, or 0 if the stream is closed. + fn read(&mut self, buf: &mut [u8]) -> io::Result; + + /// Writes all data from the buffer to the stream. + fn write_all(&mut self, buf: &[u8]) -> io::Result<()>; + + /// Shuts down the stream for both reading and writing. + fn shutdown(&mut self) -> io::Result<()>; + + /// Sets the TCP_NODELAY option (no-op on non-TCP streams). + fn set_nodelay(&self, nodelay: bool) -> io::Result<()>; +} + +// Native implementation using std::net::TcpStream +#[cfg(not(target_arch = "wasm32"))] +mod native { + use super::*; + use std::io::{Read, Write}; + use std::net::{Shutdown, TcpStream}; + + impl Stream for TcpStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + Read::read(self, buf) + } + + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + Write::write_all(self, buf) + } + + fn shutdown(&mut self) -> io::Result<()> { + TcpStream::shutdown(self, Shutdown::Both) + } + + fn set_nodelay(&self, nodelay: bool) -> io::Result<()> { + TcpStream::set_nodelay(self, nodelay) + } + } +} + +// WASM implementation using WebSocket +#[cfg(target_arch = "wasm32")] +mod wasm { + use super::*; + use std::cell::RefCell; + use std::collections::VecDeque; + use std::rc::Rc; + use wasm_bindgen::prelude::*; + use wasm_bindgen::JsCast; + use web_sys::{BinaryType, ErrorEvent, MessageEvent, WebSocket}; + + /// WebSocket-based stream for WASM environments. + /// + /// This connects to a websockify-compatible server that relays + /// TCP connections over WebSocket. + pub struct WsStream { + ws: WebSocket, + recv_queue: Rc>>, + error: Rc>>, + closed: Rc>, + } + + impl WsStream { + /// Creates a new WebSocket stream. + /// + /// The URL should point to a websockify server that will relay + /// the TCP connection. For example: "ws://localhost:8080/tcp/gateway:1998" + pub fn connect(url: &str) -> Result { + let ws = WebSocket::new(url)?; + ws.set_binary_type(BinaryType::Arraybuffer); + + let recv_queue = Rc::new(RefCell::new(VecDeque::new())); + let error = Rc::new(RefCell::new(None)); + let closed = Rc::new(RefCell::new(false)); + + // Set up message handler + { + let recv_queue = Rc::clone(&recv_queue); + let onmessage = Closure::wrap(Box::new(move |e: MessageEvent| { + if let Ok(arraybuf) = e.data().dyn_into::() { + let array = js_sys::Uint8Array::new(&arraybuf); + let mut queue = recv_queue.borrow_mut(); + for byte in array.to_vec() { + queue.push_back(byte); + } + } + }) as Box); + ws.set_onmessage(Some(onmessage.as_ref().unchecked_ref())); + onmessage.forget(); + } + + // Set up error handler + { + let error = Rc::clone(&error); + let onerror = Closure::wrap(Box::new(move |e: ErrorEvent| { + *error.borrow_mut() = Some(format!("WebSocket error: {:?}", e)); + }) as Box); + ws.set_onerror(Some(onerror.as_ref().unchecked_ref())); + onerror.forget(); + } + + // Set up close handler + { + let closed = Rc::clone(&closed); + let onclose = Closure::wrap(Box::new(move |_e: web_sys::CloseEvent| { + *closed.borrow_mut() = true; + }) as Box); + ws.set_onclose(Some(onclose.as_ref().unchecked_ref())); + onclose.forget(); + } + + Ok(WsStream { + ws, + recv_queue, + error, + closed, + }) + } + + /// Checks if the WebSocket is ready for communication. + pub fn is_ready(&self) -> bool { + self.ws.ready_state() == WebSocket::OPEN + } + + /// Gets the WebSocket ready state. + pub fn ready_state(&self) -> u16 { + self.ws.ready_state() + } + } + + impl Stream for WsStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + // Check for errors + if let Some(err) = self.error.borrow_mut().take() { + return Err(io::Error::new(io::ErrorKind::Other, err)); + } + + // Check if closed + if *self.closed.borrow() && self.recv_queue.borrow().is_empty() { + return Ok(0); + } + + let mut queue = self.recv_queue.borrow_mut(); + let mut count = 0; + + for i in 0..buf.len() { + if let Some(byte) = queue.pop_front() { + buf[i] = byte; + count += 1; + } else { + break; + } + } + + if count == 0 && !*self.closed.borrow() { + // No data available yet, but connection is still open + Err(io::Error::new(io::ErrorKind::WouldBlock, "no data available")) + } else { + Ok(count) + } + } + + fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { + if let Some(err) = self.error.borrow_mut().take() { + return Err(io::Error::new(io::ErrorKind::Other, err)); + } + + if *self.closed.borrow() { + return Err(io::Error::new(io::ErrorKind::BrokenPipe, "connection closed")); + } + + self.ws + .send_with_u8_array(buf) + .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{:?}", e))) + } + + fn shutdown(&mut self) -> io::Result<()> { + self.ws + .close() + .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("{:?}", e))) + } + + fn set_nodelay(&self, _nodelay: bool) -> io::Result<()> { + // No-op for WebSocket + Ok(()) + } + } +} + +#[cfg(target_arch = "wasm32")] +pub use wasm::WsStream; diff --git a/libxotpad/src/wasm_async.rs b/libxotpad/src/wasm_async.rs new file mode 100644 index 0000000..fbab525 --- /dev/null +++ b/libxotpad/src/wasm_async.rs @@ -0,0 +1,767 @@ +//! Async WASM-specific implementation for X.25 PAD. +//! +//! This module provides a simplified async implementation for WASM environments +//! that doesn't rely on threads. It's separate from the native threaded implementation. + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_futures::JsFuture; + +use bytes::{Bytes, BytesMut}; +use std::cell::RefCell; +use std::collections::VecDeque; +use std::io; +use std::rc::Rc; + +use crate::stream::WsStream; +use crate::x121::X121Addr; +use crate::x25::facility::X25Facility; +use crate::x25::packet::{ + X25CallRequest, X25ClearConfirm, X25ClearRequest, X25Data, X25Packet, X25ReceiveReady, + X25ResetConfirm, +}; +use crate::x25::X25Params; +use crate::x29::X29PadMessage; +use crate::xot::XotLink; + +/// Async X.25 connection for WASM environments. +#[derive(Clone)] +pub struct WasmConnection { + link: Rc>>, + channel: u16, + params: X25Params, + state: Rc>, + recv_buffer: Rc>>, + recv_seq: Rc>, // P(R) - next expected receive sequence number + send_seq: Rc>, // P(S) - next send sequence number + param_updates: Rc>>>, // Pending X.29 local parameter updates (Set/SetRead) + remote_param_updates: Rc>>>, // Pending X.29 remote parameter indications (Indicate) + on_packet: Rc>>, // Packet inspection callback +} + +#[derive(Debug, Clone, PartialEq)] +enum ConnectionState { + Idle, + Calling, + Connected, + Clearing, + Closed, +} + +impl WasmConnection { + /// Creates a new WASM connection with the given link and parameters. + pub fn new(link: XotLink, channel: u16, params: X25Params) -> Self { + WasmConnection { + link: Rc::new(RefCell::new(link)), + channel, + params, + state: Rc::new(RefCell::new(ConnectionState::Idle)), + recv_buffer: Rc::new(RefCell::new(VecDeque::new())), + recv_seq: Rc::new(RefCell::new(0)), + send_seq: Rc::new(RefCell::new(0)), + param_updates: Rc::new(RefCell::new(None)), + remote_param_updates: Rc::new(RefCell::new(None)), + on_packet: Rc::new(RefCell::new(None)), + } + } + + /// Sets the callback for packet inspection. + pub fn set_packet_callback(&self, callback: Option) { + *self.on_packet.borrow_mut() = callback; + } + + /// Emits packet information to the JavaScript callback. + #[cfg(target_arch = "wasm32")] + fn emit_packet_info(&self, direction: &str, packet_bytes: &[u8]) { + if let Some(callback) = self.on_packet.borrow().as_ref() { + // Convert packet bytes to JavaScript Uint8Array + let bytes_array = js_sys::Uint8Array::new_with_length(packet_bytes.len() as u32); + bytes_array.copy_from(packet_bytes); + + // Extract GFI and channel from header (first 2 bytes) + let gfi = if packet_bytes.len() >= 1 { + (packet_bytes[0] & 0xf0) >> 4 + } else { + 0 + }; + let channel = if packet_bytes.len() >= 2 { + ((u16::from(packet_bytes[0]) & 0x0f) << 8) | u16::from(packet_bytes[1]) + } else { + 0 + }; + + // Decode packet to get type and details + let (packet_type, details) = match X25Packet::decode(Bytes::copy_from_slice(packet_bytes)) { + Ok(X25Packet::CallRequest(ref p)) => { + let obj = js_sys::Object::new(); + js_sys::Reflect::set(&obj, &"called".into(), &p.called_addr.to_string().into()).unwrap(); + js_sys::Reflect::set(&obj, &"calling".into(), &p.calling_addr.to_string().into()).unwrap(); + + // Format facilities as a string + let facilities_str = if p.facilities.is_empty() { + "None".to_string() + } else { + format!("{} facilities", p.facilities.len()) + }; + js_sys::Reflect::set(&obj, &"facilities".into(), &facilities_str.into()).unwrap(); + + // Call user data + let cud_len = p.call_user_data.len(); + js_sys::Reflect::set(&obj, &"call_user_data_len".into(), &(cud_len as u32).into()).unwrap(); + + ("CallRequest", obj) + } + Ok(X25Packet::CallAccept(ref p)) => { + let obj = js_sys::Object::new(); + js_sys::Reflect::set(&obj, &"called".into(), &p.called_addr.to_string().into()).unwrap(); + js_sys::Reflect::set(&obj, &"calling".into(), &p.calling_addr.to_string().into()).unwrap(); + + // Format facilities as a string + let facilities_str = if p.facilities.is_empty() { + "None".to_string() + } else { + format!("{} facilities", p.facilities.len()) + }; + js_sys::Reflect::set(&obj, &"facilities".into(), &facilities_str.into()).unwrap(); + + ("CallAccept", obj) + } + Ok(X25Packet::ClearRequest(ref p)) => { + let obj = js_sys::Object::new(); + js_sys::Reflect::set(&obj, &"cause".into(), &(p.cause_code as u32).into()).unwrap(); + js_sys::Reflect::set(&obj, &"diag".into(), &(p.diagnostic_code as u32).into()).unwrap(); + ("ClearRequest", obj) + } + Ok(X25Packet::ClearConfirm(_)) => ("ClearConfirm", js_sys::Object::new()), + Ok(X25Packet::Data(ref p)) => { + let obj = js_sys::Object::new(); + js_sys::Reflect::set(&obj, &"PS".into(), &(p.send_seq as u32).into()).unwrap(); + js_sys::Reflect::set(&obj, &"PR".into(), &(p.recv_seq as u32).into()).unwrap(); + js_sys::Reflect::set(&obj, &"Q".into(), &p.qualifier.into()).unwrap(); + js_sys::Reflect::set(&obj, &"D".into(), &p.delivery.into()).unwrap(); + js_sys::Reflect::set(&obj, &"M".into(), &p.more.into()).unwrap(); + js_sys::Reflect::set(&obj, &"len".into(), &(p.user_data.len() as u32).into()).unwrap(); + + // If Q-bit is set, try to parse as X.29 PAD message + if p.qualifier { + match X29PadMessage::decode(p.user_data.clone()) { + Ok(X29PadMessage::Set(params)) => { + js_sys::Reflect::set(&obj, &"x29_type".into(), &"Set".into()).unwrap(); + js_sys::Reflect::set(&obj, &"x29_params".into(), &(params.len() as u32).into()).unwrap(); + } + Ok(X29PadMessage::Read(params)) => { + js_sys::Reflect::set(&obj, &"x29_type".into(), &"Read".into()).unwrap(); + js_sys::Reflect::set(&obj, &"x29_params".into(), &(params.len() as u32).into()).unwrap(); + } + Ok(X29PadMessage::SetRead(params)) => { + js_sys::Reflect::set(&obj, &"x29_type".into(), &"SetRead".into()).unwrap(); + js_sys::Reflect::set(&obj, &"x29_params".into(), &(params.len() as u32).into()).unwrap(); + } + Ok(X29PadMessage::Indicate(params)) => { + js_sys::Reflect::set(&obj, &"x29_type".into(), &"Indicate".into()).unwrap(); + js_sys::Reflect::set(&obj, &"x29_params".into(), &(params.len() as u32).into()).unwrap(); + } + Ok(X29PadMessage::ClearInvitation) => { + js_sys::Reflect::set(&obj, &"x29_type".into(), &"ClearInvitation".into()).unwrap(); + } + Err(_) => { + // Not a valid X.29 message + } + } + } + + ("Data", obj) + } + Ok(X25Packet::ReceiveReady(ref p)) => { + let obj = js_sys::Object::new(); + js_sys::Reflect::set(&obj, &"PR".into(), &(p.recv_seq as u32).into()).unwrap(); + ("RR", obj) + } + Ok(X25Packet::ReceiveNotReady(ref p)) => { + let obj = js_sys::Object::new(); + js_sys::Reflect::set(&obj, &"PR".into(), &(p.recv_seq as u32).into()).unwrap(); + ("RNR", obj) + } + Ok(X25Packet::ResetRequest(ref p)) => { + let obj = js_sys::Object::new(); + js_sys::Reflect::set(&obj, &"cause".into(), &(p.cause_code as u32).into()).unwrap(); + js_sys::Reflect::set(&obj, &"diag".into(), &(p.diagnostic_code as u32).into()).unwrap(); + ("ResetRequest", obj) + } + Ok(X25Packet::ResetConfirm(_)) => ("ResetConfirm", js_sys::Object::new()), + _ => ("Unknown", js_sys::Object::new()), + }; + + // Create packet info object + let info = js_sys::Object::new(); + js_sys::Reflect::set(&info, &"direction".into(), &direction.into()).unwrap(); + js_sys::Reflect::set(&info, &"timestamp".into(), &js_sys::Date::now().into()).unwrap(); + js_sys::Reflect::set(&info, &"type".into(), &packet_type.into()).unwrap(); + js_sys::Reflect::set(&info, &"gfi".into(), &(gfi as u32).into()).unwrap(); + js_sys::Reflect::set(&info, &"channel".into(), &(channel as u32).into()).unwrap(); + js_sys::Reflect::set(&info, &"details".into(), &details).unwrap(); + js_sys::Reflect::set(&info, &"size".into(), &(packet_bytes.len() as u32).into()).unwrap(); + js_sys::Reflect::set(&info, &"bytes".into(), &bytes_array).unwrap(); + + // Call the callback + let _ = callback.call1(&wasm_bindgen::JsValue::NULL, &info); + } + } + + /// Initiates a call to the specified X.121 address. + /// + /// Returns a Future that resolves when the call is accepted or rejects on error. + #[cfg(target_arch = "wasm32")] + pub async fn call(&self, addr: &X121Addr, call_user_data: &[u8]) -> Result<(), JsValue> { + *self.state.borrow_mut() = ConnectionState::Calling; + + // Build and send Call Request packet + let call_request = self.build_call_request(addr, call_user_data); + let mut buf = BytesMut::new(); + call_request.encode(&mut buf) + .map_err(|e| JsValue::from_str(&format!("Failed to encode call request: {}", e)))?; + + self.send_packet(&buf) + .await + .map_err(|e| JsValue::from_str(&format!("Failed to send call request: {}", e)))?; + + // Wait for Call Accept + let response = self + .recv_packet() + .await + .map_err(|e| JsValue::from_str(&format!("Failed to receive response: {}", e)))?; + + match X25Packet::decode(response) { + Ok(X25Packet::CallAccept(accept)) => { + // Extract negotiated parameters from facilities + let mut params = self.params.clone(); + for facility in &accept.facilities { + match facility { + X25Facility::PacketSize { from_called, from_calling } => { + params.recv_packet_size = *from_calling; + params.send_packet_size = *from_called; + } + X25Facility::WindowSize { from_called, from_calling } => { + params.recv_window_size = *from_calling; + params.send_window_size = *from_called; + } + _ => {} // Ignore other facilities + } + } + + *self.state.borrow_mut() = ConnectionState::Connected; + Ok(()) + } + Ok(X25Packet::ClearRequest(clear)) => { + *self.state.borrow_mut() = ConnectionState::Closed; + Err(JsValue::from_str(&format!( + "Call rejected: cause={}, diag={}", + clear.cause_code, clear.diagnostic_code + ))) + } + Ok(other) => Err(JsValue::from_str(&format!( + "Unexpected packet type: {:?}", + other + ))), + Err(e) => Err(JsValue::from_str(&format!("Failed to decode packet: {}", e))), + } + } + + /// Sends data to the remote host. + #[cfg(target_arch = "wasm32")] + pub async fn send(&self, data: &[u8]) -> Result<(), JsValue> { + if *self.state.borrow() != ConnectionState::Connected { + return Err(JsValue::from_str("Not connected")); + } + + // Fragment data into X.25 packets if needed + let max_data_size = self.params.send_packet_size as usize - 3; // Account for X.25 header + + for chunk in data.chunks(max_data_size) { + let more = chunk.len() == max_data_size && !chunk.is_empty(); + + // Get current send sequence and increment it + let current_send_seq = *self.send_seq.borrow(); + let next_send_seq = (current_send_seq + 1) % 8; + *self.send_seq.borrow_mut() = next_send_seq; + + let data_packet = X25Data { + channel: self.channel, + qualifier: false, + delivery: false, + modulo: self.params.modulo, + send_seq: current_send_seq, + recv_seq: *self.recv_seq.borrow(), + user_data: Bytes::copy_from_slice(chunk), + more, + }; + + let mut buf = BytesMut::new(); + data_packet.encode(&mut buf) + .map_err(|e| JsValue::from_str(&format!("Failed to encode data packet: {}", e)))?; + self.send_packet(&buf) + .await + .map_err(|e| JsValue::from_str(&format!("Failed to send data: {}", e)))?; + } + + Ok(()) + } + + /// Receives data from the remote host. + /// + /// Returns None if no data is available yet. + #[cfg(target_arch = "wasm32")] + pub async fn recv(&self) -> Result, JsValue> { + let state = self.state.borrow().clone(); + + // Allow receiving packets while Connected or Clearing (to receive ClearConfirm) + if state != ConnectionState::Connected && state != ConnectionState::Clearing { + return Ok(None); + } + + // Check if we have buffered data + { + let mut buffer = self.recv_buffer.borrow_mut(); + if !buffer.is_empty() { + let data: Vec = buffer.drain(..).collect(); + return Ok(Some(Bytes::from(data))); + } + } + + // Try to receive a packet (non-blocking) + match self.try_recv_packet().await { + Ok(Some(packet_bytes)) => { + // Log packet reception for debugging + web_sys::console::log_1(&JsValue::from_str(&format!( + "Received packet ({} bytes)", + packet_bytes.len() + ))); + + match X25Packet::decode(packet_bytes) { + Ok(X25Packet::Data(data)) => { + // Increment recv_seq and send RR + let new_seq = (*self.recv_seq.borrow() + 1) % 8; + *self.recv_seq.borrow_mut() = new_seq; + self.send_rr().await + .map_err(|e| JsValue::from_str(&format!("Failed to send RR: {}", e)))?; + + // Check if this is an X.29 message (Q bit set) + if data.qualifier { + self.handle_x29_message(&data.user_data).await?; + Ok(None) // X.29 messages don't produce user data + } else { + // Regular data + self.recv_buffer.borrow_mut().extend(&data.user_data); + + // Return the data + let mut buffer = self.recv_buffer.borrow_mut(); + let data: Vec = buffer.drain(..).collect(); + Ok(Some(Bytes::from(data))) + } + } + Ok(X25Packet::ClearRequest(clear)) => { + web_sys::console::log_1(&JsValue::from_str(&format!( + "Received ClearRequest: cause={}, diag={}", + clear.cause_code, clear.diagnostic_code + ))); + + // Send ClearConfirm + self.send_clear_confirm().await + .map_err(|e| JsValue::from_str(&format!("Failed to send ClearConfirm: {}", e)))?; + + *self.state.borrow_mut() = ConnectionState::Closed; + + // Return X.28 format clear status with cause and diagnostic codes + Err(JsValue::from_str(&format!("CLR REMOTE C:{} D:{}", clear.cause_code, clear.diagnostic_code))) + } + Ok(X25Packet::ReceiveReady(_)) | Ok(X25Packet::ReceiveNotReady(_)) => { + // Acknowledgment, no data + Ok(None) + } + Ok(X25Packet::ResetRequest(reset)) => { + // Remote is requesting to reset the VC - acknowledge it + web_sys::console::log_1(&JsValue::from_str(&format!( + "Received ResetRequest: cause={}, diag={}", + reset.cause_code, reset.diagnostic_code + ))); + self.send_reset_confirm().await + .map_err(|e| JsValue::from_str(&format!("Failed to send ResetConfirm: {}", e)))?; + // Reset both send and receive sequence numbers + *self.recv_seq.borrow_mut() = 0; + *self.send_seq.borrow_mut() = 0; + Ok(None) + } + Ok(X25Packet::ClearConfirm(_)) => { + // Remote has confirmed our Clear Request + web_sys::console::log_1(&JsValue::from_str("Received ClearConfirm")); + + *self.state.borrow_mut() = ConnectionState::Closed; + + // Return X.28 format clear confirmation + Err(JsValue::from_str("CLR CONF")) + } + Ok(other) => { + // Other packet types - ignore for now + web_sys::console::log_1(&JsValue::from_str(&format!( + "Ignoring packet type: {:?}", + other + ))); + Ok(None) + } + Err(e) => Err(JsValue::from_str(&format!("Failed to decode packet: {}", e))), + } + } + Ok(None) => Ok(None), + Err(e) => Err(JsValue::from_str(&format!("Failed to receive packet: {}", e))), + } + } + + /// Clears the connection. + #[cfg(target_arch = "wasm32")] + pub async fn clear(&self) -> Result<(), JsValue> { + if *self.state.borrow() == ConnectionState::Closed { + return Ok(()); + } + + *self.state.borrow_mut() = ConnectionState::Clearing; + + let clear_request = X25ClearRequest { + channel: self.channel, + modulo: self.params.modulo, + cause_code: 0, // DTE originated + diagnostic_code: 0, + called_addr: self.params.addr.clone(), + calling_addr: self.params.addr.clone(), + facilities: vec![], + clear_user_data: Bytes::new(), + }; + + let mut buf = BytesMut::new(); + clear_request.encode(&mut buf) + .map_err(|e| JsValue::from_str(&format!("Failed to encode clear request: {}", e)))?; + self.send_packet(&buf) + .await + .map_err(|e| JsValue::from_str(&format!("Failed to send clear request: {}", e)))?; + + *self.state.borrow_mut() = ConnectionState::Closed; + Ok(()) + } + + /// Sends an X.29 Clear Invitation to the remote PAD. + #[cfg(target_arch = "wasm32")] + pub async fn send_clear_invitation(&self) -> Result<(), JsValue> { + let message = X29PadMessage::ClearInvitation; + self.send_x29_message(&message).await + } + + /// Sends an X.29 Read message to request remote PAD parameters. + /// The response will be delivered via the onParams callback. + #[cfg(target_arch = "wasm32")] + pub async fn read_remote_params(&self, params: Vec) -> Result<(), JsValue> { + let message = X29PadMessage::Read(params); + self.send_x29_message(&message).await + } + + /// Sends an X.29 SetRead message to set and read back remote PAD parameters. + /// The response will be delivered via the onParams callback. + #[cfg(target_arch = "wasm32")] + pub async fn setread_remote_params(&self, params: Vec<(u8, u8)>) -> Result<(), JsValue> { + let message = X29PadMessage::SetRead(params); + self.send_x29_message(&message).await + } + + /// Retrieves and clears any pending local PAD parameter updates from X.29 Set/SetRead messages. + /// + /// Returns Some(params) if parameters were updated, None otherwise. + /// After calling this, the parameter updates are cleared. + #[cfg(target_arch = "wasm32")] + pub fn take_param_updates(&self) -> Option> { + self.param_updates.borrow_mut().take() + } + + /// Retrieves and clears any pending remote PAD parameter indications from X.29 Indicate messages. + /// + /// Returns Some(params) if remote parameters were received, None otherwise. + /// After calling this, the remote parameter updates are cleared. + #[cfg(target_arch = "wasm32")] + pub fn take_remote_param_updates(&self) -> Option> { + self.remote_param_updates.borrow_mut().take() + } + + fn build_call_request(&self, addr: &X121Addr, call_user_data: &[u8]) -> X25CallRequest { + let mut facilities = vec![]; + + // Always add packet size facility (required for PAD calls) + facilities.push(X25Facility::PacketSize { + from_called: self.params.send_packet_size, + from_calling: self.params.recv_packet_size, + }); + + // Always add window size facility (required for PAD calls) + facilities.push(X25Facility::WindowSize { + from_called: self.params.send_window_size, + from_calling: self.params.recv_window_size, + }); + + X25CallRequest { + channel: self.channel, + modulo: self.params.modulo, + calling_addr: self.params.addr.clone(), + called_addr: addr.clone(), + facilities, + call_user_data: Bytes::copy_from_slice(call_user_data), + } + } + + #[cfg(target_arch = "wasm32")] + async fn send_packet(&self, packet: &[u8]) -> io::Result<()> { + // Emit packet info for inspection + self.emit_packet_info("send", packet); + + let mut link = self.link.borrow_mut(); + link.send(packet)?; + Ok(()) + } + + #[cfg(target_arch = "wasm32")] + async fn recv_packet(&self) -> io::Result { + // This is a simplified blocking recv - in a real implementation, + // you'd want to properly integrate with the browser event loop + loop { + let mut link = self.link.borrow_mut(); + match link.recv() { + Ok(packet) => return Ok(packet), + Err(e) if e.kind() == io::ErrorKind::WouldBlock => { + // Drop the borrow before yielding + drop(link); + // Yield to the browser event loop + yield_now().await; + } + Err(e) => return Err(e), + } + } + } + + #[cfg(target_arch = "wasm32")] + async fn try_recv_packet(&self) -> io::Result> { + let mut link = self.link.borrow_mut(); + match link.recv() { + Ok(packet) => { + // Emit packet info for inspection + drop(link); // Drop borrow before calling emit + self.emit_packet_info("recv", &packet); + Ok(Some(packet)) + } + Err(e) if e.kind() == io::ErrorKind::WouldBlock => Ok(None), + Err(e) => Err(e), + } + } + + /// Sends an RR (Receive Ready) packet to acknowledge received data. + #[cfg(target_arch = "wasm32")] + async fn send_rr(&self) -> io::Result<()> { + let rr = X25ReceiveReady { + channel: self.channel, + modulo: self.params.modulo, + recv_seq: *self.recv_seq.borrow(), + }; + + let mut buf = BytesMut::new(); + rr.encode(&mut buf).map_err(io::Error::other)?; + self.send_packet(&buf).await + } + + /// Sends a Reset Confirm in response to a Reset Request. + #[cfg(target_arch = "wasm32")] + async fn send_reset_confirm(&self) -> io::Result<()> { + let reset_conf = X25ResetConfirm { + channel: self.channel, + modulo: self.params.modulo, + }; + + let mut buf = BytesMut::new(); + reset_conf.encode(&mut buf).map_err(io::Error::other)?; + self.send_packet(&buf).await + } + + /// Sends a Clear Confirm in response to a Clear Request. + #[cfg(target_arch = "wasm32")] + async fn send_clear_confirm(&self) -> io::Result<()> { + let clear_conf = X25ClearConfirm { + channel: self.channel, + modulo: self.params.modulo, + called_addr: self.params.addr.clone(), + calling_addr: self.params.addr.clone(), + facilities: vec![], + }; + + let mut buf = BytesMut::new(); + clear_conf.encode(&mut buf).map_err(io::Error::other)?; + self.send_packet(&buf).await + } + + /// Handles X.29 PAD messages and responds appropriately. + #[cfg(target_arch = "wasm32")] + async fn handle_x29_message(&self, data: &[u8]) -> Result<(), JsValue> { + let message = X29PadMessage::decode(Bytes::copy_from_slice(data)) + .map_err(|e| JsValue::from_str(&format!("Failed to decode X.29 message: {}", e)))?; + + web_sys::console::log_1(&JsValue::from_str(&format!("X.29 message: {:?}", message))); + + match message { + X29PadMessage::Read(_params) => { + // Server is asking for our PAD parameters + // Send default parameters + self.send_pad_parameters().await?; + } + X29PadMessage::SetRead(params) => { + // Server is setting parameters and asking for confirmation + // Store the parameter updates for JavaScript to retrieve + *self.param_updates.borrow_mut() = Some(params.clone()); + // Echo them back + self.send_pad_parameters_values(params).await?; + } + X29PadMessage::Set(params) => { + // Server is setting parameters (no response needed) + // Store the parameter updates for JavaScript to retrieve + *self.param_updates.borrow_mut() = Some(params); + } + X29PadMessage::ClearInvitation => { + // Remote PAD is requesting we disconnect + web_sys::console::log_1(&JsValue::from_str("Received X.29 Clear Invitation")); + + // Send Clear Request to initiate disconnect + *self.state.borrow_mut() = ConnectionState::Clearing; + + let clear_request = X25ClearRequest { + channel: self.channel, + modulo: self.params.modulo, + cause_code: 0, // DTE originated + diagnostic_code: 0, + called_addr: self.params.addr.clone(), + calling_addr: self.params.addr.clone(), + facilities: vec![], + clear_user_data: Bytes::new(), + }; + + let mut buf = BytesMut::new(); + clear_request.encode(&mut buf) + .map_err(|e| JsValue::from_str(&format!("Failed to encode clear request: {}", e)))?; + + web_sys::console::log_1(&JsValue::from_str("Sending Clear Request")); + + self.send_packet(&buf) + .await + .map_err(|e| JsValue::from_str(&format!("Failed to send clear request: {}", e)))?; + + web_sys::console::log_1(&JsValue::from_str("Clear Request sent successfully")); + } + X29PadMessage::Indicate(params) => { + // Response to Read or SetRead - these are REMOTE parameter values + // Store them separately so JavaScript can display them without modifying local params + let param_values: Vec<(u8, u8)> = params.into_iter() + .filter_map(|(param, result)| result.ok().map(|value| (param, value))) + .collect(); + + if !param_values.is_empty() { + *self.remote_param_updates.borrow_mut() = Some(param_values); + } + } + } + + Ok(()) + } + + /// Sends default PAD parameters in response to a Read message. + #[cfg(target_arch = "wasm32")] + async fn send_pad_parameters(&self) -> Result<(), JsValue> { + // Send common PAD parameters + let params = vec![ + (2, Ok(1)), // Echo + (3, Ok(126)), // Data forwarding (all chars) + (4, Ok(0)), // Idle timer disabled + (13, Ok(0)), // Line feed insert + (15, Ok(0)), // Editing + (16, Ok(127)), // Character delete + (17, Ok(24)), // Line delete + (18, Ok(18)), // Line display + ]; + + let indication = X29PadMessage::Indicate(params); + self.send_x29_message(&indication).await + } + + /// Sends PAD parameter values in response to SetRead. + #[cfg(target_arch = "wasm32")] + async fn send_pad_parameters_values(&self, params: Vec<(u8, u8)>) -> Result<(), JsValue> { + // Echo back the parameters as "OK" + let response: Vec<(u8, Result)> = params.iter() + .map(|(param, value)| (*param, Ok(*value))) + .collect(); + + let indication = X29PadMessage::Indicate(response); + self.send_x29_message(&indication).await + } + + /// Sends an X.29 PAD message with the Q bit set. + #[cfg(target_arch = "wasm32")] + async fn send_x29_message(&self, message: &X29PadMessage) -> Result<(), JsValue> { + let mut user_data = BytesMut::new(); + message.encode(&mut user_data); + + // Get current send sequence and increment it + let current_send_seq = *self.send_seq.borrow(); + let next_send_seq = (current_send_seq + 1) % 8; + *self.send_seq.borrow_mut() = next_send_seq; + + let data_packet = X25Data { + channel: self.channel, + qualifier: true, // Q bit set for X.29 messages + delivery: false, + modulo: self.params.modulo, + send_seq: current_send_seq, + recv_seq: *self.recv_seq.borrow(), + user_data: user_data.freeze(), + more: false, + }; + + let mut buf = BytesMut::new(); + data_packet.encode(&mut buf) + .map_err(|e| JsValue::from_str(&format!("Failed to encode X.29 packet: {}", e)))?; + self.send_packet(&buf) + .await + .map_err(|e| JsValue::from_str(&format!("Failed to send X.29 message: {}", e)))?; + + Ok(()) + } +} + +/// Yields control back to the browser event loop. +#[cfg(target_arch = "wasm32")] +async fn yield_now() { + sleep_ms(10).await; +} + +/// Sleep for a specified number of milliseconds. +#[cfg(target_arch = "wasm32")] +async fn sleep_ms(ms: i32) { + use wasm_bindgen::closure::Closure; + use wasm_bindgen::JsCast; + + let promise = js_sys::Promise::new(&mut |resolve, _reject| { + let window = web_sys::window().expect("no window"); + let closure = Closure::once(move || { + resolve.call0(&JsValue::NULL).unwrap(); + }); + window + .set_timeout_with_callback_and_timeout_and_arguments_0( + closure.as_ref().unchecked_ref(), + ms, + ) + .unwrap(); + closure.forget(); + }); + + JsFuture::from(promise).await.unwrap(); +} diff --git a/libxotpad/src/x25/mod.rs b/libxotpad/src/x25/mod.rs index 996a84f..4398726 100644 --- a/libxotpad/src/x25/mod.rs +++ b/libxotpad/src/x25/mod.rs @@ -13,4 +13,7 @@ mod vc; pub use self::packet::{MAX_PACKET_LEN, MIN_PACKET_LEN}; pub use self::params::X25Params; pub use self::seq::X25Modulo; -pub use self::vc::{Svc, SvcIncomingCall, Vc}; +pub use self::vc::Vc; + +#[cfg(not(target_arch = "wasm32"))] +pub use self::vc::{Svc, SvcIncomingCall}; diff --git a/libxotpad/src/x25/packet.rs b/libxotpad/src/x25/packet.rs index 7657f88..e3d6841 100644 --- a/libxotpad/src/x25/packet.rs +++ b/libxotpad/src/x25/packet.rs @@ -370,16 +370,14 @@ impl X25ClearRequest { buf.put_u8(self.cause_code); len += 1; - let has_diagnostic_code = self.diagnostic_code > 0; + // Diagnostic code is always present in Clear Request + buf.put_u8(self.diagnostic_code); + len += 1; + let has_addr = !self.called_addr.is_null() || !self.calling_addr.is_null(); let has_facilities = !self.facilities.is_empty(); let has_clear_user_data = !self.clear_user_data.is_empty(); - if has_diagnostic_code || has_addr || has_facilities || has_clear_user_data { - buf.put_u8(self.diagnostic_code); - len += 1; - } - // When the extended format is used, the address block and the facilities // block must be present. if has_addr || has_facilities || has_clear_user_data { diff --git a/libxotpad/src/x25/vc.rs b/libxotpad/src/x25/vc.rs index 96e3136..3881792 100644 --- a/libxotpad/src/x25/vc.rs +++ b/libxotpad/src/x25/vc.rs @@ -19,7 +19,7 @@ use crate::x25::packet::{ }; use crate::x25::params::X25Params; use crate::x25::seq::{next_seq, Window, X25Modulo}; -use crate::xot::XotLink; +use crate::xot::PlatformXotLink; /// X.25 virtual circuit. pub trait Vc { @@ -76,11 +76,14 @@ enum ClearInitiator { } /// X.25 _switched_ virtual circuit, or _virtual call_. +/// Only available on native platforms (not WASM). +#[cfg(not(target_arch = "wasm32"))] pub struct Svc(Arc); +#[cfg(not(target_arch = "wasm32"))] impl Svc { pub fn call( - link: XotLink, + link: PlatformXotLink, channel: u16, addr: &X121Addr, call_user_data: &[u8], @@ -151,7 +154,7 @@ impl Svc { } pub fn listen_timeout( - link: XotLink, + link: PlatformXotLink, channel: u16, params: &X25Params, duration: Duration, @@ -269,7 +272,7 @@ impl Svc { } } - fn new(link: XotLink, channel: u16, params: &X25Params) -> Self { + fn new(link: PlatformXotLink, channel: u16, params: &X25Params) -> Self { let (send_link, recv_link) = split_xot_link(link); let inner = Arc::new(VcInner::new(send_link, channel, params)); @@ -293,8 +296,10 @@ impl Svc { } /// Incoming X.25 _call_ that can be accepted, or cleared. +#[cfg(not(target_arch = "wasm32"))] pub struct SvcIncomingCall(Svc, X25CallRequest); +#[cfg(not(target_arch = "wasm32"))] impl SvcIncomingCall { pub fn request(&self) -> &X25CallRequest { &self.1 @@ -366,6 +371,7 @@ impl SvcIncomingCall { } } +#[cfg(not(target_arch = "wasm32"))] impl Vc for Svc { fn send(&self, user_data: Bytes, qualifier: bool) -> io::Result<()> { let inner = &self.0; @@ -526,6 +532,7 @@ impl Vc for Svc { } } +#[cfg(not(target_arch = "wasm32"))] impl Clone for Svc { fn clone(&self) -> Self { // TODO: is an appropriate way to do this, it may be better to "split" into a read @@ -534,8 +541,9 @@ impl Clone for Svc { } } +#[cfg(not(target_arch = "wasm32"))] struct VcInner { - send_link: Arc>, + send_link: Arc>, engine_wait: Arc, channel: u16, state: Arc<(Mutex, Condvar)>, @@ -544,14 +552,16 @@ struct VcInner { recv_data_queue: Arc<(Mutex>, Condvar)>, } +#[cfg(not(target_arch = "wasm32"))] struct SendData { user_data: Bytes, qualifier: bool, more: bool, } +#[cfg(not(target_arch = "wasm32"))] impl VcInner { - fn new(send_link: XotLink, channel: u16, params: &X25Params) -> Self { + fn new(send_link: PlatformXotLink, channel: u16, params: &X25Params) -> Self { let state = VcState::Ready; VcInner { @@ -565,7 +575,7 @@ impl VcInner { } } - fn run(&self, mut recv_link: XotLink, barrier: &Arc) { + fn run(&self, mut recv_link: PlatformXotLink, barrier: &Arc) { // Create another thread that reads packets, this allows the main loop // wait to be interrupted while the XOT socket read is blocked. let recv_queue = Arc::new(Mutex::new(VecDeque::>::new())); @@ -1039,6 +1049,7 @@ impl VcInner { } } +#[cfg(not(target_arch = "wasm32"))] fn create_call_request( channel: u16, addr: &X121Addr, @@ -1066,6 +1077,7 @@ fn create_call_request( } } +#[cfg(not(target_arch = "wasm32"))] fn create_call_accept(channel: u16, params: &X25Params) -> X25CallAccept { let facilities = vec![ X25Facility::PacketSize { @@ -1088,6 +1100,7 @@ fn create_call_accept(channel: u16, params: &X25Params) -> X25CallAccept { } } +#[cfg(not(target_arch = "wasm32"))] fn negotiate_calling_params(call_accept: &X25CallAccept, params: &X25Params) -> X25Params { let mut params = params.clone(); @@ -1110,6 +1123,7 @@ fn negotiate_calling_params(call_accept: &X25CallAccept, params: &X25Params) -> params } +#[cfg(not(target_arch = "wasm32"))] fn negotiate_called_params(call_request: &X25CallRequest, params: &X25Params) -> X25Params { let mut params = params.clone(); @@ -1132,6 +1146,7 @@ fn negotiate_called_params(call_request: &X25CallRequest, params: &X25Params) -> params } +#[cfg(not(target_arch = "wasm32"))] fn get_packet_size(facilities: &[X25Facility]) -> Option<(usize, usize)> { facilities.iter().find_map(|f| match f { X25Facility::PacketSize { @@ -1142,6 +1157,7 @@ fn get_packet_size(facilities: &[X25Facility]) -> Option<(usize, usize)> { }) } +#[cfg(not(target_arch = "wasm32"))] fn get_window_size(facilities: &[X25Facility]) -> Option<(u8, u8)> { facilities.iter().find_map(|f| match f { X25Facility::WindowSize { @@ -1152,6 +1168,7 @@ fn get_window_size(facilities: &[X25Facility]) -> Option<(u8, u8)> { }) } +#[cfg(not(target_arch = "wasm32"))] fn clamp_window_size(size: u8, modulo: X25Modulo) -> u8 { min(size, (modulo as u8) - 1) } @@ -1174,6 +1191,7 @@ impl DataTransferState { } } +#[cfg(not(target_arch = "wasm32"))] fn pop_complete_data(queue: &mut VecDeque) -> Option<(Bytes, bool)> { if queue.is_empty() { return None; @@ -1196,12 +1214,13 @@ fn pop_complete_data(queue: &mut VecDeque) -> Option<(Bytes, bool)> { Some((user_data.freeze(), qualifier)) } -fn split_xot_link(link: XotLink) -> (XotLink, XotLink) { +#[cfg(not(target_arch = "wasm32"))] +fn split_xot_link(link: PlatformXotLink) -> (PlatformXotLink, PlatformXotLink) { // crazy hack... let tcp_stream = link.into_stream(); ( - XotLink::new(tcp_stream.try_clone().unwrap()), - XotLink::new(tcp_stream), + PlatformXotLink::new(tcp_stream.try_clone().unwrap()), + PlatformXotLink::new(tcp_stream), ) } diff --git a/libxotpad/src/xot/link.rs b/libxotpad/src/xot/link.rs index 806b310..756238f 100644 --- a/libxotpad/src/xot/link.rs +++ b/libxotpad/src/xot/link.rs @@ -1,21 +1,21 @@ use bytes::{BufMut, Bytes, BytesMut}; -use std::io::{self, Read, Write}; -use std::net::{Shutdown, TcpStream}; +use std::io; +use crate::stream::Stream; use crate::x25; -/// XOT link allowing X.25 packets to be transmitted over a `TcpStream`. -pub struct XotLink { - stream: TcpStream, +/// XOT link allowing X.25 packets to be transmitted over a stream. +pub struct XotLink { + stream: S, recv_buf: BytesMut, } -impl XotLink { - /// Creates a new `XotLink` over the underlying `TcpStream`. +impl XotLink { + /// Creates a new `XotLink` over the underlying stream. /// - /// This will set the `TCP_NODELAY` option on the provided socket. - pub fn new(stream: TcpStream) -> Self { - stream.set_nodelay(true).expect("set_nodelay error"); + /// This will set the `TCP_NODELAY` option if supported. + pub fn new(stream: S) -> Self { + let _ = stream.set_nodelay(true); XotLink { stream, @@ -55,16 +55,16 @@ impl XotLink { } } - /// Unwraps this `XotLink`, returning the underlying `TcpStream`. + /// Unwraps this `XotLink`, returning the underlying stream. /// /// Note that any leftover data in the internal buffer is lost. Therefore, a - /// following read from the underlying `TcpStream` may lead to data loss. - pub fn into_stream(self) -> TcpStream { + /// following read from the underlying stream may lead to data loss. + pub fn into_stream(self) -> S { self.stream } pub(crate) fn shutdown(&mut self) -> io::Result<()> { - self.stream.shutdown(Shutdown::Both) + self.stream.shutdown() } } diff --git a/libxotpad/src/xot/mod.rs b/libxotpad/src/xot/mod.rs index 1b4e9d8..348e9d7 100644 --- a/libxotpad/src/xot/mod.rs +++ b/libxotpad/src/xot/mod.rs @@ -7,7 +7,6 @@ //! [IETF RFC 1613]: https://tools.ietf.org/html/rfc1613 use std::io; -use std::net::TcpStream; use crate::x121::X121Addr; @@ -20,7 +19,18 @@ pub use self::resolver::XotResolver; /// Registered XOT TCP port number. pub const TCP_PORT: u16 = 1998; -pub fn connect(addr: &X121Addr, resolver: &XotResolver) -> io::Result { +// Type aliases for platform-specific XotLink types +#[cfg(not(target_arch = "wasm32"))] +pub type PlatformXotLink = XotLink; + +#[cfg(target_arch = "wasm32")] +pub type PlatformXotLink = XotLink; + +// Native implementation using TcpStream +#[cfg(not(target_arch = "wasm32"))] +pub fn connect(addr: &X121Addr, resolver: &XotResolver) -> io::Result { + use std::net::TcpStream; + let Some(xot_gateway) = resolver.lookup(addr) else { // TODO: HostUnreachable... return Err(io::Error::new( @@ -36,6 +46,55 @@ pub fn connect(addr: &X121Addr, resolver: &XotResolver) -> io::Result { Ok(xot_link) } +// WASM implementation using WebSocket +#[cfg(target_arch = "wasm32")] +pub fn connect(addr: &X121Addr, resolver: &XotResolver) -> io::Result { + use crate::stream::WsStream; + + let Some(xot_gateway) = resolver.lookup(addr) else { + return Err(io::Error::new( + io::ErrorKind::Other, + "no XOT gateway found".to_owned(), + )); + }; + + // Construct WebSocket URL for websockify + // Format: ws://host:port/tcp/gateway:1998 + let ws_url = format!("ws://{}/tcp/{}:{}", + get_websockify_host(), + xot_gateway, + TCP_PORT + ); + + let ws_stream = WsStream::connect(&ws_url) + .map_err(|e| io::Error::new(io::ErrorKind::Other, format!("WebSocket connection failed: {:?}", e)))?; + + // Wait for connection to be ready (this is a simplification) + // In a real implementation, you'd want to handle this asynchronously + while !ws_stream.is_ready() { + // Yield to browser event loop + // Note: This is blocking and not ideal - see below for async alternative + } + + let xot_link = XotLink::new(ws_stream); + + Ok(xot_link) +} + +/// Gets the WebSocket relay host from the window location. +/// In WASM, connections must go through the same origin or a configured relay. +#[cfg(target_arch = "wasm32")] +fn get_websockify_host() -> String { + use wasm_bindgen::JsCast; + + let window = web_sys::window().expect("no window"); + let location = window.location(); + let host = location.host().expect("no host"); + + // Return the same host as the page was served from + host +} + #[cfg(fuzzing)] pub mod fuzzing { use bytes::{Bytes, BytesMut}; diff --git a/src/lib.rs b/src/lib.rs index b696222..c04db58 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,18 @@ +#[cfg(not(target_arch = "wasm32"))] pub mod host_pad; +#[cfg(not(target_arch = "wasm32"))] pub mod user_pad; +#[cfg(not(target_arch = "wasm32"))] mod util; +#[cfg(not(target_arch = "wasm32"))] pub mod x28; +#[cfg(not(target_arch = "wasm32"))] pub mod x3; +#[cfg(target_arch = "wasm32")] +pub mod wasm; + +#[cfg(target_arch = "wasm32")] +pub use wasm::WasmPad; + pub static ABOUT: &str = "X.25 PAD for XOT"; diff --git a/src/main.rs b/src/main.rs index e265d6c..4da5228 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +#[cfg(feature = "native")] use clap::Parser; use libxotpad::pad::PadParams; use libxotpad::x121::X121Addr; @@ -13,6 +14,7 @@ use xotpad::user_pad; use xotpad::x28::X28Selection; use xotpad::x3::{UserPadParams, X3CharDelete, X3LineDelete, X3LineDisplay}; +#[cfg(feature = "native")] fn main() -> io::Result<()> { let args = Args::parse(); @@ -43,9 +45,16 @@ fn main() -> io::Result<()> { ) } +#[cfg(not(feature = "native"))] +fn main() { + // WASM entry point is handled by wasm_bindgen + panic!("This binary requires the 'native' feature to run as a CLI application"); +} + // -c, --config config file // -P, --x25-profile X.25 profile // -s, --serve SERVE! +#[cfg(feature = "native")] #[derive(Parser, Debug)] #[command(about = xotpad::ABOUT)] struct Args { @@ -84,6 +93,7 @@ struct Args { selection: Option, } +#[cfg(feature = "native")] struct Config<'a> { x25_params: X25Params, x3_profiles: HashMap<&'a str, PadParams>, @@ -91,6 +101,7 @@ struct Config<'a> { x3_profile: &'a str, } +#[cfg(feature = "native")] fn load_config(args: &Args) -> Config { let addr = match args.local_addr { Some(ref local_addr) => local_addr.clone(), diff --git a/src/wasm.rs b/src/wasm.rs new file mode 100644 index 0000000..7d86c65 --- /dev/null +++ b/src/wasm.rs @@ -0,0 +1,514 @@ +//! WASM bindings for xotpad. +//! +//! This module provides a JavaScript-friendly interface to the PAD functionality +//! for use in web browsers with xterm.js. + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen::prelude::*; +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_futures::spawn_local; + +#[cfg(target_arch = "wasm32")] +use std::str::FromStr; +#[cfg(target_arch = "wasm32")] +use libxotpad::stream::WsStream; +#[cfg(target_arch = "wasm32")] +use libxotpad::wasm_async::WasmConnection; +#[cfg(target_arch = "wasm32")] +use libxotpad::x121::X121Addr; +#[cfg(target_arch = "wasm32")] +use libxotpad::x25::{X25Modulo, X25Params}; +#[cfg(target_arch = "wasm32")] +use libxotpad::xot::{self, XotLink, XotResolver}; +#[cfg(target_arch = "wasm32")] +use std::cell::RefCell; +#[cfg(target_arch = "wasm32")] +use std::rc::Rc; +#[cfg(target_arch = "wasm32")] +use std::time::Duration; +#[cfg(target_arch = "wasm32")] +use web_sys::console; + +/// WASM PAD instance for browser-based X.25 access. +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +pub struct WasmPad { + resolver: XotResolver, + x25_params: X25Params, + connection: Rc>>, + on_data: Rc>>, + on_status: Rc>>, + on_params: Rc>>, + on_remote_params: Rc>>, + on_packet: Rc>>, + recv_task_running: Rc>, +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen] +impl WasmPad { + /// Creates a new WASM PAD instance. + /// + /// # Arguments + /// * `local_addr` - Local X.121 address (optional, can be empty string) + /// * `xot_gateway` - XOT gateway hostname (optional, can be empty string to use x25.org DNS service) + #[wasm_bindgen(constructor)] + pub fn new(local_addr: &str, xot_gateway: &str) -> Result { + // Set panic hook for better error messages + console_error_panic_hook::set_once(); + + let addr = if local_addr.is_empty() { + // Default calling address to 999999 + X121Addr::from_str("999999").unwrap() + } else { + X121Addr::from_str(local_addr) + .map_err(|e| JsValue::from_str(&format!("Invalid X.121 address: {}", e)))? + }; + + let x25_params = X25Params { + addr, + modulo: libxotpad::x25::X25Modulo::Normal, + send_packet_size: 128, + send_window_size: 2, + recv_packet_size: 128, + recv_window_size: 2, + t21: Duration::from_secs(5), + t22: Duration::from_secs(5), + t23: Duration::from_secs(5), + }; + + let mut resolver = XotResolver::new(); + + if xot_gateway.is_empty() { + #[cfg(feature = "x25_org")] + { + let _ = resolver.add("^(...)(...)", "\\2.\\1.x25.org"); + } + #[cfg(not(feature = "x25_org"))] + { + return Err(JsValue::from_str( + "No gateway specified and x25_org feature not enabled", + )); + } + } else { + let _ = resolver.add(".*", xot_gateway); + } + + Ok(WasmPad { + resolver, + x25_params, + connection: Rc::new(RefCell::new(None)), + on_data: Rc::new(RefCell::new(None)), + on_status: Rc::new(RefCell::new(None)), + on_params: Rc::new(RefCell::new(None)), + on_remote_params: Rc::new(RefCell::new(None)), + on_packet: Rc::new(RefCell::new(None)), + recv_task_running: Rc::new(RefCell::new(false)), + }) + } + + /// Sets the callback for receiving data from the remote host. + /// + /// The callback receives a Uint8Array of data. + #[wasm_bindgen(js_name = "onData")] + pub fn on_data(&mut self, callback: js_sys::Function) { + *self.on_data.borrow_mut() = Some(callback); + } + + /// Sets the callback for status updates. + /// + /// The callback receives a string describing the status. + #[wasm_bindgen(js_name = "onStatus")] + pub fn on_status(&mut self, callback: js_sys::Function) { + *self.on_status.borrow_mut() = Some(callback); + } + + /// Sets the callback for local PAD parameter updates (from Set/SetRead messages). + /// + /// The callback receives a JavaScript object with parameter updates: {param: value, ...} + #[wasm_bindgen(js_name = "onParams")] + pub fn on_params(&mut self, callback: js_sys::Function) { + *self.on_params.borrow_mut() = Some(callback); + } + + /// Sets the callback for remote PAD parameter indications (from Indicate messages). + /// + /// The callback receives a JavaScript object with remote parameter values: {param: value, ...} + #[wasm_bindgen(js_name = "onRemoteParams")] + pub fn on_remote_params(&mut self, callback: js_sys::Function) { + *self.on_remote_params.borrow_mut() = Some(callback); + } + + /// Sets the callback for packet inspection. + /// + /// The callback receives a JavaScript object with packet details: + /// {direction: "send"|"recv", timestamp: number, type: string, details: object} + #[wasm_bindgen(js_name = "onPacket")] + pub fn on_packet(&mut self, callback: js_sys::Function) { + *self.on_packet.borrow_mut() = Some(callback); + } + + /// Initiates a call to the specified X.121 address. + /// + /// Returns a promise that resolves when the call is established. + #[wasm_bindgen] + pub async fn call(&self, address: &str) -> Result<(), JsValue> { + let addr = X121Addr::from_str(address) + .map_err(|e| JsValue::from_str(&format!("Invalid X.121 address: {}", e)))?; + + self.emit_status(&format!("Resolving gateway for {}...", address))?; + + // Resolve gateway hostname + let xot_gateway = self.resolver.lookup(&addr) + .ok_or_else(|| JsValue::from_str("No XOT gateway found for address"))?; + + self.emit_status(&format!("Connecting to gateway {}...", xot_gateway))?; + + // Construct WebSocket URL using websockify token format via Caddy reverse proxy + // + + let ws_protocol = web_sys::window() + .and_then(|w| w.location().protocol().ok()) + .map(|p| if p == "https:" { "wss" } else { "ws" }) + .unwrap_or("ws"); + + + let ws_url = format!("{}://{}/ws/?token={}:{}", + ws_protocol, + get_websockify_host(), + xot_gateway, + xot::TCP_PORT + ); + + // Connect WebSocket + let ws_stream = WsStream::connect(&ws_url)?; + + // Wait for WebSocket to be ready with timeout + let mut attempts = 0; + const MAX_ATTEMPTS: u32 = 100; // ~1 second at 10ms per attempt + while !ws_stream.is_ready() { + if attempts >= MAX_ATTEMPTS { + return Err(JsValue::from_str("WebSocket connection timeout - check websockify is running")); + } + + // Check for connection errors + if ws_stream.ready_state() == 3 { // CLOSED + return Err(JsValue::from_str("WebSocket connection closed")); + } + + yield_now().await; + attempts += 1; + } + + self.emit_status("Gateway connected, initiating call...")?; + + // Create XOT link and connection + let link = XotLink::new(ws_stream); + let connection = WasmConnection::new(link, 1, self.x25_params.clone()); + + // Set packet callback if registered + if let Some(callback) = self.on_packet.borrow().as_ref() { + connection.set_packet_callback(Some(callback.clone())); + } + + // Make the call with X.29 call user data (protocol ID + 3 bytes of PAD params) + connection.call(&addr, &[0x01, 0x00, 0x00, 0x00]).await?; // X.29 format + + self.emit_status(&format!("Connected to {}", address))?; + + // Store connection + *self.connection.borrow_mut() = Some(connection); + + // Start receive loop + self.start_recv_loop(); + + Ok(()) + } + + /// Sends data to the remote host. + /// + /// # Arguments + /// * `data` - Uint8Array of data to send + #[wasm_bindgen] + pub fn send(&self, data: &[u8]) -> Result<(), JsValue> { + let connection = self.connection.borrow(); + let conn = connection.as_ref() + .ok_or_else(|| JsValue::from_str("Not connected"))?; + + // Clone what we need for the async task + let conn_clone = Rc::new(conn.clone()); + let data_vec = data.to_vec(); + + // Spawn async send task + spawn_local(async move { + if let Err(e) = conn_clone.send(&data_vec).await { + console::error_1(&JsValue::from_str(&format!("Send error: {:?}", e))); + } + }); + + Ok(()) + } + + /// Clears the current call. + #[wasm_bindgen] + pub fn clear(&self) -> Result<(), JsValue> { + let connection = self.connection.borrow(); + let conn = connection.as_ref() + .ok_or_else(|| JsValue::from_str("Not connected"))?; + + let conn_clone = Rc::new(conn.clone()); + let on_status = Rc::clone(&self.on_status); + + spawn_local(async move { + if let Err(e) = conn_clone.clear().await { + console::error_1(&JsValue::from_str(&format!("Clear error: {:?}", e))); + } else if let Some(callback) = on_status.borrow().as_ref() { + let _ = callback.call1(&JsValue::NULL, &JsValue::from_str("CLR CONF")); + } + }); + + *self.recv_task_running.borrow_mut() = false; + + Ok(()) + } + + /// Sends an X.29 Clear Invitation to request remote disconnect. + #[wasm_bindgen(js_name = "sendClearInvitation")] + pub fn send_clear_invitation(&self) -> Result<(), JsValue> { + let connection = self.connection.borrow(); + let conn = connection.as_ref() + .ok_or_else(|| JsValue::from_str("Not connected"))?; + + let conn_clone = Rc::new(conn.clone()); + + spawn_local(async move { + if let Err(e) = conn_clone.send_clear_invitation().await { + console::error_1(&JsValue::from_str(&format!("Send clear invitation error: {:?}", e))); + } + }); + + Ok(()) + } + + /// Reads remote PAD parameters. Response delivered via onParams callback. + #[wasm_bindgen(js_name = "readRemoteParams")] + pub fn read_remote_params(&self, params: Vec) -> Result<(), JsValue> { + let connection = self.connection.borrow(); + let conn = connection.as_ref() + .ok_or_else(|| JsValue::from_str("Not connected"))?; + + let conn_clone = Rc::new(conn.clone()); + + spawn_local(async move { + if let Err(e) = conn_clone.read_remote_params(params).await { + console::error_1(&JsValue::from_str(&format!("Read remote params error: {:?}", e))); + } + }); + + Ok(()) + } + + /// Sets and reads back remote PAD parameters. Response delivered via onParams callback. + #[wasm_bindgen(js_name = "setreadRemoteParams")] + pub fn setread_remote_params(&self, params: js_sys::Array) -> Result<(), JsValue> { + let connection = self.connection.borrow(); + let conn = connection.as_ref() + .ok_or_else(|| JsValue::from_str("Not connected"))?; + + // Convert JavaScript array of [param, value] pairs to Vec<(u8, u8)> + let param_vec: Result, JsValue> = params.iter() + .map(|item| { + let arr = js_sys::Array::from(&item); + if arr.length() != 2 { + return Err(JsValue::from_str("Each parameter must be [param, value]")); + } + let param = arr.get(0).as_f64() + .ok_or_else(|| JsValue::from_str("Invalid parameter number"))? as u8; + let value = arr.get(1).as_f64() + .ok_or_else(|| JsValue::from_str("Invalid parameter value"))? as u8; + Ok((param, value)) + }) + .collect(); + + let conn_clone = Rc::new(conn.clone()); + let param_vec = param_vec?; + + spawn_local(async move { + if let Err(e) = conn_clone.setread_remote_params(param_vec).await { + console::error_1(&JsValue::from_str(&format!("Setread remote params error: {:?}", e))); + } + }); + + Ok(()) + } + + /// Sends data received from remote to JavaScript callback. + fn emit_data(&self, data: &[u8]) -> Result<(), JsValue> { + if let Some(callback) = self.on_data.borrow().as_ref() { + let array = js_sys::Uint8Array::from(data); + callback.call1(&JsValue::NULL, &array)?; + } + Ok(()) + } + + /// Sends status update to JavaScript callback. + fn emit_status(&self, status: &str) -> Result<(), JsValue> { + if let Some(callback) = self.on_status.borrow().as_ref() { + callback.call1(&JsValue::NULL, &JsValue::from_str(status))?; + } + Ok(()) + } + + /// Starts the receive loop that polls for incoming data. + fn start_recv_loop(&self) { + if *self.recv_task_running.borrow() { + return; // Already running + } + + *self.recv_task_running.borrow_mut() = true; + + let connection = Rc::clone(&self.connection); + let on_data = Rc::clone(&self.on_data); + let on_status = Rc::clone(&self.on_status); + let on_params = Rc::clone(&self.on_params); + let on_remote_params = Rc::clone(&self.on_remote_params); + let running = Rc::clone(&self.recv_task_running); + + spawn_local(async move { + // Clone the connection once outside the loop + let conn = match connection.borrow().as_ref() { + Some(c) => c.clone(), + None => { + console::error_1(&JsValue::from_str("No connection to start receive loop")); + return; + } + }; + + console::log_1(&JsValue::from_str("Receive loop started")); + + loop { + // Check if we should stop + if !*running.borrow() { + break; + } + + // Try to receive data + match conn.recv().await { + Ok(Some(data)) => { + // Send data to JavaScript callback + if let Some(callback) = on_data.borrow().as_ref() { + let array = js_sys::Uint8Array::from(&data[..]); + if let Err(e) = callback.call1(&JsValue::NULL, &array) { + console::error_1(&e); + } + } + } + Ok(None) => { + // No data yet, continue polling + } + Err(e) => { + // Error occurred - extract error message + let error_msg = if let Some(s) = e.as_string() { + s + } else { + "Connection error".to_string() + }; + + if let Some(callback) = on_status.borrow().as_ref() { + if let Err(cb_err) = callback.call1(&JsValue::NULL, &JsValue::from_str(&error_msg)) { + console::error_1(&JsValue::from_str(&format!("Status callback failed: {:?}", cb_err))); + } + } + *running.borrow_mut() = false; + break; + } + } + + // Check for local PAD parameter updates (Set/SetRead messages) + if let Some(params) = conn.take_param_updates() { + if let Some(callback) = on_params.borrow().as_ref() { + // Convert parameter vector to JavaScript object + let obj = js_sys::Object::new(); + for (param, value) in params { + let key = JsValue::from_f64(param as f64); + let val = JsValue::from_f64(value as f64); + js_sys::Reflect::set(&obj, &key, &val).unwrap(); + } + if let Err(e) = callback.call1(&JsValue::NULL, &obj) { + console::error_1(&e); + } + } + } + + // Check for remote PAD parameter indications (Indicate messages) + if let Some(params) = conn.take_remote_param_updates() { + if let Some(callback) = on_remote_params.borrow().as_ref() { + // Convert parameter vector to JavaScript object + let obj = js_sys::Object::new(); + for (param, value) in params { + let key = JsValue::from_f64(param as f64); + let val = JsValue::from_f64(value as f64); + js_sys::Reflect::set(&obj, &key, &val).unwrap(); + } + if let Err(e) = callback.call1(&JsValue::NULL, &obj) { + console::error_1(&e); + } + } + } + + // Yield to browser event loop with small delay to prevent spinning + yield_now().await; + } + }); + } +} + +/// Gets the WebSocket relay host from the window location. +#[cfg(target_arch = "wasm32")] +fn get_websockify_host() -> String { + let window = web_sys::window().expect("no window"); + let location = window.location(); + + // Use window.location.host which includes hostname:port + // This allows the WebSocket to use the same port as the web page + location.host().expect("no host") +} + +/// Yields control back to the browser event loop. +/// Uses setTimeout to ensure a real async yield. +#[cfg(target_arch = "wasm32")] +async fn yield_now() { + sleep_ms(10).await; +} + +/// Sleep for a specified number of milliseconds. +#[cfg(target_arch = "wasm32")] +async fn sleep_ms(ms: i32) { + use wasm_bindgen::closure::Closure; + use wasm_bindgen::JsCast; + use wasm_bindgen_futures::JsFuture; + + let promise = js_sys::Promise::new(&mut |resolve, _reject| { + let window = web_sys::window().expect("no window"); + let closure = Closure::once(move || { + resolve.call0(&JsValue::NULL).unwrap(); + }); + window + .set_timeout_with_callback_and_timeout_and_arguments_0( + closure.as_ref().unchecked_ref(), + ms, + ) + .unwrap(); + closure.forget(); + }); + + JsFuture::from(promise).await.unwrap(); +} + +/// Initialize the WASM module. +/// This should be called once when the module is loaded. +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen(start)] +pub fn init() { + console_error_panic_hook::set_once(); +} diff --git a/token_plugin.py b/token_plugin.py new file mode 100644 index 0000000..637479d --- /dev/null +++ b/token_plugin.py @@ -0,0 +1,29 @@ +"""Websockify token plugin that allows dynamic host:port targets.""" + + +class TokenPlugin(object): + """Token plugin that parses tokens as host:port pairs.""" + + def __init__(self, token_source): + """Initialize token plugin with token source (ignored).""" + self.token_source = token_source + + def lookup(self, token): + """Parse token as host:port and return target. + + Args: + token: String in format "host:port" + + Returns: + List [host, port] if valid, None otherwise + """ + if ":" in token: + parts = token.split(":") + if len(parts) == 2: + host, port = parts + try: + port = int(port) + return [host, port] + except ValueError: + pass + return None diff --git a/websockify_wrapper.py b/websockify_wrapper.py new file mode 100644 index 0000000..d13ced7 --- /dev/null +++ b/websockify_wrapper.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python3 +"""Websockify wrapper that adds pong timeout detection. + +Standard websockify sends WebSocket pings on --heartbeat but never checks +if pongs come back. This wrapper adds tracking so that if a pong is not +received before the next heartbeat fires, the connection is closed. This +handles the case where a browser tab is suspended (e.g. Safari on iOS) +and stops responding to pings. +""" + +import time +from websockify.websocketproxy import ( + ProxyRequestHandler, WebSocketProxy, websockify_init, +) + + +class PongTimeoutHandler(ProxyRequestHandler): + + def do_proxy(self, target): + # Monkey-patch handle_pong on the WebSocket object to track receipt + self._last_pong = time.time() + self._ping_outstanding = False + + original_handle_pong = self.request.handle_pong + def tracked_handle_pong(data): + self._last_pong = time.time() + self._ping_outstanding = False + original_handle_pong(data) + self.request.handle_pong = tracked_handle_pong + + super().do_proxy(target) + + def send_ping(self, data=b''): + if self._ping_outstanding: + # Previous ping got no pong before the next heartbeat + elapsed = time.time() - self._last_pong + self.log_message("Pong timeout (%.1fs since last pong), closing", + elapsed) + raise self.CClose(1000, "Pong timeout") + + self._ping_outstanding = True + super().send_ping(data) + + +# Patch the default argument of WebSocketProxy.__init__ so that our handler +# is used instead of the original ProxyRequestHandler. The default is +# captured at class definition time, so we must patch __init__.__defaults__. +original_defaults = list(WebSocketProxy.__init__.__defaults__) +original_defaults[0] = PongTimeoutHandler +WebSocketProxy.__init__.__defaults__ = tuple(original_defaults) + +if __name__ == '__main__': + websockify_init() diff --git a/www/index.html b/www/index.html new file mode 100644 index 0000000..eaf4a5f --- /dev/null +++ b/www/index.html @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + Bootstrap + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+

Telebahn

+

an interactive online museum of Packet Switched Public Data Networking

+
+ + + +
+ + + + + + diff --git a/www/inspector.html b/www/inspector.html new file mode 100644 index 0000000..ee4f1e4 --- /dev/null +++ b/www/inspector.html @@ -0,0 +1,574 @@ + + + + + + X.25 Packet Inspector + + + +
+

📊 X.25 Packet Inspector

+
+ + +
+
+
+
+
Packet List
+
+
+
+
+
Packet Details
+
+
Select a packet to view details
+
+
+
+
Hex Dump
+
+
Select a packet to view hex dump
+
+
+
+
+ + + + diff --git a/www/pad.html b/www/pad.html new file mode 100644 index 0000000..d98c8d0 --- /dev/null +++ b/www/pad.html @@ -0,0 +1,890 @@ + + + + + + xotpad - Interactive X.25 PAD + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
Terminal Settings
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+
+
+
+
+
+ + +
+
+
xotpad Help
+
+
+
+

X.28 Commands

+
    +
  • call <address> - Call a remote X.121 address (e.g., call 737411 or just 737411)
  • +
  • clr/clear - Clear the current call
  • +
  • iclr/iclear - Send clear invitation to remote PAD
  • +
  • stat/status - Display connection status (FREE or ENGAGED)
  • +
  • par?/par/read - Display local PAD parameters
  • +
  • set <p>:<v>[,<p>:<v>] - Set local PAD parameter(s) (e.g., set 2:1,15:0)
  • +
  • set?/setread <p>:<v> - Set and display local parameter(s)
  • +
  • rpar?/rread [params] - Read remote PAD parameter(s) (e.g., rpar? 2,13)
  • +
  • rset?/rsetread <p>:<v> - Set and read remote parameter(s)
  • +
  • help/? - Display PAD information
  • +
+

Usage

+

When connected to a remote host, all input is sent directly. Use Ctrl+P to return to PAD command mode (PAD recall). Press Enter on an empty line in command mode to return to data mode.

+

Service Signals

+
    +
  • COM - Connected to remote host
  • +
  • CLR CONF - Call cleared (confirmation)
  • +
  • FREE - PAD is free (not connected)
  • +
  • ENGAGED - PAD is engaged (connected)
  • +
  • ERR - Error in command or operation
  • +
  • PAR <params> - Local parameter values
  • +
  • RPAR <params> - Remote parameter values
  • +
+
+
+
+
+ + + + + + + + + diff --git a/www/style/bootstrap.min.css b/www/style/bootstrap.min.css new file mode 100644 index 0000000..f9e9766 --- /dev/null +++ b/www/style/bootstrap.min.css @@ -0,0 +1,12 @@ +/*! + * Bootswatch v5.0.1 + * Homepage: https://bootswatch.com + * Copyright 2012-2021 Thomas Park + * Licensed under MIT + * Based on Bootstrap +*//*! + * Bootstrap v5.0.1 (https://getbootstrap.com/) + * Copyright 2011-2021 The Bootstrap Authors + * Copyright 2011-2021 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */@import url(https://fonts.googleapis.com/css2?family=Lato:wght@400;700&display=swap);:root{--bs-blue:#1ba2f6;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#ea39b8;--bs-red:#e44c55;--bs-orange:#f1b633;--bs-yellow:#ffc107;--bs-green:#3cf281;--bs-teal:#3f81a2;--bs-cyan:#32fbe2;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-primary:#6f42c1;--bs-secondary:#ea39b8;--bs-success:#3cf281;--bs-info:#1ba2f6;--bs-warning:#ffc107;--bs-danger:#e44c55;--bs-light:#44d9e8;--bs-dark:#170229;--bs-font-sans-serif:Lato,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0))}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-font-sans-serif);font-size:1rem;font-weight:400;line-height:1.5;color:#32fbe2;background-color:#1a0933;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:0}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}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,small{font-size:.875em}.mark,mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#32fbe2;text-decoration:underline}a:hover{color:#28c9b5}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#ea39b8;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#170229;border-radius:.05rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:rgba(50,251,226,.3);text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[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}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-6{font-size:2.5rem}}.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:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:rgba(50,251,226,.3)}.blockquote-footer::before{content:"\2014\00A0"}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#1a0933;border:0 solid #dee2e6;border-radius:.15rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:#6c757d}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{width:100%;padding-right:var(--bs-gutter-x,.75rem);padding-left:var(--bs-gutter-x,.75rem);margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media (min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;margin-top:calc(var(--bs-gutter-y) * -1);margin-right:calc(var(--bs-gutter-x)/ -2);margin-left:calc(var(--bs-gutter-x)/ -2)}.row>*{-ms-flex-negative:0;flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x)/ 2);padding-left:calc(var(--bs-gutter-x)/ 2);margin-top:var(--bs-gutter-y)}.col{-ms-flex:1 0 0%;flex:1 0 0%}.row-cols-auto>*{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.row-cols-1>*{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.row-cols-2>*{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.row-cols-3>*{-ms-flex:0 0 auto;flex:0 0 auto;width:33.333333%}.row-cols-4>*{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.row-cols-5>*{-ms-flex:0 0 auto;flex:0 0 auto;width:20%}.row-cols-6>*{-ms-flex:0 0 auto;flex:0 0 auto;width:16.666667%}.col-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.col-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.333333%}.col-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.666667%}.col-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.col-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.333333%}.col-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.666667%}.col-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.col-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.333333%}.col-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.666667%}.col-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.col-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.333333%}.col-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.666667%}.col-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.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%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.25rem}.g-1,.gy-1{--bs-gutter-y:0.25rem}.g-2,.gx-2{--bs-gutter-x:0.5rem}.g-2,.gy-2{--bs-gutter-y:0.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (min-width:576px){.col-sm{-ms-flex:1 0 0%;flex:1 0 0%}.row-cols-sm-auto>*{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.row-cols-sm-1>*{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.row-cols-sm-2>*{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.row-cols-sm-3>*{-ms-flex:0 0 auto;flex:0 0 auto;width:33.333333%}.row-cols-sm-4>*{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.row-cols-sm-5>*{-ms-flex:0 0 auto;flex:0 0 auto;width:20%}.row-cols-sm-6>*{-ms-flex:0 0 auto;flex:0 0 auto;width:16.666667%}.col-sm-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.col-sm-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.333333%}.col-sm-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.666667%}.col-sm-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.col-sm-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.333333%}.col-sm-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.666667%}.col-sm-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.col-sm-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.333333%}.col-sm-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.666667%}.col-sm-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.col-sm-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.333333%}.col-sm-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.666667%}.col-sm-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.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%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:0.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:0.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (min-width:768px){.col-md{-ms-flex:1 0 0%;flex:1 0 0%}.row-cols-md-auto>*{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.row-cols-md-1>*{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.row-cols-md-2>*{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.row-cols-md-3>*{-ms-flex:0 0 auto;flex:0 0 auto;width:33.333333%}.row-cols-md-4>*{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.row-cols-md-5>*{-ms-flex:0 0 auto;flex:0 0 auto;width:20%}.row-cols-md-6>*{-ms-flex:0 0 auto;flex:0 0 auto;width:16.666667%}.col-md-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.col-md-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.333333%}.col-md-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.666667%}.col-md-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.col-md-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.333333%}.col-md-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.666667%}.col-md-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.col-md-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.333333%}.col-md-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.666667%}.col-md-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.col-md-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.333333%}.col-md-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.666667%}.col-md-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.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%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:0.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:0.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (min-width:992px){.col-lg{-ms-flex:1 0 0%;flex:1 0 0%}.row-cols-lg-auto>*{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.row-cols-lg-1>*{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.row-cols-lg-2>*{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.row-cols-lg-3>*{-ms-flex:0 0 auto;flex:0 0 auto;width:33.333333%}.row-cols-lg-4>*{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.row-cols-lg-5>*{-ms-flex:0 0 auto;flex:0 0 auto;width:20%}.row-cols-lg-6>*{-ms-flex:0 0 auto;flex:0 0 auto;width:16.666667%}.col-lg-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.col-lg-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.333333%}.col-lg-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.666667%}.col-lg-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.col-lg-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.333333%}.col-lg-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.666667%}.col-lg-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.col-lg-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.333333%}.col-lg-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.666667%}.col-lg-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.col-lg-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.333333%}.col-lg-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.666667%}.col-lg-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.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%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:0.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:0.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (min-width:1200px){.col-xl{-ms-flex:1 0 0%;flex:1 0 0%}.row-cols-xl-auto>*{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.row-cols-xl-1>*{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.row-cols-xl-2>*{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.row-cols-xl-3>*{-ms-flex:0 0 auto;flex:0 0 auto;width:33.333333%}.row-cols-xl-4>*{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.row-cols-xl-5>*{-ms-flex:0 0 auto;flex:0 0 auto;width:20%}.row-cols-xl-6>*{-ms-flex:0 0 auto;flex:0 0 auto;width:16.666667%}.col-xl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.col-xl-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.333333%}.col-xl-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.666667%}.col-xl-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.col-xl-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.333333%}.col-xl-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.666667%}.col-xl-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.col-xl-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.333333%}.col-xl-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.666667%}.col-xl-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.col-xl-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.333333%}.col-xl-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.666667%}.col-xl-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.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%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:0.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:0.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (min-width:1400px){.col-xxl{-ms-flex:1 0 0%;flex:1 0 0%}.row-cols-xxl-auto>*{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.row-cols-xxl-1>*{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.row-cols-xxl-2>*{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.row-cols-xxl-3>*{-ms-flex:0 0 auto;flex:0 0 auto;width:33.333333%}.row-cols-xxl-4>*{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.row-cols-xxl-5>*{-ms-flex:0 0 auto;flex:0 0 auto;width:20%}.row-cols-xxl-6>*{-ms-flex:0 0 auto;flex:0 0 auto;width:16.666667%}.col-xxl-auto{-ms-flex:0 0 auto;flex:0 0 auto;width:auto}.col-xxl-1{-ms-flex:0 0 auto;flex:0 0 auto;width:8.333333%}.col-xxl-2{-ms-flex:0 0 auto;flex:0 0 auto;width:16.666667%}.col-xxl-3{-ms-flex:0 0 auto;flex:0 0 auto;width:25%}.col-xxl-4{-ms-flex:0 0 auto;flex:0 0 auto;width:33.333333%}.col-xxl-5{-ms-flex:0 0 auto;flex:0 0 auto;width:41.666667%}.col-xxl-6{-ms-flex:0 0 auto;flex:0 0 auto;width:50%}.col-xxl-7{-ms-flex:0 0 auto;flex:0 0 auto;width:58.333333%}.col-xxl-8{-ms-flex:0 0 auto;flex:0 0 auto;width:66.666667%}.col-xxl-9{-ms-flex:0 0 auto;flex:0 0 auto;width:75%}.col-xxl-10{-ms-flex:0 0 auto;flex:0 0 auto;width:83.333333%}.col-xxl-11{-ms-flex:0 0 auto;flex:0 0 auto;width:91.666667%}.col-xxl-12{-ms-flex:0 0 auto;flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.333333%}.offset-xxl-2{margin-left:16.666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.333333%}.offset-xxl-5{margin-left:41.666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.333333%}.offset-xxl-8{margin-left:66.666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.333333%}.offset-xxl-11{margin-left:91.666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:0.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:0.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-bg:transparent;--bs-table-accent-bg:transparent;--bs-table-striped-color:#fff;--bs-table-striped-bg:rgba(0, 0, 0, 0.05);--bs-table-active-color:#fff;--bs-table-active-bg:rgba(0, 0, 0, 0.1);--bs-table-hover-color:#fff;--bs-table-hover-bg:rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:#fff;vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:0;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:last-child)>:last-child>*{border-bottom-color:currentColor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:0 0}.table-bordered>:not(caption)>*>*{border-width:0 0}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-striped>tbody>tr:nth-of-type(odd){--bs-table-accent-bg:var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg:var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover{--bs-table-accent-bg:var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg:#6f42c1;--bs-table-striped-bg:#764bc4;--bs-table-striped-color:#fff;--bs-table-active-bg:#7d55c7;--bs-table-active-color:#fff;--bs-table-hover-bg:#7a50c6;--bs-table-hover-color:#fff;color:#fff;border-color:#7d55c7}.table-secondary{--bs-table-bg:#ea39b8;--bs-table-striped-bg:#eb43bc;--bs-table-striped-color:#fff;--bs-table-active-bg:#ec4dbf;--bs-table-active-color:#fff;--bs-table-hover-bg:#ec48bd;--bs-table-hover-color:#fff;color:#fff;border-color:#ec4dbf}.table-success{--bs-table-bg:#3cf281;--bs-table-striped-bg:#46f387;--bs-table-striped-color:#fff;--bs-table-active-bg:#50f38e;--bs-table-active-color:#fff;--bs-table-hover-bg:#4bf38a;--bs-table-hover-color:#fff;color:#fff;border-color:#50f38e}.table-info{--bs-table-bg:#1ba2f6;--bs-table-striped-bg:#26a7f6;--bs-table-striped-color:#fff;--bs-table-active-bg:#32abf7;--bs-table-active-color:#fff;--bs-table-hover-bg:#2ca9f7;--bs-table-hover-color:#fff;color:#fff;border-color:#32abf7}.table-warning{--bs-table-bg:#ffc107;--bs-table-striped-bg:#ffc413;--bs-table-striped-color:#fff;--bs-table-active-bg:#ffc720;--bs-table-active-color:#fff;--bs-table-hover-bg:#ffc61a;--bs-table-hover-color:#fff;color:#fff;border-color:#ffc720}.table-danger{--bs-table-bg:#e44c55;--bs-table-striped-bg:#e5555e;--bs-table-striped-color:#fff;--bs-table-active-bg:#e75e66;--bs-table-active-color:#fff;--bs-table-hover-bg:#e65962;--bs-table-hover-color:#fff;color:#fff;border-color:#e75e66}.table-light{--bs-table-bg:#44d9e8;--bs-table-striped-bg:#4ddbe9;--bs-table-striped-color:#fff;--bs-table-active-bg:#57ddea;--bs-table-active-color:#fff;--bs-table-hover-bg:#52dcea;--bs-table-hover-color:#fff;color:#fff;border-color:#57ddea}.table-dark{--bs-table-bg:#170229;--bs-table-striped-bg:#230f34;--bs-table-striped-color:#fff;--bs-table-active-bg:#2e1b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#281539;--bs-table-hover-color:#fff;color:#fff;border-color:#2e1b3e}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:.375rem;padding-bottom:.375rem;margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:.5rem;padding-bottom:.5rem;font-size:1.25rem}.col-form-label-sm{padding-top:.25rem;padding-bottom:.25rem;font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:rgba(50,251,226,.3)}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#fff;background-color:#30115e;background-clip:padding-box;border:0 solid #ced4da;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.15rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#fff;background-color:#30115e;border-color:#f59cdc;outline:0;box-shadow:0 0 0 .25rem rgba(234,57,184,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::-webkit-input-placeholder{color:rgba(255,255,255,.4);opacity:1}.form-control::-moz-placeholder{color:rgba(255,255,255,.4);opacity:1}.form-control:-ms-input-placeholder{color:rgba(255,255,255,.4);opacity:1}.form-control::-ms-input-placeholder{color:rgba(255,255,255,.4);opacity:1}.form-control::placeholder{color:rgba(255,255,255,.4);opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#250d49;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;-moz-margin-end:.75rem;margin-inline-end:.75rem;color:#fff;background-color:#250d49;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:0;border-radius:0;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){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#230c45}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#fff;background-color:#250d49;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:0;border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;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){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#230c45}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#32fbe2;background-color:transparent;border:solid transparent;border-width:0 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + .5rem);padding:.25rem .5rem;font-size:.875rem;border-radius:.05rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;-moz-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem);padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;-moz-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + .75rem)}textarea.form-control-sm{min-height:calc(1.5em + .5rem)}textarea.form-control-lg{min-height:calc(1.5em + 1rem)}.form-control-color{max-width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em;border-radius:.15rem}.form-control-color::-webkit-color-swatch{height:1.5em;border-radius:.15rem}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#fff;background-color:#30115e;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:0 solid #ced4da;border-radius:.15rem;-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-select:focus{border-color:#f59cdc;outline:0;box-shadow:0 0 0 .25rem rgba(234,57,184,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #fff}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#30115e;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{-webkit-filter:brightness(90%);filter:brightness(90%)}.form-check-input:focus{border-color:#f59cdc;outline:0;box-shadow:0 0 0 .25rem rgba(234,57,184,.25)}.form-check-input:checked{background-color:#ea39b8;border-color:#ea39b8}.form-check-input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#ea39b8;border-color:#ea39b8;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;-webkit-filter:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;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='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{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='%23f59cdc'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;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")}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;-webkit-filter:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #1a0933,0 0 0 .25rem rgba(234,57,184,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #1a0933,0 0 0 .25rem rgba(234,57,184,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#ea39b8;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;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){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#f9c4ea}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#30115e;border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#ea39b8;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;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){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#f9c4ea}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#30115e;border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#6f42c1}.form-range:disabled::-moz-range-thumb{background-color:#6f42c1}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:3.5rem;padding:1rem .75rem}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:0 solid transparent;-webkit-transform-origin:0 0;transform-origin:0 0;transition:opacity .1s ease-in-out,-webkit-transform .1s ease-in-out;transition:opacity .1s ease-in-out,transform .1s ease-in-out;transition:opacity .1s ease-in-out,transform .1s ease-in-out,-webkit-transform .1s ease-in-out}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control::-webkit-input-placeholder{color:transparent}.form-floating>.form-control::-moz-placeholder{color:transparent}.form-floating>.form-control:-ms-input-placeholder{color:transparent}.form-floating>.form-control::-ms-input-placeholder{color:transparent}.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-ms-input-placeholder){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-moz-placeholder-shown)~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:not(:-ms-input-placeholder)~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;-webkit-transform:scale(.85) translateY(-.5rem) translateX(.15rem);transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;-webkit-transform:scale(.85) translateY(-.5rem) translateX(.15rem);transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.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>.form-control,.input-group>.form-select{position:relative;-ms-flex:1 1 auto;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.input-group-text{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#fff;text-align:center;white-space:nowrap;background-color:#250d49;border:0 solid #ced4da;border-radius:.15rem}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:.05rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#3cf281}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:#3cf281;border-radius:.15rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#3cf281;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='%233cf281' 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:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#3cf281;box-shadow:0 0 0 .25rem rgba(60,242,129,.25)}.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)}.form-select.is-valid,.was-validated .form-select:valid{border-color:#3cf281}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%233cf281' 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-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:#3cf281;box-shadow:0 0 0 .25rem rgba(60,242,129,.25)}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:#3cf281}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:#3cf281}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(60,242,129,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#3cf281}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group .form-control.is-valid,.input-group .form-select.is-valid,.was-validated .input-group .form-control:valid,.was-validated .input-group .form-select:valid{z-index:1}.input-group .form-control.is-valid:focus,.input-group .form-select.is-valid:focus,.was-validated .input-group .form-control:valid:focus,.was-validated .input-group .form-select:valid:focus{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#e44c55}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:#e44c55;border-radius:.15rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#e44c55;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23e44c55'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23e44c55' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#e44c55;box-shadow:0 0 0 .25rem rgba(228,76,85,.25)}.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)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:#e44c55}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23e44c55'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23e44c55' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:#e44c55;box-shadow:0 0 0 .25rem rgba(228,76,85,.25)}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:#e44c55}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:#e44c55}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(228,76,85,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#e44c55}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group .form-control.is-invalid,.input-group .form-select.is-invalid,.was-validated .input-group .form-control:invalid,.was-validated .input-group .form-select:invalid{z-index:2}.input-group .form-control.is-invalid:focus,.input-group .form-select.is-invalid:focus,.was-validated .input-group .form-control:invalid:focus,.was-validated .input-group .form-select:invalid:focus{z-index:3}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#32fbe2;text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:0 solid transparent;padding:.375rem .75rem;font-size:1rem;border-radius:.15rem;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:#32fbe2}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 0 0 .25rem rgba(234,57,184,.25)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65}.btn-primary{color:#fff;background-color:#6f42c1;border-color:#6f42c1}.btn-primary:hover{color:#fff;background-color:#5e38a4;border-color:#59359a}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#5e38a4;border-color:#59359a;box-shadow:0 0 0 .25rem rgba(133,94,202,.5)}.btn-check:active+.btn-primary,.btn-check:checked+.btn-primary,.btn-primary.active,.btn-primary:active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#59359a;border-color:#533291}.btn-check:active+.btn-primary:focus,.btn-check:checked+.btn-primary:focus,.btn-primary.active:focus,.btn-primary:active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(133,94,202,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#6f42c1;border-color:#6f42c1}.btn-secondary{color:#fff;background-color:#ea39b8;border-color:#ea39b8}.btn-secondary:hover{color:#fff;background-color:#c7309c;border-color:#bb2e93}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#fff;background-color:#c7309c;border-color:#bb2e93;box-shadow:0 0 0 .25rem rgba(237,87,195,.5)}.btn-check:active+.btn-secondary,.btn-check:checked+.btn-secondary,.btn-secondary.active,.btn-secondary:active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#bb2e93;border-color:#b02b8a}.btn-check:active+.btn-secondary:focus,.btn-check:checked+.btn-secondary:focus,.btn-secondary.active:focus,.btn-secondary:active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(237,87,195,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#ea39b8;border-color:#ea39b8}.btn-success{color:#fff;background-color:#3cf281;border-color:#3cf281}.btn-success:hover{color:#fff;background-color:#33ce6e;border-color:#30c267}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#33ce6e;border-color:#30c267;box-shadow:0 0 0 .25rem rgba(89,244,148,.5)}.btn-check:active+.btn-success,.btn-check:checked+.btn-success,.btn-success.active,.btn-success:active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#30c267;border-color:#2db661}.btn-check:active+.btn-success:focus,.btn-check:checked+.btn-success:focus,.btn-success.active:focus,.btn-success:active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(89,244,148,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#3cf281;border-color:#3cf281}.btn-info{color:#fff;background-color:#1ba2f6;border-color:#1ba2f6}.btn-info:hover{color:#fff;background-color:#178ad1;border-color:#1682c5}.btn-check:focus+.btn-info,.btn-info:focus{color:#fff;background-color:#178ad1;border-color:#1682c5;box-shadow:0 0 0 .25rem rgba(61,176,247,.5)}.btn-check:active+.btn-info,.btn-check:checked+.btn-info,.btn-info.active,.btn-info:active,.show>.btn-info.dropdown-toggle{color:#fff;background-color:#1682c5;border-color:#147ab9}.btn-check:active+.btn-info:focus,.btn-check:checked+.btn-info:focus,.btn-info.active:focus,.btn-info:active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(61,176,247,.5)}.btn-info.disabled,.btn-info:disabled{color:#fff;background-color:#1ba2f6;border-color:#1ba2f6}.btn-warning{color:#fff;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#fff;background-color:#d9a406;border-color:#cc9a06}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#fff;background-color:#d9a406;border-color:#cc9a06;box-shadow:0 0 0 .25rem rgba(255,202,44,.5)}.btn-check:active+.btn-warning,.btn-check:checked+.btn-warning,.btn-warning.active,.btn-warning:active,.show>.btn-warning.dropdown-toggle{color:#fff;background-color:#cc9a06;border-color:#bf9105}.btn-check:active+.btn-warning:focus,.btn-check:checked+.btn-warning:focus,.btn-warning.active:focus,.btn-warning:active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(255,202,44,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#fff;background-color:#ffc107;border-color:#ffc107}.btn-danger{color:#fff;background-color:#e44c55;border-color:#e44c55}.btn-danger:hover{color:#fff;background-color:#c24148;border-color:#b63d44}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#c24148;border-color:#b63d44;box-shadow:0 0 0 .25rem rgba(232,103,111,.5)}.btn-check:active+.btn-danger,.btn-check:checked+.btn-danger,.btn-danger.active,.btn-danger:active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#b63d44;border-color:#ab3940}.btn-check:active+.btn-danger:focus,.btn-check:checked+.btn-danger:focus,.btn-danger.active:focus,.btn-danger:active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(232,103,111,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#e44c55;border-color:#e44c55}.btn-light{color:#fff;background-color:#44d9e8;border-color:#44d9e8}.btn-light:hover{color:#fff;background-color:#3ab8c5;border-color:#36aeba}.btn-check:focus+.btn-light,.btn-light:focus{color:#fff;background-color:#3ab8c5;border-color:#36aeba;box-shadow:0 0 0 .25rem rgba(96,223,235,.5)}.btn-check:active+.btn-light,.btn-check:checked+.btn-light,.btn-light.active,.btn-light:active,.show>.btn-light.dropdown-toggle{color:#fff;background-color:#36aeba;border-color:#33a3ae}.btn-check:active+.btn-light:focus,.btn-check:checked+.btn-light:focus,.btn-light.active:focus,.btn-light:active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(96,223,235,.5)}.btn-light.disabled,.btn-light:disabled{color:#fff;background-color:#44d9e8;border-color:#44d9e8}.btn-dark{color:#fff;background-color:#170229;border-color:#170229}.btn-dark:hover{color:#fff;background-color:#140223;border-color:#120221}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#140223;border-color:#120221;box-shadow:0 0 0 .25rem rgba(58,40,73,.5)}.btn-check:active+.btn-dark,.btn-check:checked+.btn-dark,.btn-dark.active,.btn-dark:active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#120221;border-color:#11021f}.btn-check:active+.btn-dark:focus,.btn-check:checked+.btn-dark:focus,.btn-dark.active:focus,.btn-dark:active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(58,40,73,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#170229;border-color:#170229}.btn-outline-primary{color:#6f42c1;border-color:#6f42c1}.btn-outline-primary:hover{color:#fff;background-color:#6f42c1;border-color:#6f42c1}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(111,66,193,.5)}.btn-check:active+.btn-outline-primary,.btn-check:checked+.btn-outline-primary,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show,.btn-outline-primary:active{color:#fff;background-color:#6f42c1;border-color:#6f42c1}.btn-check:active+.btn-outline-primary:focus,.btn-check:checked+.btn-outline-primary:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus,.btn-outline-primary:active:focus{box-shadow:0 0 0 .25rem rgba(111,66,193,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#6f42c1;background-color:transparent}.btn-outline-secondary{color:#ea39b8;border-color:#ea39b8}.btn-outline-secondary:hover{color:#fff;background-color:#ea39b8;border-color:#ea39b8}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(234,57,184,.5)}.btn-check:active+.btn-outline-secondary,.btn-check:checked+.btn-outline-secondary,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show,.btn-outline-secondary:active{color:#fff;background-color:#ea39b8;border-color:#ea39b8}.btn-check:active+.btn-outline-secondary:focus,.btn-check:checked+.btn-outline-secondary:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus,.btn-outline-secondary:active:focus{box-shadow:0 0 0 .25rem rgba(234,57,184,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#ea39b8;background-color:transparent}.btn-outline-success{color:#3cf281;border-color:#3cf281}.btn-outline-success:hover{color:#fff;background-color:#3cf281;border-color:#3cf281}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(60,242,129,.5)}.btn-check:active+.btn-outline-success,.btn-check:checked+.btn-outline-success,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show,.btn-outline-success:active{color:#fff;background-color:#3cf281;border-color:#3cf281}.btn-check:active+.btn-outline-success:focus,.btn-check:checked+.btn-outline-success:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus,.btn-outline-success:active:focus{box-shadow:0 0 0 .25rem rgba(60,242,129,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#3cf281;background-color:transparent}.btn-outline-info{color:#1ba2f6;border-color:#1ba2f6}.btn-outline-info:hover{color:#fff;background-color:#1ba2f6;border-color:#1ba2f6}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(27,162,246,.5)}.btn-check:active+.btn-outline-info,.btn-check:checked+.btn-outline-info,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show,.btn-outline-info:active{color:#fff;background-color:#1ba2f6;border-color:#1ba2f6}.btn-check:active+.btn-outline-info:focus,.btn-check:checked+.btn-outline-info:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus,.btn-outline-info:active:focus{box-shadow:0 0 0 .25rem rgba(27,162,246,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#1ba2f6;background-color:transparent}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#fff;background-color:#ffc107;border-color:#ffc107}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-check:active+.btn-outline-warning,.btn-check:checked+.btn-outline-warning,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show,.btn-outline-warning:active{color:#fff;background-color:#ffc107;border-color:#ffc107}.btn-check:active+.btn-outline-warning:focus,.btn-check:checked+.btn-outline-warning:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus,.btn-outline-warning:active:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-danger{color:#e44c55;border-color:#e44c55}.btn-outline-danger:hover{color:#fff;background-color:#e44c55;border-color:#e44c55}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(228,76,85,.5)}.btn-check:active+.btn-outline-danger,.btn-check:checked+.btn-outline-danger,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show,.btn-outline-danger:active{color:#fff;background-color:#e44c55;border-color:#e44c55}.btn-check:active+.btn-outline-danger:focus,.btn-check:checked+.btn-outline-danger:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus,.btn-outline-danger:active:focus{box-shadow:0 0 0 .25rem rgba(228,76,85,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#e44c55;background-color:transparent}.btn-outline-light{color:#44d9e8;border-color:#44d9e8}.btn-outline-light:hover{color:#fff;background-color:#44d9e8;border-color:#44d9e8}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(68,217,232,.5)}.btn-check:active+.btn-outline-light,.btn-check:checked+.btn-outline-light,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show,.btn-outline-light:active{color:#fff;background-color:#44d9e8;border-color:#44d9e8}.btn-check:active+.btn-outline-light:focus,.btn-check:checked+.btn-outline-light:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus,.btn-outline-light:active:focus{box-shadow:0 0 0 .25rem rgba(68,217,232,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#44d9e8;background-color:transparent}.btn-outline-dark{color:#170229;border-color:#170229}.btn-outline-dark:hover{color:#fff;background-color:#170229;border-color:#170229}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(23,2,41,.5)}.btn-check:active+.btn-outline-dark,.btn-check:checked+.btn-outline-dark,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show,.btn-outline-dark:active{color:#fff;background-color:#170229;border-color:#170229}.btn-check:active+.btn-outline-dark:focus,.btn-check:checked+.btn-outline-dark:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus,.btn-outline-dark:active:focus{box-shadow:0 0 0 .25rem rgba(23,2,41,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#170229;background-color:transparent}.btn-link{font-weight:400;color:#32fbe2;text-decoration:underline}.btn-link:hover{color:#28c9b5}.btn-link.disabled,.btn-link:disabled{color:#6c757d}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;border-radius:.05rem}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.dropdown,.dropend,.dropstart,.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;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:#32fbe2;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:0 solid rgba(0,0,0,.15);border-radius:.15rem}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{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}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropend .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}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .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}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid rgba(0,0,0,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#170229;text-align:inherit;text-decoration:none;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#150225;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#ea39b8}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#170229}.dropdown-menu-dark{color:#dee2e6;background-color:#343a40;border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:focus,.dropdown-menu-dark .dropdown-item:hover{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#fff;background-color:#ea39b8}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.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-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{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:0}.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:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .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:0}.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~.btn{border-top-left-radius:0;border-top-right-radius:0}.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;color:#32fbe2;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:#28c9b5}.nav-link.disabled{color:rgba(50,251,226,.3);pointer-events:none;cursor:default}.nav-tabs{border-bottom:0 solid #dee2e6}.nav-tabs .nav-link{margin-bottom:0;background:0 0;border:0 solid transparent;border-top-left-radius:.15rem;border-top-right-radius:.15rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:rgba(50,251,226,.3);background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#ea39b8;background-color:#1a0933;border-color:#dee2e6 #dee2e6 #1a0933}.nav-tabs .dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:0 0;border:0;border-radius:.15rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#ea39b8}.nav-fill .nav-item,.nav-fill>.nav-link{-ms-flex:1 1 auto;flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{-ms-flex-preferred-size:0;flex-basis:0;-ms-flex-positive:1;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.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-top:.5rem;padding-bottom:.5rem}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:-ms-flexbox;display:flex;-ms-flex-wrap:inherit;flex-wrap:inherit;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;white-space:nowrap}.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}.navbar-text{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:0 solid transparent;border-radius:.15rem;transition:box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (min-width:576px){.navbar-expand-sm{-ms-flex-wrap:nowrap;flex-wrap: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 .navbar-nav-scroll{overflow:visible}.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 (min-width:768px){.navbar-expand-md{-ms-flex-wrap:nowrap;flex-wrap: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 .navbar-nav-scroll{overflow:visible}.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 (min-width:992px){.navbar-expand-lg{-ms-flex-wrap:nowrap;flex-wrap: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 .navbar-nav-scroll{overflow:visible}.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 (min-width:1200px){.navbar-expand-xl{-ms-flex-wrap:nowrap;flex-wrap: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 .navbar-nav-scroll{overflow:visible}.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}}@media (min-width:1400px){.navbar-expand-xxl{-ms-flex-wrap:nowrap;flex-wrap:nowrap;-ms-flex-pack:start;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{-ms-flex-direction:row;flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:-ms-flexbox!important;display:flex!important;-ms-flex-preferred-size:auto;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}}.navbar-expand{-ms-flex-wrap:nowrap;flex-wrap:nowrap;-ms-flex-pack:start;justify-content:flex-start}.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 .navbar-nav-scroll{overflow:visible}.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,.55)}.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 .nav-link.active,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.55);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.55)}.navbar-light .navbar-text a,.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,.55)}.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 .nav-link.active,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.55);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.55)}.navbar-dark .navbar-text a,.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:2px solid rgba(0,0,0,.125);border-radius:.15rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.15rem - 2px);border-top-right-radius:calc(.15rem - 2px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.15rem - 2px);border-bottom-left-radius:calc(.15rem - 2px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{-ms-flex:1 1 auto;flex:1 1 auto;padding:1rem 1rem;color:#fff}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link:hover{text-decoration:none}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;color:#fff;background-color:rgba(0,0,0,.03);border-bottom:2px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.15rem - 2px) calc(.15rem - 2px) 0 0}.card-footer{padding:.5rem 1rem;color:#fff;background-color:rgba(0,0,0,.03);border-top:2px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.15rem - 2px) calc(.15rem - 2px)}.card-header-tabs{margin-right:-.5rem;margin-bottom:-.5rem;margin-left:-.5rem;border-bottom:0}.card-header-tabs .nav-link.active{background-color:#fff;border-bottom-color:#fff}.card-header-pills{margin-right:-.5rem;margin-left:-.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(.15rem - 2px)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.15rem - 2px);border-top-right-radius:calc(.15rem - 2px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.15rem - 2px);border-bottom-left-radius:calc(.15rem - 2px)}.card-group>.card{margin-bottom:.75rem}@media (min-width:576px){.card-group{display:-ms-flexbox;display:flex;-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}}.accordion-button{position:relative;display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#32fbe2;text-align:left;background-color:#1a0933;border:0;border-radius:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#643bae;background-color:#fdebf8;box-shadow:inset 0 0 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23643bae'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");-webkit-transform:rotate(-180deg);transform:rotate(-180deg)}.accordion-button::after{-ms-flex-negative:0;flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%2332fbe2'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:1.25rem;transition:-webkit-transform .2s ease-in-out;transition:transform .2s ease-in-out;transition:transform .2s ease-in-out,-webkit-transform .2s ease-in-out}@media (prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#f59cdc;outline:0;box-shadow:0 0 0 .25rem rgba(234,57,184,.25)}.accordion-header{margin-bottom:0}.accordion-item{background-color:#1a0933;border:0 solid rgba(0,0,0,.125)}.accordion-item:first-of-type{border-top-left-radius:.15rem;border-top-right-radius:.15rem}.accordion-item:first-of-type .accordion-button{border-top-left-radius:.15rem;border-top-right-radius:.15rem}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:.15rem;border-bottom-left-radius:.15rem}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:.15rem;border-bottom-left-radius:.15rem}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:.15rem;border-bottom-left-radius:.15rem}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button{border-radius:0}.breadcrumb{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:0 0;margin-bottom:1rem;list-style:none}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:rgba(50,251,226,.3);content:var(--bs-breadcrumb-divider, "/")}.breadcrumb-item.active{color:#ea39b8}.pagination{display:-ms-flexbox;display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#32fbe2;text-decoration:none;background-color:transparent;border:0 solid #dee2e6;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){.page-link{transition:none}}.page-link:hover{z-index:2;color:#28c9b5;background-color:transparent;border-color:#dee2e6}.page-link:focus{z-index:3;color:#28c9b5;background-color:transparent;outline:0;box-shadow:0 0 0 .25rem rgba(234,57,184,.25)}.page-item:not(:first-child) .page-link{margin-left:0}.page-item.active .page-link{z-index:3;color:#fff;background-color:#ea39b8;border-color:#ea39b8}.page-item.disabled .page-link{color:rgba(50,251,226,.3);pointer-events:none;background-color:transparent;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.page-item:first-child .page-link{border-top-left-radius:.15rem;border-bottom-left-radius:.15rem}.page-item:last-child .page-link{border-top-right-radius:.15rem;border-bottom-right-radius:.15rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.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}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.05rem;border-bottom-left-radius:.05rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.05rem;border-bottom-right-radius:.05rem}.badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.15rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:0 solid transparent;border-radius:.15rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-primary{color:#432874;background-color:#e2d9f3;border-color:#d4c6ec}.alert-primary .alert-link{color:#36205d}.alert-secondary{color:#8c226e;background-color:#fbd7f1;border-color:#f9c4ea}.alert-secondary .alert-link{color:#701b58}.alert-success{color:#24914d;background-color:#d8fce6;border-color:#c5fbd9}.alert-success .alert-link{color:#1d743e}.alert-info{color:#106194;background-color:#d1ecfd;border-color:#bbe3fc}.alert-info .alert-link{color:#0d4e76}.alert-warning{color:#997404;background-color:#fff3cd;border-color:#ffecb5}.alert-warning .alert-link{color:#7a5d03}.alert-danger{color:#892e33;background-color:#fadbdd;border-color:#f7c9cc}.alert-danger .alert-link{color:#6e2529}.alert-light{color:#29828b;background-color:#daf7fa;border-color:#c7f4f8}.alert-light .alert-link{color:#21686f}.alert-dark{color:#0e0119;background-color:#d1ccd4;border-color:#b9b3bf}.alert-dark .alert-link{color:#0b0114}@-webkit-keyframes progress-bar-stripes{0%{background-position-x:1rem}}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress{display:-ms-flexbox;display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#250d49;border-radius:.15rem}.progress-bar{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:center;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#6f42c1;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:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.list-group{display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.15rem}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#32fbe2;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#fff;text-decoration:none;background-color:#ea39b8}.list-group-item-action:active{color:#32fbe2;background-color:#ea39b8}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#fff;text-decoration:none;background-color:#250d49;border:0 solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:rgba(50,251,226,.3);pointer-events:none;background-color:#250d49}.list-group-item.active{z-index:2;color:#fff;background-color:#ea39b8;border-color:#ea39b8}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:0;border-top-width:0}.list-group-horizontal{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.15rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.15rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:0;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:0;border-left-width:0}@media (min-width:576px){.list-group-horizontal-sm{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.15rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.15rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:0;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:0;border-left-width:0}}@media (min-width:768px){.list-group-horizontal-md{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.15rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.15rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:0;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:0;border-left-width:0}}@media (min-width:992px){.list-group-horizontal-lg{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.15rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.15rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:0;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:0;border-left-width:0}}@media (min-width:1200px){.list-group-horizontal-xl{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.15rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.15rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:0;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:0;border-left-width:0}}@media (min-width:1400px){.list-group-horizontal-xxl{-ms-flex-direction:row;flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child{border-bottom-left-radius:.15rem;border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child{border-top-right-radius:.15rem;border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:0;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:0;border-left-width:0}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 0}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#432874;background-color:#e2d9f3}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#432874;background-color:#cbc3db}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#432874;border-color:#432874}.list-group-item-secondary{color:#8c226e;background-color:#fbd7f1}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#8c226e;background-color:#e2c2d9}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#8c226e;border-color:#8c226e}.list-group-item-success{color:#24914d;background-color:#d8fce6}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#24914d;background-color:#c2e3cf}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#24914d;border-color:#24914d}.list-group-item-info{color:#106194;background-color:#d1ecfd}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#106194;background-color:#bcd4e4}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#106194;border-color:#106194}.list-group-item-warning{color:#997404;background-color:#fff3cd}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#997404;background-color:#e6dbb9}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#997404;border-color:#997404}.list-group-item-danger{color:#892e33;background-color:#fadbdd}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#892e33;background-color:#e1c5c7}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#892e33;border-color:#892e33}.list-group-item-light{color:#29828b;background-color:#daf7fa}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#29828b;background-color:#c4dee1}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#29828b;border-color:#29828b}.list-group-item-dark{color:#0e0119;background-color:#d1ccd4}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#0e0119;background-color:#bcb8bf}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#0e0119;border-color:#0e0119}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#000;background:transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;border-radius:.15rem;opacity:.5}.btn-close:hover{color:#000;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(234,57,184,.25);opacity:1}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;opacity:.25}.btn-close-white{-webkit-filter:invert(1) grayscale(100%) brightness(200%);filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:.875rem;color:#fff;pointer-events:auto;background-color:#6f42c1;background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(0,0,0,.15);border-radius:.15rem}.toast:not(.showing):not(.show){opacity:0}.toast.hide{display:none}.toast-container{width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:.75rem}.toast-header{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;padding:.5rem .75rem;color:#fff;background-color:#6f42c1;background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.15rem - 1px);border-top-right-radius:calc(.15rem - 1px)}.toast-header .btn-close{margin-right:-.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal{position:fixed;top:0;left:0;z-index:1060;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;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.modal-static .modal-dialog{-webkit-transform:scale(1.02);transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.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-content{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;width:100%;color:#fff;pointer-events:auto;background-color:#6f42c1;background-clip:padding-box;border:0 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-negative:0;flex-shrink:0;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem;border-bottom:0 solid #dee2e6;border-top-left-radius:.3rem;border-top-right-radius:.3rem}.modal-header .btn-close{padding:.5rem .5rem;margin:-.5rem -.5rem -.5rem 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-wrap:wrap;flex-wrap:wrap;-ms-flex-negative:0;flex-shrink:0;-ms-flex-align:center;align-items:center;-ms-flex-pack:end;justify-content:flex-end;padding:.75rem;border-top:0 solid #dee2e6;border-bottom-right-radius:.3rem;border-bottom-left-radius:.3rem}.modal-footer>*{margin:.25rem}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 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}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}.modal-fullscreen .modal-footer{border-radius:0}@media (max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}.modal-fullscreen-sm-down .modal-footer{border-radius:0}}@media (max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}.modal-fullscreen-md-down .modal-footer{border-radius:0}}@media (max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}.modal-fullscreen-lg-down .modal-footer{border-radius:0}}@media (max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}.modal-fullscreen-xl-down .modal-footer{border-radius:0}}@media (max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}.modal-fullscreen-xxl-down .modal-footer{border-radius:0}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:var(--bs-font-sans-serif);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:1}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#170229}.bs-tooltip-auto[data-popper-placement^=right],.bs-tooltip-end{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#170229}.bs-tooltip-auto[data-popper-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#170229}.bs-tooltip-auto[data-popper-placement^=left],.bs-tooltip-start{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#170229}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#170229;border-radius:.15rem}.popover{position:absolute;top:0;left:0;z-index:1070;display:block;max-width:276px;font-family:var(--bs-font-sans-serif);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:#6f42c1;background-clip:padding-box;border:0 solid rgba(0,0,0,.2);border-radius:.3rem}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:-.5rem}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:0;border-width:.5rem .5rem 0;border-top-color:#6f42c1}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:-.5rem;width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:#6f42c1}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:-.5rem}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:#6f42c1}.bs-popover-auto[data-popper-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:0 solid #683eb5}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:-.5rem;width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:#6f42c1}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;color:#fff;background-color:#683eb5;border-bottom:0 solid #5e38a3;border-top-left-radius:.3rem;border-top-right-radius:.3rem}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#fff}.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-end,.carousel-item-next:not(.carousel-item-start){-webkit-transform:translateX(100%);transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){-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-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{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%;padding:0;color:#fff;text-align:center;background:0 0;border:0;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:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:-ms-flexbox;display:flex;-ms-flex-pack:center;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;-ms-flex:0 1 auto;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-next-icon,.carousel-dark .carousel-control-prev-icon{-webkit-filter:invert(1) grayscale(100);filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@-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:-.125em;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:.75s linear infinite spinner-border;animation:.75s linear infinite spinner-border}.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;-webkit-transform:none;transform:none}}@keyframes spinner-grow{0%{-webkit-transform:scale(0);transform:scale(0)}50%{opacity:1;-webkit-transform:none;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:.75s linear infinite spinner-grow;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1050;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;max-width:100%;color:#fff;visibility:hidden;background-color:#6f42c1;background-clip:padding-box;outline:0;transition:-webkit-transform .3s ease-in-out;transition:transform .3s ease-in-out;transition:transform .3s ease-in-out,-webkit-transform .3s ease-in-out}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas-header{display:-ms-flexbox;display:flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:justify;justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin:-.5rem -.5rem -.5rem auto}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{-ms-flex-positive:1;flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:0 solid rgba(0,0,0,.2);-webkit-transform:translateX(-100%);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:0 solid rgba(0,0,0,.2);-webkit-transform:translateX(100%);transform:translateX(100%)}.offcanvas-top{top:0;right:0;left:0;height:30vh;max-height:100%;border-bottom:0 solid rgba(0,0,0,.2);-webkit-transform:translateY(-100%);transform:translateY(-100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:0 solid rgba(0,0,0,.2);-webkit-transform:translateY(100%);transform:translateY(100%)}.offcanvas.show{-webkit-transform:none;transform:none}.clearfix::after{display:block;clear:both;content:""}.link-primary{color:#6f42c1}.link-primary:focus,.link-primary:hover{color:#59359a}.link-secondary{color:#ea39b8}.link-secondary:focus,.link-secondary:hover{color:#bb2e93}.link-success{color:#3cf281}.link-success:focus,.link-success:hover{color:#30c267}.link-info{color:#1ba2f6}.link-info:focus,.link-info:hover{color:#1682c5}.link-warning{color:#ffc107}.link-warning:focus,.link-warning:hover{color:#cc9a06}.link-danger{color:#e44c55}.link-danger:focus,.link-danger:hover{color:#b63d44}.link-light{color:#44d9e8}.link-light:focus,.link-light:hover{color:#36aeba}.link-dark{color:#170229}.link-dark:focus,.link-dark:hover{color:#120221}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:calc(3 / 4 * 100%)}.ratio-16x9{--bs-aspect-ratio:calc(9 / 16 * 100%)}.ratio-21x9{--bs-aspect-ratio:calc(9 / 21 * 100%)}.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}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}@media (min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.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}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!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}.d-none{display:none!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!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}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{-webkit-transform:translate(-50%,-50%)!important;transform:translate(-50%,-50%)!important}.translate-middle-x{-webkit-transform:translateX(-50%)!important;transform:translateX(-50%)!important}.translate-middle-y{-webkit-transform:translateY(-50%)!important;transform:translateY(-50%)!important}.border{border:0 solid #dee2e6!important}.border-0{border:0!important}.border-top{border-top:0 solid #dee2e6!important}.border-top-0{border-top:0!important}.border-end{border-right:0 solid #dee2e6!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:0 solid #dee2e6!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:0 solid #dee2e6!important}.border-start-0{border-left:0!important}.border-primary{border-color:#6f42c1!important}.border-secondary{border-color:#ea39b8!important}.border-success{border-color:#3cf281!important}.border-info{border-color:#1ba2f6!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#e44c55!important}.border-light{border-color:#44d9e8!important}.border-dark{border-color:#170229!important}.border-white{border-color:#fff!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!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}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!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}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.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-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}.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}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!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}.justify-content-evenly{-ms-flex-pack:space-evenly!important;justify-content:space-evenly!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}.order-first{-ms-flex-order:-1!important;order:-1!important}.order-0{-ms-flex-order:0!important;order:0!important}.order-1{-ms-flex-order:1!important;order:1!important}.order-2{-ms-flex-order:2!important;order:2!important}.order-3{-ms-flex-order:3!important;order:3!important}.order-4{-ms-flex-order:4!important;order:4!important}.order-5{-ms-flex-order:5!important;order:5!important}.order-last{-ms-flex-order:6!important;order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-3{margin-right:1rem!important;margin-left:1rem!important}.mx-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-5{margin-right:3rem!important;margin-left:3rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-3{padding-right:1rem!important;padding-left:1rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-5{padding-right:3rem!important;padding-left:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-light{font-weight:300!important}.fw-lighter{font-weight:lighter!important}.fw-normal{font-weight:400!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{color:#6f42c1!important}.text-secondary{color:#ea39b8!important}.text-success{color:#3cf281!important}.text-info{color:#1ba2f6!important}.text-warning{color:#ffc107!important}.text-danger{color:#e44c55!important}.text-light{color:#44d9e8!important}.text-dark{color:#170229!important}.text-white{color:#fff!important}.text-body{color:#32fbe2!important}.text-muted{color:rgba(50,251,226,.3)!important}.text-black-50{color:rgba(0,0,0,.5)!important}.text-white-50{color:rgba(255,255,255,.5)!important}.text-reset{color:inherit!important}.bg-primary{background-color:#6f42c1!important}.bg-secondary{background-color:#ea39b8!important}.bg-success{background-color:#3cf281!important}.bg-info{background-color:#1ba2f6!important}.bg-warning{background-color:#ffc107!important}.bg-danger{background-color:#e44c55!important}.bg-light{background-color:#44d9e8!important}.bg-dark{background-color:#170229!important}.bg-body{background-color:#1a0933!important}.bg-white{background-color:#fff!important}.bg-transparent{background-color:transparent!important}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;-ms-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;-ms-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:.15rem!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:.05rem!important}.rounded-2{border-radius:.15rem!important}.rounded-3{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-top{border-top-left-radius:.15rem!important;border-top-right-radius:.15rem!important}.rounded-end{border-top-right-radius:.15rem!important;border-bottom-right-radius:.15rem!important}.rounded-bottom{border-bottom-right-radius:.15rem!important;border-bottom-left-radius:.15rem!important}.rounded-start{border-bottom-left-radius:.15rem!important;border-top-left-radius:.15rem!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media (min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!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}.d-sm-none{display:none!important}.flex-sm-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.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-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}.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}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!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}.justify-content-sm-evenly{-ms-flex-pack:space-evenly!important;justify-content:space-evenly!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}.order-sm-first{-ms-flex-order:-1!important;order:-1!important}.order-sm-0{-ms-flex-order:0!important;order:0!important}.order-sm-1{-ms-flex-order:1!important;order:1!important}.order-sm-2{-ms-flex-order:2!important;order:2!important}.order-sm-3{-ms-flex-order:3!important;order:3!important}.order-sm-4{-ms-flex-order:4!important;order:4!important}.order-sm-5{-ms-flex-order:5!important;order:5!important}.order-sm-last{-ms-flex-order:6!important;order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!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}.d-md-none{display:none!important}.flex-md-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.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-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}.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}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!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}.justify-content-md-evenly{-ms-flex-pack:space-evenly!important;justify-content:space-evenly!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}.order-md-first{-ms-flex-order:-1!important;order:-1!important}.order-md-0{-ms-flex-order:0!important;order:0!important}.order-md-1{-ms-flex-order:1!important;order:1!important}.order-md-2{-ms-flex-order:2!important;order:2!important}.order-md-3{-ms-flex-order:3!important;order:3!important}.order-md-4{-ms-flex-order:4!important;order:4!important}.order-md-5{-ms-flex-order:5!important;order:5!important}.order-md-last{-ms-flex-order:6!important;order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!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}.d-lg-none{display:none!important}.flex-lg-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.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-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}.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}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!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}.justify-content-lg-evenly{-ms-flex-pack:space-evenly!important;justify-content:space-evenly!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}.order-lg-first{-ms-flex-order:-1!important;order:-1!important}.order-lg-0{-ms-flex-order:0!important;order:0!important}.order-lg-1{-ms-flex-order:1!important;order:1!important}.order-lg-2{-ms-flex-order:2!important;order:2!important}.order-lg-3{-ms-flex-order:3!important;order:3!important}.order-lg-4{-ms-flex-order:4!important;order:4!important}.order-lg-5{-ms-flex-order:5!important;order:5!important}.order-lg-last{-ms-flex-order:6!important;order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!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}.d-xl-none{display:none!important}.flex-xl-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.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-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}.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}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!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}.justify-content-xl-evenly{-ms-flex-pack:space-evenly!important;justify-content:space-evenly!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}.order-xl-first{-ms-flex-order:-1!important;order:-1!important}.order-xl-0{-ms-flex-order:0!important;order:0!important}.order-xl-1{-ms-flex-order:1!important;order:1!important}.order-xl-2{-ms-flex-order:2!important;order:2!important}.order-xl-3{-ms-flex-order:3!important;order:3!important}.order-xl-4{-ms-flex-order:4!important;order:4!important}.order-xl-5{-ms-flex-order:5!important;order:5!important}.order-xl-last{-ms-flex-order:6!important;order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:-ms-flexbox!important;display:flex!important}.d-xxl-inline-flex{display:-ms-inline-flexbox!important;display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{-ms-flex:1 1 auto!important;flex:1 1 auto!important}.flex-xxl-row{-ms-flex-direction:row!important;flex-direction:row!important}.flex-xxl-column{-ms-flex-direction:column!important;flex-direction:column!important}.flex-xxl-row-reverse{-ms-flex-direction:row-reverse!important;flex-direction:row-reverse!important}.flex-xxl-column-reverse{-ms-flex-direction:column-reverse!important;flex-direction:column-reverse!important}.flex-xxl-grow-0{-ms-flex-positive:0!important;flex-grow:0!important}.flex-xxl-grow-1{-ms-flex-positive:1!important;flex-grow:1!important}.flex-xxl-shrink-0{-ms-flex-negative:0!important;flex-shrink:0!important}.flex-xxl-shrink-1{-ms-flex-negative:1!important;flex-shrink:1!important}.flex-xxl-wrap{-ms-flex-wrap:wrap!important;flex-wrap:wrap!important}.flex-xxl-nowrap{-ms-flex-wrap:nowrap!important;flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{-ms-flex-wrap:wrap-reverse!important;flex-wrap:wrap-reverse!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.justify-content-xxl-start{-ms-flex-pack:start!important;justify-content:flex-start!important}.justify-content-xxl-end{-ms-flex-pack:end!important;justify-content:flex-end!important}.justify-content-xxl-center{-ms-flex-pack:center!important;justify-content:center!important}.justify-content-xxl-between{-ms-flex-pack:justify!important;justify-content:space-between!important}.justify-content-xxl-around{-ms-flex-pack:distribute!important;justify-content:space-around!important}.justify-content-xxl-evenly{-ms-flex-pack:space-evenly!important;justify-content:space-evenly!important}.align-items-xxl-start{-ms-flex-align:start!important;align-items:flex-start!important}.align-items-xxl-end{-ms-flex-align:end!important;align-items:flex-end!important}.align-items-xxl-center{-ms-flex-align:center!important;align-items:center!important}.align-items-xxl-baseline{-ms-flex-align:baseline!important;align-items:baseline!important}.align-items-xxl-stretch{-ms-flex-align:stretch!important;align-items:stretch!important}.align-content-xxl-start{-ms-flex-line-pack:start!important;align-content:flex-start!important}.align-content-xxl-end{-ms-flex-line-pack:end!important;align-content:flex-end!important}.align-content-xxl-center{-ms-flex-line-pack:center!important;align-content:center!important}.align-content-xxl-between{-ms-flex-line-pack:justify!important;align-content:space-between!important}.align-content-xxl-around{-ms-flex-line-pack:distribute!important;align-content:space-around!important}.align-content-xxl-stretch{-ms-flex-line-pack:stretch!important;align-content:stretch!important}.align-self-xxl-auto{-ms-flex-item-align:auto!important;align-self:auto!important}.align-self-xxl-start{-ms-flex-item-align:start!important;align-self:flex-start!important}.align-self-xxl-end{-ms-flex-item-align:end!important;align-self:flex-end!important}.align-self-xxl-center{-ms-flex-item-align:center!important;align-self:center!important}.align-self-xxl-baseline{-ms-flex-item-align:baseline!important;align-self:baseline!important}.align-self-xxl-stretch{-ms-flex-item-align:stretch!important;align-self:stretch!important}.order-xxl-first{-ms-flex-order:-1!important;order:-1!important}.order-xxl-0{-ms-flex-order:0!important;order:0!important}.order-xxl-1{-ms-flex-order:1!important;order:1!important}.order-xxl-2{-ms-flex-order:2!important;order:2!important}.order-xxl-3{-ms-flex-order:3!important;order:3!important}.order-xxl-4{-ms-flex-order:4!important;order:4!important}.order-xxl-5{-ms-flex-order:5!important;order:5!important}.order-xxl-last{-ms-flex-order:6!important;order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!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}.d-print-none{display:none!important}}body{background-image:linear-gradient(#0f051d 0,#1a0933 7%,#1a0933 80%,#061129 100%);text-shadow:0 0 1px rgba(50,251,226,.3),0 0 2px rgba(50,251,226,.3),0 0 5px rgba(50,251,226,.2)}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{text-shadow:0 0 1px rgba(50,251,226,.6),0 0 3px rgba(50,251,226,.5),0 0 .5rem rgba(50,251,226,.3),0 0 2rem rgba(50,251,226,.2)}.text-primary{text-shadow:0 0 1px rgba(111,66,193,.3),0 0 2px rgba(111,66,193,.3),0 0 5px rgba(111,66,193,.2)}.text-secondary{text-shadow:0 0 1px rgba(234,57,184,.3),0 0 2px rgba(234,57,184,.3),0 0 5px rgba(234,57,184,.2)}.text-success{text-shadow:0 0 1px rgba(60,242,129,.3),0 0 2px rgba(60,242,129,.3),0 0 5px rgba(60,242,129,.2)}.text-info{text-shadow:0 0 1px rgba(27,162,246,.3),0 0 2px rgba(27,162,246,.3),0 0 5px rgba(27,162,246,.2)}.text-warning{text-shadow:0 0 1px rgba(255,193,7,.3),0 0 2px rgba(255,193,7,.3),0 0 5px rgba(255,193,7,.2)}.text-danger{text-shadow:0 0 1px rgba(228,76,85,.3),0 0 2px rgba(228,76,85,.3),0 0 5px rgba(228,76,85,.2)}.text-light{text-shadow:0 0 1px rgba(68,217,232,.3),0 0 2px rgba(68,217,232,.3),0 0 5px rgba(68,217,232,.2)}.text-dark{text-shadow:0 0 1px rgba(23,2,41,.3),0 0 2px rgba(23,2,41,.3),0 0 5px rgba(23,2,41,.2)}.text-white{text-shadow:0 0 1px rgba(255,255,255,.3),0 0 2px rgba(255,255,255,.3),0 0 5px rgba(255,255,255,.2)}.text-white .h1,.text-white .h2,.text-white .h3,.text-white .h4,.text-white .h5,.text-white .h6,.text-white h1,.text-white h2,.text-white h3,.text-white h4,.text-white h5,.text-white h6{text-shadow:0 0 1px rgba(255,255,255,.6),0 0 3px rgba(255,255,255,.5),0 0 .5rem rgba(255,255,255,.3),0 0 2rem rgba(255,255,255,.2)}.text-muted{text-shadow:0 0 1px rgba(5,245,215,.3),0 0 2px rgba(5,245,215,.3),0 0 5px rgba(5,245,215,.2)}a{text-shadow:0 0 1px rgba(50,251,226,.3),0 0 2px rgba(50,251,226,.3),0 0 5px rgba(50,251,226,.2)}.blockquote-footer{text-shadow:0 0 1px rgba(5,245,215,.3),0 0 2px rgba(5,245,215,.3),0 0 5px rgba(5,245,215,.2)}.table,table{text-shadow:0 0 1px rgba(255,255,255,.3),0 0 2px rgba(255,255,255,.3),0 0 5px rgba(255,255,255,.2)}.btn-outline-primary,.btn-primary{box-shadow:0 0 2px rgba(111,66,193,.9),0 0 4px rgba(111,66,193,.4),0 0 1rem rgba(111,66,193,.3),0 0 4rem rgba(111,66,193,.1)}.btn-primary{text-shadow:0 0 1px rgba(255,255,255,.3),0 0 2px rgba(255,255,255,.3),0 0 5px rgba(255,255,255,.2)}.btn-outline-primary{border-width:2px;color:#fff}.btn-outline-secondary,.btn-secondary{box-shadow:0 0 2px rgba(234,57,184,.9),0 0 4px rgba(234,57,184,.4),0 0 1rem rgba(234,57,184,.3),0 0 4rem rgba(234,57,184,.1)}.btn-secondary{text-shadow:0 0 1px rgba(255,255,255,.3),0 0 2px rgba(255,255,255,.3),0 0 5px rgba(255,255,255,.2)}.btn-outline-secondary{border-width:2px;color:#fff}.btn-outline-success,.btn-success{box-shadow:0 0 2px rgba(60,242,129,.9),0 0 4px rgba(60,242,129,.4),0 0 1rem rgba(60,242,129,.3),0 0 4rem rgba(60,242,129,.1)}.btn-success{text-shadow:0 0 1px rgba(255,255,255,.3),0 0 2px rgba(255,255,255,.3),0 0 5px rgba(255,255,255,.2)}.btn-outline-success{border-width:2px;color:#fff}.btn-info,.btn-outline-info{box-shadow:0 0 2px rgba(27,162,246,.9),0 0 4px rgba(27,162,246,.4),0 0 1rem rgba(27,162,246,.3),0 0 4rem rgba(27,162,246,.1)}.btn-info{text-shadow:0 0 1px rgba(255,255,255,.3),0 0 2px rgba(255,255,255,.3),0 0 5px rgba(255,255,255,.2)}.btn-outline-info{border-width:2px;color:#fff}.btn-outline-warning,.btn-warning{box-shadow:0 0 2px rgba(255,193,7,.9),0 0 4px rgba(255,193,7,.4),0 0 1rem rgba(255,193,7,.3),0 0 4rem rgba(255,193,7,.1)}.btn-warning{text-shadow:0 0 1px rgba(255,255,255,.3),0 0 2px rgba(255,255,255,.3),0 0 5px rgba(255,255,255,.2)}.btn-outline-warning{border-width:2px;color:#fff}.btn-danger,.btn-outline-danger{box-shadow:0 0 2px rgba(228,76,85,.9),0 0 4px rgba(228,76,85,.4),0 0 1rem rgba(228,76,85,.3),0 0 4rem rgba(228,76,85,.1)}.btn-danger{text-shadow:0 0 1px rgba(255,255,255,.3),0 0 2px rgba(255,255,255,.3),0 0 5px rgba(255,255,255,.2)}.btn-outline-danger{border-width:2px;color:#fff}.btn-light,.btn-outline-light{box-shadow:0 0 2px rgba(68,217,232,.9),0 0 4px rgba(68,217,232,.4),0 0 1rem rgba(68,217,232,.3),0 0 4rem rgba(68,217,232,.1)}.btn-light{text-shadow:0 0 1px rgba(255,255,255,.3),0 0 2px rgba(255,255,255,.3),0 0 5px rgba(255,255,255,.2)}.btn-outline-light{border-width:2px;color:#fff}.btn-dark,.btn-outline-dark{box-shadow:0 0 2px rgba(23,2,41,.9),0 0 4px rgba(23,2,41,.4),0 0 1rem rgba(23,2,41,.3),0 0 4rem rgba(23,2,41,.1)}.btn-dark{text-shadow:0 0 1px rgba(255,255,255,.3),0 0 2px rgba(255,255,255,.3),0 0 5px rgba(255,255,255,.2)}.btn-outline-dark{border-width:2px;color:#fff}.btn-dark{box-shadow:0 0 2px rgba(111,66,193,.9),0 0 4px rgba(111,66,193,.4),0 0 1rem rgba(111,66,193,.3),0 0 4rem rgba(111,66,193,.1)}.btn-link{box-shadow:none;text-shadow:0 0 1px rgba(50,251,226,.6),0 0 3px rgba(50,251,226,.5),0 0 .5rem rgba(50,251,226,.3),0 0 2rem rgba(50,251,226,.2)}.btn-outline-dark{color:#fff}.navbar.bg-primary{box-shadow:0 0 2px rgba(111,66,193,.9),0 0 4px rgba(111,66,193,.4),0 0 1rem rgba(111,66,193,.3),0 0 4rem rgba(111,66,193,.1)}.navbar.bg-secondary{box-shadow:0 0 2px rgba(234,57,184,.9),0 0 4px rgba(234,57,184,.4),0 0 1rem rgba(234,57,184,.3),0 0 4rem rgba(234,57,184,.1)}.navbar.bg-success{box-shadow:0 0 2px rgba(60,242,129,.9),0 0 4px rgba(60,242,129,.4),0 0 1rem rgba(60,242,129,.3),0 0 4rem rgba(60,242,129,.1)}.navbar.bg-info{box-shadow:0 0 2px rgba(27,162,246,.9),0 0 4px rgba(27,162,246,.4),0 0 1rem rgba(27,162,246,.3),0 0 4rem rgba(27,162,246,.1)}.navbar.bg-warning{box-shadow:0 0 2px rgba(255,193,7,.9),0 0 4px rgba(255,193,7,.4),0 0 1rem rgba(255,193,7,.3),0 0 4rem rgba(255,193,7,.1)}.navbar.bg-danger{box-shadow:0 0 2px rgba(228,76,85,.9),0 0 4px rgba(228,76,85,.4),0 0 1rem rgba(228,76,85,.3),0 0 4rem rgba(228,76,85,.1)}.navbar.bg-light{box-shadow:0 0 2px rgba(68,217,232,.9),0 0 4px rgba(68,217,232,.4),0 0 1rem rgba(68,217,232,.3),0 0 4rem rgba(68,217,232,.1)}.navbar.bg-dark{box-shadow:0 0 2px rgba(23,2,41,.9),0 0 4px rgba(23,2,41,.4),0 0 1rem rgba(23,2,41,.3),0 0 4rem rgba(23,2,41,.1)}.navbar-dark,.navbar-dark a{text-shadow:0 0 1px rgba(255,255,255,.3),0 0 2px rgba(255,255,255,.3),0 0 5px rgba(255,255,255,.2)}.navbar-dark .navbar-brand{text-shadow:0 0 1px rgba(255,255,255,.6),0 0 3px rgba(255,255,255,.5),0 0 .5rem rgba(255,255,255,.3),0 0 2rem rgba(255,255,255,.2)}.navbar-light,.navbar-light a{text-shadow:0 0 1px rgba(0,0,0,.3),0 0 2px rgba(0,0,0,.3),0 0 5px rgba(0,0,0,.2)}.navbar-light .navbar-brand{text-shadow:0 0 1px rgba(0,0,0,.6),0 0 3px rgba(0,0,0,.5),0 0 .5rem rgba(0,0,0,.3),0 0 2rem rgba(0,0,0,.2)}.nav-link.disabled{text-shadow:0 0 1px rgba(50,251,226,.3),0 0 2px rgba(50,251,226,.3),0 0 5px rgba(50,251,226,.2)}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{text-shadow:0 0 1px rgba(234,57,184,.3),0 0 2px rgba(234,57,184,.3),0 0 5px rgba(234,57,184,.2)}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{box-shadow:0 0 2px rgba(234,57,184,.9),0 0 4px rgba(234,57,184,.4),0 0 1rem rgba(234,57,184,.3),0 0 4rem rgba(234,57,184,.1)}.breadcrumb-item.active{text-shadow:0 0 1px rgba(234,57,184,.3),0 0 2px rgba(234,57,184,.3),0 0 5px rgba(234,57,184,.2)}.breadcrumb-item+.breadcrumb-item::before{text-shadow:0 0 1px rgba(50,251,226,.3),0 0 2px rgba(50,251,226,.3),0 0 5px rgba(50,251,226,.2)}.page-link{border-radius:.15rem}.page-item.active .page-link{box-shadow:0 0 2px rgba(234,57,184,.9),0 0 4px rgba(234,57,184,.4),0 0 1rem rgba(234,57,184,.3),0 0 4rem rgba(234,57,184,.1)}legend{text-shadow:0 0 1px rgba(50,251,226,.6),0 0 3px rgba(50,251,226,.5),0 0 .5rem rgba(50,251,226,.3),0 0 2rem rgba(50,251,226,.2)}.valid-feedback{text-shadow:0 0 1px rgba(60,242,129,.3),0 0 2px rgba(60,242,129,.3),0 0 5px rgba(60,242,129,.2)}.invalid-feedback{text-shadow:0 0 1px rgba(228,76,85,.3),0 0 2px rgba(228,76,85,.3),0 0 5px rgba(228,76,85,.2)}.alert-primary{background-color:#6f42c1;color:#fff;text-shadow:0 0 1px rgba(255,255,255,.3),0 0 2px rgba(255,255,255,.3),0 0 5px rgba(255,255,255,.2);box-shadow:0 0 2rem rgba(126,85,199,.4),0 0 8rem rgba(126,85,199,.3)}.alert-secondary{background-color:#ea39b8;color:#fff;text-shadow:0 0 1px rgba(255,255,255,.3),0 0 2px rgba(255,255,255,.3),0 0 5px rgba(255,255,255,.2);box-shadow:0 0 2rem rgba(236,80,192,.4),0 0 8rem rgba(236,80,192,.3)}.alert-success{background-color:#3cf281;color:#fff;text-shadow:0 0 1px rgba(255,255,255,.3),0 0 2px rgba(255,255,255,.3),0 0 5px rgba(255,255,255,.2);box-shadow:0 0 2rem rgba(84,244,144,.4),0 0 8rem rgba(84,244,144,.3)}.alert-info{background-color:#1ba2f6;color:#fff;text-shadow:0 0 1px rgba(255,255,255,.3),0 0 2px rgba(255,255,255,.3),0 0 5px rgba(255,255,255,.2);box-shadow:0 0 2rem rgba(52,172,247,.4),0 0 8rem rgba(52,172,247,.3)}.alert-warning{background-color:#ffc107;color:#fff;text-shadow:0 0 1px rgba(255,255,255,.3),0 0 2px rgba(255,255,255,.3),0 0 5px rgba(255,255,255,.2);box-shadow:0 0 2rem rgba(255,199,33,.4),0 0 8rem rgba(255,199,33,.3)}.alert-danger{background-color:#e44c55;color:#fff;text-shadow:0 0 1px rgba(255,255,255,.3),0 0 2px rgba(255,255,255,.3),0 0 5px rgba(255,255,255,.2);box-shadow:0 0 2rem rgba(231,98,106,.4),0 0 8rem rgba(231,98,106,.3)}.alert-light{background-color:#44d9e8;color:#fff;text-shadow:0 0 1px rgba(255,255,255,.3),0 0 2px rgba(255,255,255,.3),0 0 5px rgba(255,255,255,.2);box-shadow:0 0 2rem rgba(91,222,235,.4),0 0 8rem rgba(91,222,235,.3)}.alert-dark{background-color:#170229;color:#fff;text-shadow:0 0 1px rgba(255,255,255,.3),0 0 2px rgba(255,255,255,.3),0 0 5px rgba(255,255,255,.2);box-shadow:0 0 2rem rgba(37,3,65,.4),0 0 8rem rgba(37,3,65,.3)}.alert .alert-link,.alert a{color:#fff}.alert .h1,.alert .h2,.alert .h3,.alert .h4,.alert .h5,.alert .h6,.alert h1,.alert h2,.alert h3,.alert h4,.alert h5,.alert h6{text-shadow:0 0 1px rgba(255,255,255,.6),0 0 3px rgba(255,255,255,.5),0 0 .5rem rgba(255,255,255,.3),0 0 2rem rgba(255,255,255,.2)}.progress{overflow:visible}.progress-bar{box-shadow:0 0 2px rgba(111,66,193,.9),0 0 4px rgba(111,66,193,.4),0 0 1rem rgba(111,66,193,.3),0 0 4rem rgba(111,66,193,.1)}.progress-bar.bg-primary{box-shadow:0 0 2px rgba(111,66,193,.9),0 0 4px rgba(111,66,193,.4),0 0 1rem rgba(111,66,193,.3),0 0 4rem rgba(111,66,193,.1)}.progress-bar.bg-secondary{box-shadow:0 0 2px rgba(234,57,184,.9),0 0 4px rgba(234,57,184,.4),0 0 1rem rgba(234,57,184,.3),0 0 4rem rgba(234,57,184,.1)}.progress-bar.bg-success{box-shadow:0 0 2px rgba(60,242,129,.9),0 0 4px rgba(60,242,129,.4),0 0 1rem rgba(60,242,129,.3),0 0 4rem rgba(60,242,129,.1)}.progress-bar.bg-info{box-shadow:0 0 2px rgba(27,162,246,.9),0 0 4px rgba(27,162,246,.4),0 0 1rem rgba(27,162,246,.3),0 0 4rem rgba(27,162,246,.1)}.progress-bar.bg-warning{box-shadow:0 0 2px rgba(255,193,7,.9),0 0 4px rgba(255,193,7,.4),0 0 1rem rgba(255,193,7,.3),0 0 4rem rgba(255,193,7,.1)}.progress-bar.bg-danger{box-shadow:0 0 2px rgba(228,76,85,.9),0 0 4px rgba(228,76,85,.4),0 0 1rem rgba(228,76,85,.3),0 0 4rem rgba(228,76,85,.1)}.progress-bar.bg-light{box-shadow:0 0 2px rgba(68,217,232,.9),0 0 4px rgba(68,217,232,.4),0 0 1rem rgba(68,217,232,.3),0 0 4rem rgba(68,217,232,.1)}.progress-bar.bg-dark{box-shadow:0 0 2px rgba(23,2,41,.9),0 0 4px rgba(23,2,41,.4),0 0 1rem rgba(23,2,41,.3),0 0 4rem rgba(23,2,41,.1)}.tooltip .arrow,.tooltip-inner{box-shadow:0 0 2rem rgba(126,85,199,.4),0 0 8rem rgba(126,85,199,.3)}.modal,.popover,.toast{text-shadow:0 0 1px rgba(255,255,255,.3),0 0 2px rgba(255,255,255,.3),0 0 5px rgba(255,255,255,.2)}.modal .h1,.modal .h2,.modal .h3,.modal .h4,.modal .h5,.modal .h6,.modal h1,.modal h2,.modal h3,.modal h4,.modal h5,.modal h6,.popover .h1,.popover .h2,.popover .h3,.popover .h4,.popover .h5,.popover .h6,.popover h1,.popover h2,.popover h3,.popover h4,.popover h5,.popover h6,.toast .h1,.toast .h2,.toast .h3,.toast .h4,.toast .h5,.toast .h6,.toast h1,.toast h2,.toast h3,.toast h4,.toast h5,.toast h6{text-shadow:0 0 1px rgba(255,255,255,.6),0 0 3px rgba(255,255,255,.5),0 0 .5rem rgba(255,255,255,.3),0 0 2rem rgba(255,255,255,.2)}.popover,.toast{box-shadow:0 0 2rem rgba(126,85,199,.4),0 0 8rem rgba(126,85,199,.3)}.modal-content{box-shadow:0 0 2rem rgba(126,85,199,.4),0 0 8rem rgba(126,85,199,.3)}.list-group-item.active .h1,.list-group-item.active .h2,.list-group-item.active .h3,.list-group-item.active .h4,.list-group-item.active .h5,.list-group-item.active .h6,.list-group-item.active h1,.list-group-item.active h2,.list-group-item.active h3,.list-group-item.active h4,.list-group-item.active h5,.list-group-item.active h6{text-shadow:0 0 1px rgba(255,255,255,.6),0 0 3px rgba(255,255,255,.5),0 0 .5rem rgba(255,255,255,.3),0 0 2rem rgba(255,255,255,.2)}.card{background-color:transparent;text-shadow:0 0 1px rgba(255,255,255,.3),0 0 2px rgba(255,255,255,.3),0 0 5px rgba(255,255,255,.2)}.card.border-primary{box-shadow:0 0 2px rgba(111,66,193,.9),0 0 4px rgba(111,66,193,.4),0 0 1rem rgba(111,66,193,.3),0 0 4rem rgba(111,66,193,.1)}.card.border-secondary{box-shadow:0 0 2px rgba(234,57,184,.9),0 0 4px rgba(234,57,184,.4),0 0 1rem rgba(234,57,184,.3),0 0 4rem rgba(234,57,184,.1)}.card.border-success{box-shadow:0 0 2px rgba(60,242,129,.9),0 0 4px rgba(60,242,129,.4),0 0 1rem rgba(60,242,129,.3),0 0 4rem rgba(60,242,129,.1)}.card.border-info{box-shadow:0 0 2px rgba(27,162,246,.9),0 0 4px rgba(27,162,246,.4),0 0 1rem rgba(27,162,246,.3),0 0 4rem rgba(27,162,246,.1)}.card.border-warning{box-shadow:0 0 2px rgba(255,193,7,.9),0 0 4px rgba(255,193,7,.4),0 0 1rem rgba(255,193,7,.3),0 0 4rem rgba(255,193,7,.1)}.card.border-danger{box-shadow:0 0 2px rgba(228,76,85,.9),0 0 4px rgba(228,76,85,.4),0 0 1rem rgba(228,76,85,.3),0 0 4rem rgba(228,76,85,.1)}.card.border-light{box-shadow:0 0 2px rgba(68,217,232,.9),0 0 4px rgba(68,217,232,.4),0 0 1rem rgba(68,217,232,.3),0 0 4rem rgba(68,217,232,.1)}.card.border-dark{box-shadow:0 0 2px rgba(111,66,193,.9),0 0 4px rgba(111,66,193,.4),0 0 1rem rgba(111,66,193,.3),0 0 4rem rgba(111,66,193,.1)}.card .h1,.card .h2,.card .h3,.card .h4,.card .h5,.card .h6,.card h1,.card h2,.card h3,.card h4,.card h5,.card h6{text-shadow:0 0 1px rgba(255,255,255,.6),0 0 3px rgba(255,255,255,.5),0 0 .5rem rgba(255,255,255,.3),0 0 2rem rgba(255,255,255,.2)} \ No newline at end of file diff --git a/www/style/features.css b/www/style/features.css new file mode 100644 index 0000000..33942f7 --- /dev/null +++ b/www/style/features.css @@ -0,0 +1,61 @@ +.b-example-divider { + height: 3rem; + background-color: rgba(0, 0, 0, .1); + border: solid rgba(0, 0, 0, .15); + border-width: 1px 0; + box-shadow: inset 0 .5em 1.5em rgba(0, 0, 0, .1), inset 0 .125em .5em rgba(0, 0, 0, .15); +} + +.bi { + vertical-align: -.125em; + fill: currentColor; +} + +.feature-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 4rem; + height: 4rem; + margin-bottom: 1rem; + font-size: 2rem; + color: #fff; + border-radius: .75rem; +} + +.icon-link { + display: inline-flex; + align-items: center; +} +.icon-link > .bi { + margin-top: .125rem; + margin-left: .125rem; + transition: transform .25s ease-in-out; + fill: currentColor; +} +.icon-link:hover > .bi { + transform: translate(.25rem); +} + +.icon-square { + display: inline-flex; + align-items: center; + justify-content: center; + width: 3rem; + height: 3rem; + font-size: 1.5rem; + border-radius: .75rem; +} + +.rounded-4 { border-radius: .5rem; } +.rounded-5 { border-radius: 1rem; } + +.text-shadow-1 { text-shadow: 0 .125rem .25rem rgba(0, 0, 0, .25); } +.text-shadow-2 { text-shadow: 0 .25rem .5rem rgba(0, 0, 0, .25); } +.text-shadow-3 { text-shadow: 0 .5rem 1.5rem rgba(0, 0, 0, .25); } + +.card-cover { + background-repeat: no-repeat; + background-position: center center; + background-size: cover; +}