From 9c97b64006c91c3a47d842a04ff07340b4324236 Mon Sep 17 00:00:00 2001 From: Denis Shevchenko Date: Tue, 29 Mar 2022 17:22:49 +0400 Subject: [PATCH] cardano-tracer: init RTView --- cardano-tracer/bench/cardano-tracer-bench.hs | 16 +- cardano-tracer/cardano-tracer.cabal | 55 ++ .../src/Cardano/Tracer/Acceptors/Client.hs | 11 +- .../src/Cardano/Tracer/Acceptors/Run.hs | 8 +- .../src/Cardano/Tracer/Acceptors/Server.hs | 13 +- cardano-tracer/src/Cardano/Tracer/CLI.hs | 27 +- .../src/Cardano/Tracer/Configuration.hs | 4 +- .../src/Cardano/Tracer/Handlers/CheckMode.hs | 42 + .../Tracer/Handlers/Logs/TraceObjects.hs | 18 +- .../Tracer/Handlers/Metrics/Monitoring.hs | 5 +- .../Tracer/Handlers/Metrics/Prometheus.hs | 2 + .../Cardano/Tracer/Handlers/Metrics/Utils.hs | 26 + .../src/Cardano/Tracer/Handlers/RTView/Run.hs | 103 +++ .../Tracer/Handlers/RTView/State/Displayed.hs | 125 +++ .../Handlers/RTView/State/EraSettings.hs | 44 + .../Tracer/Handlers/RTView/State/Errors.hs | 132 +++ .../Handlers/RTView/State/Historical.hs | 204 ++++ .../Tracer/Handlers/RTView/State/Last.hs | 53 ++ .../Tracer/Handlers/RTView/State/Peers.hs | 73 ++ .../Handlers/RTView/State/TraceObjects.hs | 62 ++ .../Cardano/Tracer/Handlers/RTView/System.hs | 39 + .../Tracer/Handlers/RTView/UI/CSS/Bulma.hs | 40 + .../Tracer/Handlers/RTView/UI/CSS/Own.hs | 875 ++++++++++++++++++ .../Tracer/Handlers/RTView/UI/Charts.hs | 298 ++++++ .../Tracer/Handlers/RTView/UI/HTML/About.hs | 103 +++ .../Tracer/Handlers/RTView/UI/HTML/Body.hs | 682 ++++++++++++++ .../Tracer/Handlers/RTView/UI/HTML/Main.hs | 166 ++++ .../Tracer/Handlers/RTView/UI/HTML/NoNodes.hs | 136 +++ .../Handlers/RTView/UI/HTML/Node/Column.hs | 243 +++++ .../Handlers/RTView/UI/HTML/Node/EKG.hs | 48 + .../Handlers/RTView/UI/HTML/Node/Errors.hs | 121 +++ .../Handlers/RTView/UI/HTML/Node/Peers.hs | 62 ++ .../Handlers/RTView/UI/HTML/Notifications.hs | 62 ++ .../Tracer/Handlers/RTView/UI/Img/Icons.hs | 354 +++++++ .../Tracer/Handlers/RTView/UI/JS/ChartJS.hs | 55 ++ .../Tracer/Handlers/RTView/UI/JS/Charts.hs | 234 +++++ .../Tracer/Handlers/RTView/UI/JS/Utils.hs | 61 ++ .../Tracer/Handlers/RTView/UI/Theme.hs | 78 ++ .../Tracer/Handlers/RTView/UI/Types.hs | 84 ++ .../Tracer/Handlers/RTView/UI/Utils.hs | 230 +++++ .../Tracer/Handlers/RTView/Update/Chain.hs | 49 + .../Tracer/Handlers/RTView/Update/EKG.hs | 36 + .../Handlers/RTView/Update/EraSettings.hs | 51 + .../Tracer/Handlers/RTView/Update/Errors.hs | 215 +++++ .../Handlers/RTView/Update/Historical.hs | 53 ++ .../Tracer/Handlers/RTView/Update/KES.hs | 58 ++ .../Handlers/RTView/Update/Leadership.hs | 68 ++ .../Tracer/Handlers/RTView/Update/NodeInfo.hs | 73 ++ .../Handlers/RTView/Update/NodeState.hs | 49 + .../Tracer/Handlers/RTView/Update/Nodes.hs | 320 +++++++ .../Tracer/Handlers/RTView/Update/Peers.hs | 146 +++ .../Tracer/Handlers/RTView/Update/Reload.hs | 41 + .../Handlers/RTView/Update/Resources.hs | 100 ++ .../Handlers/RTView/Update/Transactions.hs | 41 + .../Tracer/Handlers/RTView/Update/Utils.hs | 73 ++ cardano-tracer/src/Cardano/Tracer/Run.hs | 25 +- .../test/Cardano/Tracer/Test/Acceptor.hs | 5 +- .../Cardano/Tracer/Test/DataPoint/Tests.hs | 3 +- .../test/Cardano/Tracer/Test/Logs/Tests.hs | 9 +- .../test/Cardano/Tracer/Test/Restart/Tests.hs | 11 +- 60 files changed, 6366 insertions(+), 54 deletions(-) create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/CheckMode.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Utils.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Run.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Displayed.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/EraSettings.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Errors.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Historical.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Last.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Peers.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/TraceObjects.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/System.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/CSS/Bulma.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/CSS/Own.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/Charts.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/About.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Body.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Main.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/NoNodes.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Node/Column.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Node/EKG.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Node/Errors.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Node/Peers.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Notifications.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/Img/Icons.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/JS/ChartJS.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/JS/Charts.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/JS/Utils.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/Theme.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/Types.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/Utils.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Chain.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/EKG.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/EraSettings.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Errors.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Historical.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/KES.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Leadership.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/NodeInfo.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/NodeState.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Nodes.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Peers.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Reload.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Resources.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Transactions.hs create mode 100644 cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Utils.hs diff --git a/cardano-tracer/bench/cardano-tracer-bench.hs b/cardano-tracer/bench/cardano-tracer-bench.hs index 772c8af4cd3..a1d0b7ee021 100644 --- a/cardano-tracer/bench/cardano-tracer-bench.hs +++ b/cardano-tracer/bench/cardano-tracer-bench.hs @@ -11,6 +11,7 @@ import Cardano.Logging hiding (LocalSocket) import Cardano.Tracer.Configuration import Cardano.Tracer.Handlers.Logs.TraceObjects (traceObjectsHandler) +import Cardano.Tracer.Handlers.RTView.State.TraceObjects (initSavedTraceObjects) import Cardano.Tracer.Types (NodeId (..)) main :: IO () @@ -25,19 +26,21 @@ main = do to100 <- generate 100 to1000 <- generate 1000 + savedTO <- initSavedTraceObjects + removePathForcibly root defaultMain [ bgroup "cardano-tracer" [ -- 10 'TraceObject's per request. - bench "Handle TraceObjects LOG, 10" $ whnfIO $ traceObjectsHandler c1 nId lock to10 - , bench "Handle TraceObjects JSON, 10" $ whnfIO $ traceObjectsHandler c2 nId lock to10 + bench "Handle TraceObjects LOG, 10" $ whnfIO $ traceObjectsHandler c1 nId lock savedTO to10 + , bench "Handle TraceObjects JSON, 10" $ whnfIO $ traceObjectsHandler c2 nId lock savedTO to10 -- 100 'TraceObject's per request. - , bench "Handle TraceObjects LOG, 100" $ whnfIO $ traceObjectsHandler c1 nId lock to100 - , bench "Handle TraceObjects JSON, 100" $ whnfIO $ traceObjectsHandler c2 nId lock to100 + , bench "Handle TraceObjects LOG, 100" $ whnfIO $ traceObjectsHandler c1 nId lock savedTO to100 + , bench "Handle TraceObjects JSON, 100" $ whnfIO $ traceObjectsHandler c2 nId lock savedTO to100 -- 1000 'TraceObject's per request. - , bench "Handle TraceObjects LOG, 1000" $ whnfIO $ traceObjectsHandler c1 nId lock to1000 - , bench "Handle TraceObjects JSON, 1000" $ whnfIO $ traceObjectsHandler c2 nId lock to1000 + , bench "Handle TraceObjects LOG, 1000" $ whnfIO $ traceObjectsHandler c1 nId lock savedTO to1000 + , bench "Handle TraceObjects JSON, 1000" $ whnfIO $ traceObjectsHandler c2 nId lock savedTO to1000 ] ] where @@ -50,6 +53,7 @@ main = do , ekgRequestFreq = Nothing , hasEKG = Nothing , hasPrometheus = Nothing + , hasRTView = Nothing , logging = NE.fromList [LoggingParams root FileMode format] , rotation = Nothing , verbosity = Nothing diff --git a/cardano-tracer/cardano-tracer.cabal b/cardano-tracer/cardano-tracer.cabal index 4483f3fbbf9..e9c89e67cbd 100644 --- a/cardano-tracer/cardano-tracer.cabal +++ b/cardano-tracer/cardano-tracer.cabal @@ -36,6 +36,8 @@ library Cardano.Tracer.Acceptors.Server Cardano.Tracer.Acceptors.Utils + Cardano.Tracer.Handlers.CheckMode + Cardano.Tracer.Handlers.Logs.File Cardano.Tracer.Handlers.Logs.Journal Cardano.Tracer.Handlers.Logs.Rotator @@ -45,6 +47,51 @@ library Cardano.Tracer.Handlers.Metrics.Monitoring Cardano.Tracer.Handlers.Metrics.Prometheus Cardano.Tracer.Handlers.Metrics.Servers + Cardano.Tracer.Handlers.Metrics.Utils + + Cardano.Tracer.Handlers.RTView.Run + Cardano.Tracer.Handlers.RTView.State.Displayed + Cardano.Tracer.Handlers.RTView.State.EraSettings + Cardano.Tracer.Handlers.RTView.State.Errors + Cardano.Tracer.Handlers.RTView.State.Historical + Cardano.Tracer.Handlers.RTView.State.Last + Cardano.Tracer.Handlers.RTView.State.Peers + Cardano.Tracer.Handlers.RTView.State.TraceObjects + Cardano.Tracer.Handlers.RTView.System + Cardano.Tracer.Handlers.RTView.UI.CSS.Bulma + Cardano.Tracer.Handlers.RTView.UI.CSS.Own + Cardano.Tracer.Handlers.RTView.UI.HTML.Node.Column + Cardano.Tracer.Handlers.RTView.UI.HTML.Node.EKG + Cardano.Tracer.Handlers.RTView.UI.HTML.Node.Errors + Cardano.Tracer.Handlers.RTView.UI.HTML.Node.Peers + Cardano.Tracer.Handlers.RTView.UI.HTML.About + Cardano.Tracer.Handlers.RTView.UI.HTML.Body + Cardano.Tracer.Handlers.RTView.UI.HTML.Main + Cardano.Tracer.Handlers.RTView.UI.HTML.NoNodes + Cardano.Tracer.Handlers.RTView.UI.HTML.Notifications + Cardano.Tracer.Handlers.RTView.UI.JS.ChartJS + Cardano.Tracer.Handlers.RTView.UI.JS.Charts + Cardano.Tracer.Handlers.RTView.UI.JS.Utils + Cardano.Tracer.Handlers.RTView.UI.Img.Icons + Cardano.Tracer.Handlers.RTView.UI.Charts + Cardano.Tracer.Handlers.RTView.UI.Theme + Cardano.Tracer.Handlers.RTView.UI.Types + Cardano.Tracer.Handlers.RTView.UI.Utils + Cardano.Tracer.Handlers.RTView.Update.Chain + Cardano.Tracer.Handlers.RTView.Update.EKG + Cardano.Tracer.Handlers.RTView.Update.EraSettings + Cardano.Tracer.Handlers.RTView.Update.Errors + Cardano.Tracer.Handlers.RTView.Update.Historical + Cardano.Tracer.Handlers.RTView.Update.KES + Cardano.Tracer.Handlers.RTView.Update.Leadership + Cardano.Tracer.Handlers.RTView.Update.NodeInfo + Cardano.Tracer.Handlers.RTView.Update.NodeState + Cardano.Tracer.Handlers.RTView.Update.Nodes + Cardano.Tracer.Handlers.RTView.Update.Peers + Cardano.Tracer.Handlers.RTView.Update.Reload + Cardano.Tracer.Handlers.RTView.Update.Resources + Cardano.Tracer.Handlers.RTView.Update.Transactions + Cardano.Tracer.Handlers.RTView.Update.Utils Cardano.Tracer.CLI Cardano.Tracer.Configuration @@ -60,6 +107,8 @@ library , blaze-html , blaze-markup , bytestring + , cardano-git-rev + , cardano-node , cborg , containers , contra-tracer @@ -76,6 +125,7 @@ library , snap-core , snap-server , stm + , string-qq , text , threepenny-gui , time @@ -87,6 +137,11 @@ library if os(linux) build-depends: libsystemd-journal + if os(windows) + build-depends: Win32 + else + build-depends: unix + executable cardano-tracer import: base, project-config diff --git a/cardano-tracer/src/Cardano/Tracer/Acceptors/Client.hs b/cardano-tracer/src/Cardano/Tracer/Acceptors/Client.hs index a5ee740ceec..5c8ab69191d 100644 --- a/cardano-tracer/src/Cardano/Tracer/Acceptors/Client.hs +++ b/cardano-tracer/src/Cardano/Tracer/Acceptors/Client.hs @@ -40,6 +40,7 @@ import Cardano.Tracer.Acceptors.Utils (prepareDataPointRequestor, prepareMetricsStores, removeDisconnectedNode) import qualified Cardano.Tracer.Configuration as TC import Cardano.Tracer.Handlers.Logs.TraceObjects (traceObjectsHandler) +import Cardano.Tracer.Handlers.RTView.Run (SavedTraceObjects) import Cardano.Tracer.Types (AcceptedMetrics, ConnectedNodes, DataPointRequestors) import Cardano.Tracer.Utils (connIdToNodeId) @@ -52,11 +53,12 @@ runAcceptorsClient ) -> ConnectedNodes -> AcceptedMetrics + -> SavedTraceObjects -> DataPointRequestors -> Lock -> IO () runAcceptorsClient config p (ekgConfig, tfConfig, dpfConfig) - connectedNodes acceptedMetrics dpRequestors currentLogLock = + connectedNodes acceptedMetrics savedTO dpRequestors currentLogLock = withIOManager $ \iocp -> doConnectToForwarder (localSnocket iocp) @@ -67,7 +69,7 @@ runAcceptorsClient config p (ekgConfig, tfConfig, dpfConfig) -- there is no mechanism to disable some of them. appInitiator [ (runEKGAcceptorInit ekgConfig connectedNodes acceptedMetrics errorHandler, 1) - , (runTraceObjectsAcceptorInit config tfConfig currentLogLock errorHandler, 2) + , (runTraceObjectsAcceptorInit config tfConfig currentLogLock savedTO errorHandler, 2) , (runDataPointsAcceptorInit dpfConfig connectedNodes dpRequestors errorHandler, 3) ] where @@ -122,13 +124,14 @@ runTraceObjectsAcceptorInit :: TC.TracerConfig -> TF.AcceptorConfiguration TraceObject -> Lock + -> SavedTraceObjects -> (ConnectionId LocalAddress -> IO ()) -> ConnectionId LocalAddress -> RunMiniProtocol 'InitiatorMode LBS.ByteString IO () Void -runTraceObjectsAcceptorInit config tfConfig currentLogLock errorHandler connId = +runTraceObjectsAcceptorInit config tfConfig currentLogLock savedTO errorHandler connId = acceptTraceObjectsInit tfConfig - (traceObjectsHandler config (connIdToNodeId connId) currentLogLock) + (traceObjectsHandler config (connIdToNodeId connId) currentLogLock savedTO) (errorHandler connId) runDataPointsAcceptorInit diff --git a/cardano-tracer/src/Cardano/Tracer/Acceptors/Run.hs b/cardano-tracer/src/Cardano/Tracer/Acceptors/Run.hs index c992f580d7b..58f18eb46cc 100644 --- a/cardano-tracer/src/Cardano/Tracer/Acceptors/Run.hs +++ b/cardano-tracer/src/Cardano/Tracer/Acceptors/Run.hs @@ -23,6 +23,7 @@ import qualified Trace.Forward.Protocol.TraceObject.Type as TOF import Cardano.Tracer.Acceptors.Client (runAcceptorsClient) import Cardano.Tracer.Acceptors.Server (runAcceptorsServer) import Cardano.Tracer.Configuration +import Cardano.Tracer.Handlers.RTView.Run (SavedTraceObjects) import Cardano.Tracer.Types (AcceptedMetrics, ConnectedNodes, DataPointRequestors, ProtocolsBrake) import Cardano.Tracer.Utils (runInLoop) @@ -36,25 +37,26 @@ runAcceptors :: TracerConfig -> ConnectedNodes -> AcceptedMetrics + -> SavedTraceObjects -> DataPointRequestors -> ProtocolsBrake -> Lock -> IO () runAcceptors c@TracerConfig{network, ekgRequestFreq, loRequestNum, verbosity} - connectedNodes acceptedMetrics dpRequestors stopIt currentLogLock = + connectedNodes acceptedMetrics savedTO dpRequestors stopIt currentLogLock = case network of AcceptAt (LocalSocket p) -> -- Run one server that accepts connections from the nodes. runInLoop (runAcceptorsServer c p (acceptorsConfigs p) connectedNodes - acceptedMetrics dpRequestors currentLogLock) + acceptedMetrics savedTO dpRequestors currentLogLock) verbosity p 1 ConnectTo localSocks -> -- Run N clients that initiate connections to the nodes. forConcurrently_ (NE.nub localSocks) $ \(LocalSocket p) -> runInLoop (runAcceptorsClient c p (acceptorsConfigs p) connectedNodes - acceptedMetrics dpRequestors currentLogLock) + acceptedMetrics savedTO dpRequestors currentLogLock) verbosity p 1 where acceptorsConfigs p = diff --git a/cardano-tracer/src/Cardano/Tracer/Acceptors/Server.hs b/cardano-tracer/src/Cardano/Tracer/Acceptors/Server.hs index ad3ef2c5132..56813446cf9 100644 --- a/cardano-tracer/src/Cardano/Tracer/Acceptors/Server.hs +++ b/cardano-tracer/src/Cardano/Tracer/Acceptors/Server.hs @@ -43,6 +43,7 @@ import Cardano.Tracer.Acceptors.Utils (prepareDataPointRequestor, prepareMetricsStores, removeDisconnectedNode) import qualified Cardano.Tracer.Configuration as TC import Cardano.Tracer.Handlers.Logs.TraceObjects (traceObjectsHandler) +import Cardano.Tracer.Handlers.RTView.Run (SavedTraceObjects) import Cardano.Tracer.Types (AcceptedMetrics, ConnectedNodes, DataPointRequestors) import Cardano.Tracer.Utils (connIdToNodeId) @@ -55,11 +56,12 @@ runAcceptorsServer ) -> ConnectedNodes -> AcceptedMetrics + -> SavedTraceObjects -> DataPointRequestors -> Lock -> IO () -runAcceptorsServer config p (ekgConfig, tfConfig, dpfConfig) - connectedNodes acceptedMetrics dpRequestors currentLogLock = withIOManager $ \iocp -> +runAcceptorsServer config p (ekgConfig, tfConfig, dpfConfig) connectedNodes + acceptedMetrics savedTO dpRequestors currentLogLock = withIOManager $ \iocp -> doListenToForwarder (localSnocket iocp) (localAddressFromPath p) @@ -69,7 +71,7 @@ runAcceptorsServer config p (ekgConfig, tfConfig, dpfConfig) -- there is no mechanism to disable some of them. appResponder [ (runEKGAcceptor ekgConfig connectedNodes acceptedMetrics errorHandler, 1) - , (runTraceObjectsAcceptor config tfConfig currentLogLock errorHandler, 2) + , (runTraceObjectsAcceptor config tfConfig currentLogLock savedTO errorHandler, 2) , (runDataPointsAcceptor dpfConfig connectedNodes dpRequestors errorHandler, 3) ] where @@ -129,13 +131,14 @@ runTraceObjectsAcceptor :: TC.TracerConfig -> TF.AcceptorConfiguration TraceObject -> Lock + -> SavedTraceObjects -> (ConnectionId LocalAddress -> IO ()) -> ConnectionId LocalAddress -> RunMiniProtocol 'ResponderMode LBS.ByteString IO Void () -runTraceObjectsAcceptor config tfConfig currentLogLock errorHandler connId = +runTraceObjectsAcceptor config tfConfig currentLogLock savedTO errorHandler connId = acceptTraceObjectsResp tfConfig - (traceObjectsHandler config (connIdToNodeId connId) currentLogLock) + (traceObjectsHandler config (connIdToNodeId connId) currentLogLock savedTO) (errorHandler connId) runDataPointsAcceptor diff --git a/cardano-tracer/src/Cardano/Tracer/CLI.hs b/cardano-tracer/src/Cardano/Tracer/CLI.hs index 9b52703bce0..e0a51791171 100644 --- a/cardano-tracer/src/Cardano/Tracer/CLI.hs +++ b/cardano-tracer/src/Cardano/Tracer/CLI.hs @@ -6,17 +6,24 @@ module Cardano.Tracer.CLI import Options.Applicative -- | CLI parameters required for the tracer. -newtype TracerParams = TracerParams - { tracerConfig :: FilePath +data TracerParams = TracerParams + { tracerConfig :: !FilePath + , checkMode :: !Bool } -- | Parse CLI parameters for the tracer. parseTracerParams :: Parser TracerParams -parseTracerParams = TracerParams <$> - strOption - ( long "config" - <> short 'c' - <> metavar "FILEPATH" - <> help "Configuration file for cardano-tracer" - <> completer (bashCompleter "file") - ) +parseTracerParams = TracerParams + <$> strOption + ( long "config" + <> short 'c' + <> metavar "FILEPATH" + <> help "Configuration file for cardano-tracer" + <> completer (bashCompleter "file") + ) + <*> flag + False + True + ( long "check-mode" + <> help "Run in check mode: shows all accepted stuff" + ) diff --git a/cardano-tracer/src/Cardano/Tracer/Configuration.hs b/cardano-tracer/src/Cardano/Tracer/Configuration.hs index 0b84afc6877..ecbdfbbed32 100644 --- a/cardano-tracer/src/Cardano/Tracer/Configuration.hs +++ b/cardano-tracer/src/Cardano/Tracer/Configuration.hs @@ -87,6 +87,7 @@ data TracerConfig = TracerConfig , ekgRequestFreq :: !(Maybe Pico) -- ^ How often to request for EKG-metrics, in seconds. , hasEKG :: !(Maybe (Endpoint, Endpoint)) -- ^ Endpoint for EKG web-page (list of nodes, monitoring). , hasPrometheus :: !(Maybe Endpoint) -- ^ Endpoint for Promeheus web-page. + , hasRTView :: !(Maybe Endpoint) -- ^ Endpoint for RTView web-page. , logging :: !(NonEmpty LoggingParams) -- ^ Logging parameters. , rotation :: !(Maybe RotationParams) -- ^ Rotation parameters. , verbosity :: !(Maybe Verbosity) -- ^ Verbosity of the tracer itself. @@ -103,7 +104,7 @@ readTracerConfig pathToConfig = Right _ -> return config checkMeaninglessValues :: TracerConfig -> Either String () -checkMeaninglessValues TracerConfig{network, hasEKG, hasPrometheus, logging} = +checkMeaninglessValues TracerConfig{network, hasEKG, hasPrometheus, hasRTView, logging} = if null problems then Right () else Left $ intercalate ", " problems @@ -115,6 +116,7 @@ checkMeaninglessValues TracerConfig{network, hasEKG, hasPrometheus, logging} = , check "empty logRoot(s)" $ notNull . NE.filter invalidFileMode $ logging , (check "no host(s) in hasEKG" . nullEndpoints) =<< hasEKG , (check "no host in hasPrometheus" . nullEndpoint) =<< hasPrometheus + , (check "no host in hasRTView" . nullEndpoint) =<< hasRTView ] check msg cond = if cond then Just msg else Nothing diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/CheckMode.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/CheckMode.hs new file mode 100644 index 00000000000..09f8292141e --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/CheckMode.hs @@ -0,0 +1,42 @@ +{-# LANGUAGE ScopedTypeVariables #-} + +module Cardano.Tracer.Handlers.CheckMode + ( runChecker + ) where + +import Control.Concurrent.STM.TVar +import Control.Monad (forever, forM_, when) +import qualified Data.Map.Strict as M +import Debug.Trace (traceIO) +import System.Time.Extra (sleep) + +import Cardano.Tracer.Handlers.Metrics.Utils +import Cardano.Tracer.Handlers.RTView.State.TraceObjects +import Cardano.Tracer.Types + +-- | When 'cardano-tracer' is launched with '--check-mode' flag, it +-- prints out all 'EKG.Metrics' and 'TraceObject's it receives from +-- the node(s). We use it for debug purposes. +runChecker + :: Bool + -> AcceptedMetrics + -> SavedTraceObjects + -> IO () +runChecker itIsCheckMode acceptedMetrics savedTO = + when itIsCheckMode doRunChecker + where + doRunChecker = forever $ do + allMetrics <- M.toList <$> readTVarIO acceptedMetrics + forM_ allMetrics $ \(nodeId, (ekgStore, _)) -> do + metrics <- getListOfMetrics ekgStore + traceIO "-----------------------" + traceIO $ "All metrics from " <> show nodeId <> ": " <> show metrics + traceIO "-----------------------" + + allTraceObjects <- M.toList <$> readTVarIO savedTO + forM_ allTraceObjects $ \(nodeId, savedForNode) -> do + traceIO "***********************" + traceIO $ "All trace objects from " <> show nodeId <> ": " <> show savedForNode + traceIO "***********************" + + sleep 1.0 diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/Logs/TraceObjects.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/Logs/TraceObjects.hs index 82657b639da..b7a2e3ad6d8 100644 --- a/cardano-tracer/src/Cardano/Tracer/Handlers/Logs/TraceObjects.hs +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/Logs/TraceObjects.hs @@ -6,6 +6,7 @@ module Cardano.Tracer.Handlers.Logs.TraceObjects import Control.Concurrent.Async (forConcurrently_) import Control.Concurrent.Extra (Lock) +import Control.Monad.Extra (whenJust) import qualified Data.List.NonEmpty as NE import Cardano.Logging (TraceObject) @@ -13,21 +14,26 @@ import Cardano.Logging (TraceObject) import Cardano.Tracer.Configuration import Cardano.Tracer.Handlers.Logs.File (writeTraceObjectsToFile) import Cardano.Tracer.Handlers.Logs.Journal (writeTraceObjectsToJournal) +import Cardano.Tracer.Handlers.RTView.Run (SavedTraceObjects, saveTraceObjects) import Cardano.Tracer.Types (NodeId) import Cardano.Tracer.Utils (showProblemIfAny) -- | This handler is called periodically by 'TraceObjectForward' protocol -- from 'trace-forward' library. traceObjectsHandler - :: TracerConfig -- ^ Tracer configuration. - -> NodeId -- ^ An id of the node 'TraceObject's were received from. - -> Lock -- ^ The lock we use for single-threaded access to the current log. - -> [TraceObject] -- ^ The list of received 'TraceObject's (may be empty). + :: TracerConfig -- ^ Tracer configuration. + -> NodeId -- ^ An id of the node 'TraceObject's were received from. + -> Lock -- ^ The lock we use for single-threaded access to the current log. + -> SavedTraceObjects -- ^ The buffer for accepted 'TraceObject's, used by RTView service. + -> [TraceObject] -- ^ The list of received 'TraceObject's (may be empty). -> IO () -traceObjectsHandler _ _ _ [] = return () -traceObjectsHandler TracerConfig{logging, verbosity} nodeId currentLogLock traceObjects = +traceObjectsHandler _ _ _ _ [] = return () +traceObjectsHandler TracerConfig{logging, verbosity, hasRTView} + nodeId currentLogLock savedTO traceObjects = do forConcurrently_ (NE.nub logging) $ \LoggingParams{logMode, logRoot, logFormat} -> showProblemIfAny verbosity $ case logMode of FileMode -> writeTraceObjectsToFile nodeId currentLogLock logRoot logFormat traceObjects JournalMode -> writeTraceObjectsToJournal nodeId traceObjects + whenJust hasRTView . const $ + saveTraceObjects savedTO nodeId traceObjects diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Monitoring.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Monitoring.hs index 141f3c79761..54a942aa709 100644 --- a/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Monitoring.hs +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Monitoring.hs @@ -20,6 +20,7 @@ import Data.Text.Encoding (encodeUtf8) import qualified Graphics.UI.Threepenny as UI import Graphics.UI.Threepenny.Core (UI, Element, liftIO, set, (#), (#+)) import System.Remote.Monitoring (forkServerWith, serverThreadId) +import System.Time.Extra (sleep) import Cardano.Tracer.Configuration (Endpoint (..)) import Cardano.Tracer.Types (AcceptedMetrics, ConnectedNodes, NodeId (..)) @@ -39,7 +40,9 @@ runMonitoringServer -> ConnectedNodes -> AcceptedMetrics -> IO () -runMonitoringServer (Endpoint listHost listPort, monitorEP) connectedNodes acceptedMetrics = +runMonitoringServer (Endpoint listHost listPort, monitorEP) connectedNodes acceptedMetrics = do + -- Pause to prevent collision between "Listening"-notifications from servers. + sleep 0.2 UI.startGUI config $ \window -> do void $ return window # set UI.title "EKG Monitoring Nodes" void $ mkPageBody window connectedNodes monitorEP acceptedMetrics diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Prometheus.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Prometheus.hs index e03dec4734c..102d62b0902 100644 --- a/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Prometheus.hs +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Prometheus.hs @@ -55,6 +55,8 @@ runPrometheusServer -> AcceptedMetrics -> IO () runPrometheusServer (Endpoint host port) connectedNodes acceptedMetrics = forever $ do + -- Pause to prevent collision between "Listening"-notifications from servers. + sleep 0.1 -- If everything is okay, the function 'simpleHttpServe' never returns. -- But if there is some problem, it never throws an exception, but just stops. -- So if it stopped - it will be re-started. diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Utils.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Utils.hs new file mode 100644 index 00000000000..5f786d2c0f7 --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/Metrics/Utils.hs @@ -0,0 +1,26 @@ +module Cardano.Tracer.Handlers.Metrics.Utils + ( MetricName + , MetricValue + , MetricsList + , getListOfMetrics + ) where + +import qualified Data.HashMap.Strict as HM +import Data.Maybe (mapMaybe) +import Data.Text (Text) +import qualified Data.Text as T +import System.Metrics (Store, Value (..), sampleAll) + +type MetricName = Text +type MetricValue = Text +type MetricsList = [(MetricName, MetricValue)] + +getListOfMetrics :: Store -> IO MetricsList +getListOfMetrics = fmap (mapMaybe metricsWeNeed . HM.toList) . sampleAll + where + metricsWeNeed (mName, mValue) = + case mValue of + Counter c -> Just (mName, T.pack $ show c) + Gauge g -> Just (mName, T.pack $ show g) + Label l -> Just (mName, l) + _ -> Nothing -- 'ekg-forward' doesn't support 'Distribution' yet. diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Run.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Run.hs new file mode 100644 index 00000000000..3c054a50f1c --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Run.hs @@ -0,0 +1,103 @@ +{-# LANGUAGE NamedFieldPuns #-} + +module Cardano.Tracer.Handlers.RTView.Run + ( runRTView + , module Cardano.Tracer.Handlers.RTView.State.TraceObjects + ) where + +import Control.Concurrent.Async.Extra (sequenceConcurrently) +import Control.Monad (void) +import Control.Monad.Extra (whenJust) +import qualified Data.Text as T +import Data.Text.Encoding (encodeUtf8) +import qualified Graphics.UI.Threepenny as UI +import System.Time.Extra (sleep) + +import Cardano.Tracer.Configuration +import Cardano.Tracer.Handlers.RTView.State.EraSettings +import Cardano.Tracer.Handlers.RTView.State.Displayed +import Cardano.Tracer.Handlers.RTView.State.Errors +import Cardano.Tracer.Handlers.RTView.State.Historical +import Cardano.Tracer.Handlers.RTView.State.Last +import Cardano.Tracer.Handlers.RTView.State.TraceObjects +import Cardano.Tracer.Handlers.RTView.UI.HTML.Main +import Cardano.Tracer.Handlers.RTView.Update.EraSettings +import Cardano.Tracer.Handlers.RTView.Update.Errors +import Cardano.Tracer.Handlers.RTView.Update.Historical +import Cardano.Tracer.Types + +-- | RTView is a part of 'cardano-tracer' that provides an ability +-- to monitor Cardano nodes in a real-time. The core idea is simple: +-- RTView periodically receives some informations from the connected +-- node(s) and displays that information on a web-page. +-- +-- The web-page is built using 'threepenny-gui' library. Please note +-- Gitub-version of this library is used, not Hackage-version! +-- +-- TODO ... + +runRTView + :: TracerConfig + -> ConnectedNodes + -> AcceptedMetrics + -> SavedTraceObjects + -> DataPointRequestors + -> IO () +runRTView TracerConfig{logging, network, hasRTView} + connectedNodes acceptedMetrics savedTO dpRequestors = + whenJust hasRTView $ \(Endpoint host port) -> do + -- Pause to prevent collision between "Listening"-notifications from servers. + sleep 0.3 + -- Initialize displayed stuff outside of main page renderer, + -- to be able to update corresponding elements after page reloading. + displayedElements <- initDisplayedElements + reloadFlag <- initPageReloadFlag + -- We have to collect different information from the node and save it + -- independently from RTView web-server. As a result, we'll be able to + -- show charts with historical data (where X axis is the time) for the + -- period when RTView web-page wasn't opened. + resourcesHistory <- initResourcesHistory + lastResources <- initLastResources + chainHistory <- initBlockchainHistory + txHistory <- initTransactionsHistory + eraSettings <- initErasSettings + errors <- initErrors + + void . sequenceConcurrently $ + [ UI.startGUI (config host port) $ + mkMainPage + connectedNodes + displayedElements + acceptedMetrics + savedTO + eraSettings + dpRequestors + reloadFlag + logging + network + resourcesHistory + chainHistory + txHistory + errors + , runHistoricalUpdater + savedTO + acceptedMetrics + resourcesHistory + lastResources + chainHistory + txHistory + , runEraSettingsUpdater + connectedNodes + eraSettings + savedTO + , runErrorsUpdater + connectedNodes + errors + savedTO + ] + where + config h p = UI.defaultConfig + { UI.jsPort = Just . fromIntegral $ p + , UI.jsAddr = Just . encodeUtf8 . T.pack $ h + , UI.jsLog = const $ return () -- To hide 'threepenny-gui' internal messages. + } diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Displayed.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Displayed.hs new file mode 100644 index 00000000000..d3aaee799d2 --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Displayed.hs @@ -0,0 +1,125 @@ +module Cardano.Tracer.Handlers.RTView.State.Displayed + ( DisplayedElements + , PageReloadedFlag + , getDisplayedValue + , getDisplayedValuePure + , initDisplayedElements + , initPageReloadFlag + , pageWasReload + , pageWasNotReload + , saveDisplayedValue + , updateDisplayedElements + ) where + +import Control.Concurrent.STM (atomically) +import Control.Concurrent.STM.TVar (TVar, modifyTVar', newTVarIO, readTVarIO) +import Data.List ((\\)) +import Data.Map.Strict (Map) +import qualified Data.Map.Strict as M +import Data.Set (Set) +import qualified Data.Set as S +import Data.Text (Text) + +import Cardano.Tracer.Types (NodeId) + +type ElementId = Text +type ElementValue = Text + +-- | We store all currently displayed values for all elements in each node panel. +-- +-- There are 2 reasons for it: +-- +-- 1. If received 'TraceObject' contains the data with the same value again, we +-- won't update corresponding element, to keep web-traffic. +-- 2. If the user reloaded the page, all previously displayed elements will be +-- updated in the same state again. +-- +type DisplayedForNode = Map ElementId ElementValue +type DisplayedElements = TVar (Map NodeId DisplayedForNode) + +initDisplayedElements :: IO DisplayedElements +initDisplayedElements = newTVarIO M.empty + +getDisplayedValue + :: DisplayedElements + -> NodeId + -> ElementId + -> IO (Maybe ElementValue) +getDisplayedValue displayedElements nodeId elId = + maybe Nothing (M.lookup elId) . M.lookup nodeId <$> readTVarIO displayedElements + +getDisplayedValuePure + :: Map NodeId DisplayedForNode + -> NodeId + -> ElementId + -> Maybe ElementValue +getDisplayedValuePure displayed nodeId elId = + maybe Nothing (M.lookup elId) . M.lookup nodeId $ displayed + +saveDisplayedValue + :: DisplayedElements + -> NodeId + -> ElementId + -> ElementValue + -> IO () +saveDisplayedValue displayedElements nodeId elId elValue = atomically $ + modifyTVar' displayedElements $ \currentDisplayedEls -> + case M.lookup nodeId currentDisplayedEls of + Nothing -> M.insert nodeId (M.singleton elId elValue) currentDisplayedEls + Just elsForNode -> + let elsForNode' = + case M.lookup elId elsForNode of + Nothing -> M.insert elId elValue elsForNode + Just _ -> M.adjust (const elValue) elId elsForNode + in M.adjust (const elsForNode') nodeId currentDisplayedEls + +-- | Makes 'displayedElements' up-to-date with 'connected'. There are 3 cases: +-- +-- 1. 'displayedElements' contains all node ids from 'connected' +-- - just keep it. +-- 2. 'displayedElements' contains some node ids that is not presented in 'connected' +-- - remove them, these ids correspond to disconnected nodes. +-- 3. 'displayedElements' doesn't contain some node ids that presented in 'connected' +-- - add them, these ids correspond to newly connected nodes. +-- +-- 2-nd and 3-d cases can coexist. +-- +updateDisplayedElements + :: DisplayedElements + -> Set NodeId + -> IO () +updateDisplayedElements displayedElements connected = atomically $ + modifyTVar' displayedElements $ \currentDisplayedEls -> + let connectedIds = S.toList connected + displayedIds = M.keys currentDisplayedEls + in if displayedIds == connectedIds + then currentDisplayedEls -- 1-st case. + else + let disconnectedIds = displayedIds \\ connectedIds -- In 'displayedIds' but not in 'connectedIds'. + newlyConnectedIds = connectedIds \\ displayedIds -- In 'connectedIds' but not in 'displayedIds'. + withoutDisconnected = deleteDisconnected disconnectedIds currentDisplayedEls + in addNewlyConnected newlyConnectedIds withoutDisconnected + where + deleteDisconnected = go + where + go [] els = els + go (anId:ids) els = go ids $ M.delete anId els + + addNewlyConnected = go + where + go [] els = els + go (anId:ids) els = go ids $ M.insert anId M.empty els + +-- | If the user reloaded the web-page, after DOM re-rendering, we have to restore +-- displayed state of all elements that they have _before_ page's reload. +type PageReloadedFlag = TVar Bool + +initPageReloadFlag :: IO PageReloadedFlag +initPageReloadFlag = newTVarIO True + +pageWasReload, pageWasNotReload :: PageReloadedFlag -> IO () +pageWasReload = setFlag True +pageWasNotReload = setFlag False + +setFlag :: Bool -> PageReloadedFlag -> IO () +setFlag state flag = atomically . modifyTVar' flag . const $ state diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/EraSettings.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/EraSettings.hs new file mode 100644 index 00000000000..173711974e8 --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/EraSettings.hs @@ -0,0 +1,44 @@ +{-# LANGUAGE OverloadedStrings #-} + +module Cardano.Tracer.Handlers.RTView.State.EraSettings + ( EraSettings (..) + , ErasSettings + , addEraSettings + , initErasSettings + ) where + +import Control.Concurrent.STM (atomically) +import Control.Concurrent.STM.TVar (TVar, modifyTVar', newTVarIO) +import Data.Map.Strict (Map) +import qualified Data.Map.Strict as M +import Data.Text (Text) + +import Cardano.Tracer.Types (NodeId) + +data EraSettings = EraSettings + { esEra :: !Text + , esSlotLengthInS :: !Int + , esEpochLength :: !Int + , esKESPeriodLength :: !Int + } deriving (Eq, Show) + +type ErasSettings = TVar (Map NodeId EraSettings) + +initErasSettings :: IO ErasSettings +initErasSettings = newTVarIO M.empty + +addEraSettings + :: ErasSettings + -> NodeId + -> EraSettings + -> IO () +addEraSettings nodesSettings nodeId settingsForIt = atomically $ + modifyTVar' nodesSettings $ \currentSettings -> + case M.lookup nodeId currentSettings of + Nothing -> + M.insert nodeId settingsForIt currentSettings + Just savedSettings -> + -- The settings for the same era shouldn't be changed. + if esEra savedSettings == esEra settingsForIt + then currentSettings + else M.adjust (const settingsForIt) nodeId currentSettings diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Errors.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Errors.hs new file mode 100644 index 00000000000..bfdca0ed55d --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Errors.hs @@ -0,0 +1,132 @@ +{-# LANGUAGE LambdaCase #-} + +module Cardano.Tracer.Handlers.RTView.State.Errors + ( ErrorIx + , ErrorInfo + , Errors + , addError + , getError + , getErrors + , getErrorsFilteredBySeverity + , getErrorsFilteredByText + , getErrorsSortedBy + , timeAsc + , timeDesc + , severityAsc + , severityDesc + , initErrors + , deleteAllErrors + ) where + +import Control.Concurrent.STM (atomically) +import Control.Concurrent.STM.TVar +import Data.List (find, sortBy) +import Data.Map.Strict (Map) +import qualified Data.Map.Strict as M +import Data.Text (Text, isInfixOf) +import qualified Data.Text as T + +import Cardano.Tracer.Handlers.RTView.State.TraceObjects +import Cardano.Tracer.Types (NodeId) + +import Cardano.Logging (SeverityS) + +type ErrorIx = Int +type ErrorInfo = (ErrorIx, TraceObjectInfo) +type Errors = TVar (Map NodeId [ErrorInfo]) + +initErrors :: IO Errors +initErrors = newTVarIO M.empty + +addError + :: Errors + -> NodeId + -> TraceObjectInfo + -> IO () +addError errors nodeId trObInfo = atomically $ + modifyTVar' errors $ \currentErrors -> + case M.lookup nodeId currentErrors of + Nothing -> do + let errorIx = 0 + M.insert nodeId [(errorIx, trObInfo)] currentErrors + Just errorsFromNode -> do + -- All errors here should be unique, so check it first. + case find (\(_, trObInfo') -> trObInfo == trObInfo') errorsFromNode of + Nothing -> do + -- No such error, add it. + let errorIx = length errorsFromNode + M.adjust (const $ errorsFromNode ++ [(errorIx, trObInfo)]) nodeId currentErrors + Just _ -> currentErrors + +deleteAllErrors + :: Errors + -> NodeId + -> IO () +deleteAllErrors errors nodeId = atomically $ + modifyTVar' errors $ \currentErrors -> + case M.lookup nodeId currentErrors of + Nothing -> currentErrors + Just _ -> M.adjust (const []) nodeId currentErrors + +getError + :: ErrorIx + -> Errors + -> NodeId + -> IO (Maybe ErrorInfo) +getError errorIx errors nodeId = + getErrors errors nodeId >>= \case + [] -> return Nothing + allErrors -> return $ find (\(ix, _) -> ix == errorIx) allErrors + +getErrors + :: Errors + -> NodeId + -> IO [ErrorInfo] +getErrors errors nodeId = + getErrorsHandled errors nodeId id + +getErrorsSortedBy + :: (ErrorInfo -> ErrorInfo -> Ordering) + -> Errors + -> NodeId + -> IO [ErrorInfo] +getErrorsSortedBy ordering errors nodeId = + getErrorsHandled errors nodeId $ sortBy ordering + +timeAsc + , timeDesc + , severityAsc + , severityDesc :: ErrorInfo -> ErrorInfo -> Ordering +timeAsc (_, (_, _, ts1)) (_, (_, _, ts2)) = ts1 `compare` ts2 +timeDesc (_, (_, _, ts1)) (_, (_, _, ts2)) = ts2 `compare` ts1 +severityAsc (_, (_, s1, _)) (_, (_, s2, _)) = s1 `compare` s2 +severityDesc (_, (_, s1, _)) (_, (_, s2, _)) = s2 `compare` s1 + +getErrorsFilteredBySeverity + :: SeverityS + -> Errors + -> NodeId + -> IO [ErrorInfo] +getErrorsFilteredBySeverity severity errors nodeId = + getErrorsHandled errors nodeId $ filter (\(_, (_, sev, _)) -> sev == severity) + +getErrorsFilteredByText + :: Text + -> Errors + -> NodeId + -> IO [ErrorInfo] +getErrorsFilteredByText textToSearch errors nodeId = + if T.null textToSearch + then return [] + else getErrorsHandled errors nodeId $ filter (\(_, (msg, _, _)) -> textToSearch `isInfixOf` msg) + +getErrorsHandled + :: Errors + -> NodeId + -> ([ErrorInfo] -> [ErrorInfo]) + -> IO [ErrorInfo] +getErrorsHandled errors nodeId handler = do + errors' <- readTVarIO errors + case M.lookup nodeId errors' of + Nothing -> return [] + Just errorsFromNode -> return $ handler errorsFromNode diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Historical.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Historical.hs new file mode 100644 index 00000000000..6d6bdf7123b --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Historical.hs @@ -0,0 +1,204 @@ +{-# LANGUAGE BangPatterns #-} + +module Cardano.Tracer.Handlers.RTView.State.Historical + ( BlockchainHistory (..) + , DataName (..) + , History + , HistoricalPoint + , POSIXTime + , ResourcesHistory (..) + , TransactionsHistory (..) + , ValueH (..) + , addHistoricalData + , getHistoricalData + , initBlockchainHistory + , initResourcesHistory + , initTransactionsHistory + , readValueI + , readValueD + ) where + +import Control.Concurrent.STM (atomically) +import Control.Concurrent.STM.TVar (TVar, modifyTVar', newTVarIO, readTVarIO) +import Data.Map.Strict (Map) +import qualified Data.Map.Strict as M +import Data.Set (Set) +import qualified Data.Set as S +import Data.Text (Text) +import Data.Text.Read +import Data.Time.Clock (UTCTime) +import Data.Word (Word64) + +import Cardano.Tracer.Handlers.RTView.Update.Utils +import Cardano.Tracer.Types (NodeId) + +-- | A lot of information received from the node is useful as historical data. +-- It means that such an information should be displayed on time charts, +-- where X axis is a time in UTC. An example: resource metrics, chain information, +-- tx information, etc. +type POSIXTime = Word64 + +data ValueH + = ValueD Double + | ValueI Int + deriving (Eq, Ord) + +instance Show ValueH where + show (ValueD d) = show d + show (ValueI i) = show i + +instance Num ValueH where + (+) (ValueI i1) (ValueI i2) = ValueI (i1 + i2) + (+) (ValueD d1) (ValueD d2) = ValueD (d1 + d2) + (+) (ValueI i1) (ValueD d1) = ValueD (fromIntegral i1 + d1) + (+) (ValueD d1) (ValueI i1) = ValueD (fromIntegral i1 + d1) + + (-) (ValueI i1) (ValueI i2) = ValueI (i1 - i2) + (-) (ValueD d1) (ValueD d2) = ValueD (d1 - d2) + (-) (ValueI i1) (ValueD d1) = ValueD (fromIntegral i1 - d1) + (-) (ValueD d1) (ValueI i1) = ValueD (fromIntegral i1 - d1) + + (*) (ValueI i1) (ValueI i2) = ValueI (i1 * i2) + (*) (ValueD d1) (ValueD d2) = ValueD (d1 * d2) + (*) (ValueI i1) (ValueD d1) = ValueD (fromIntegral i1 * d1) + (*) (ValueD d1) (ValueI i1) = ValueD (fromIntegral i1 * d1) + + abs (ValueI i) = ValueI (abs i) + abs (ValueD d) = ValueD (abs d) + + signum (ValueI i) = ValueI (signum i) + signum (ValueD d) = ValueD (signum d) + + fromInteger i = ValueI (fromInteger i) + +type HistoricalPoint = (POSIXTime, ValueH) + +type HistoricalPoints = Set HistoricalPoint + +-- | Historical points for particular data. +data DataName + = CPUData + | MemoryData + | GCMajorNumData + | GCMinorNumData + | GCLiveMemoryData + | CPUTimeGCData + | CPUTimeAppData + | ThreadsNumData + -- Chain + | ChainDensityData + | SlotNumData + | BlockNumData + | SlotInEpochData + | EpochData + | NodeCannotForgeData + | ForgedSlotLastData + | NodeIsLeaderData + | NodeIsNotLeaderData + | ForgedInvalidSlotLastData + | AdoptedSlotLastData + | NotAdoptedSlotLastData + | AboutToLeadSlotLastData + | CouldNotForgeSlotLastData + -- TX + | TxsProcessedNumData + | MempoolBytesData + | TxsInMempoolData + deriving (Eq, Ord) + +type HistoricalData = Map DataName HistoricalPoints +type History = TVar (Map NodeId HistoricalData) + +newtype BlockchainHistory = ChainHistory History +newtype ResourcesHistory = ResHistory History +newtype TransactionsHistory = TXHistory History + +initBlockchainHistory :: IO BlockchainHistory +initBlockchainHistory = ChainHistory <$> newTVarIO M.empty + +initResourcesHistory :: IO ResourcesHistory +initResourcesHistory = ResHistory <$> newTVarIO M.empty + +initTransactionsHistory :: IO TransactionsHistory +initTransactionsHistory = TXHistory <$> newTVarIO M.empty + +addHistoricalData + :: History + -> NodeId + -> UTCTime + -> DataName + -> ValueH + -> IO () +addHistoricalData history nodeId now dataName valueH = atomically $ + modifyTVar' history $ \currentHistory -> + case M.lookup nodeId currentHistory of + Nothing -> + -- There is no historical data for this node yet. + let firstPoint = S.singleton (utc2s now, valueH) + newDataForNode = M.singleton dataName firstPoint + in M.insert nodeId newDataForNode currentHistory + Just dataForNode -> + let newDataForNode = + case M.lookup dataName dataForNode of + Nothing -> + -- There is no historical points for this dataName yet. + let firstPoint = S.singleton (utc2s now, valueH) + in M.insert dataName firstPoint dataForNode + Just points -> + let pointsWeKeep = S.fromList . deleteOutdated . S.toAscList $ points + newPoints = S.insert (utc2s now, valueH) pointsWeKeep + in M.adjust (const newPoints) dataName dataForNode + in M.adjust (const newDataForNode) nodeId currentHistory + where + -- All points that older than 'minAge' should be deleted. + deleteOutdated = go + where + go [] = [] + go (point@(tsInSec, _):otherPoints) = + if tsInSec < minAge + then + -- This point is too old, do not keep it anymore. + go otherPoints + else + -- This point should be kept. + -- Since the points were converted to asc list, all the next points + -- are definitely newer (have bigger 'tsInSec'), so there is no need + -- to check them. + point : otherPoints + + !minAge = utc2s now - pointsAgeInSec + pointsAgeInSec = 12 * 60 * 60 + +getHistoricalData + :: History + -> NodeId + -> DataName + -> IO [(POSIXTime, ValueH)] +getHistoricalData history nodeId dataName = do + history' <- readTVarIO history + case M.lookup nodeId history' of + Nothing -> return [] + Just dataForNode -> + case M.lookup dataName dataForNode of + Nothing -> return [] + Just points -> return $ S.toAscList points + +readValueI + :: Monad m + => Text + -> (ValueH -> m ()) + -> m () +readValueI t f = + case decimal t of + Left _ -> return () + Right (i, _) -> f (ValueI i) + +readValueD + :: Monad m + => Text + -> (ValueH -> m ()) + -> m () +readValueD t f = + case double t of + Left _ -> return () + Right (d, _) -> f (ValueD d) diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Last.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Last.hs new file mode 100644 index 00000000000..14bf3c12ce5 --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Last.hs @@ -0,0 +1,53 @@ +module Cardano.Tracer.Handlers.RTView.State.Last + ( LastResourcesForNode (..) + , LastResources + , addNullResources + , initLastResources + , updateLastResources + ) where + +import Control.Concurrent.STM (atomically) +import Control.Concurrent.STM.TVar (TVar, modifyTVar', newTVarIO) +import Data.Map.Strict (Map) +import qualified Data.Map.Strict as M +import Data.Word (Word64) + +import Cardano.Tracer.Types (NodeId) + +-- | We have to store last received metric to be able to calculate +-- the value based on next received metric. For example, calculate +-- "CPU usage percent" based on received CPU ticks. +-- That value will be stored in corresponding 'HistoricalData' for +-- rendering on the corresponding chart. + +data LastResourcesForNode = LastResourcesForNode + { cpuLastTicks :: !Int + , cpuLastNS :: !Word64 + } + +type LastResources = TVar (Map NodeId LastResourcesForNode) + +initLastResources :: IO LastResources +initLastResources = newTVarIO M.empty + +-- | For the first update only. +addNullResources + :: LastResources + -> NodeId + -> IO () +addNullResources lastResources nodeId = atomically $ + modifyTVar' lastResources $ M.insert nodeId nulls + where + nulls = + LastResourcesForNode + { cpuLastTicks = 0 + , cpuLastNS = 0 + } + +updateLastResources + :: LastResources + -> NodeId + -> (LastResourcesForNode -> LastResourcesForNode) + -> IO () +updateLastResources lastResources nodeId updateIt = atomically $ + modifyTVar' lastResources $ M.adjust updateIt nodeId diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Peers.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Peers.hs new file mode 100644 index 00000000000..9687814787f --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/Peers.hs @@ -0,0 +1,73 @@ +module Cardano.Tracer.Handlers.RTView.State.Peers + ( PeerAddress + , PeersForNode + , Peers + , addPeer + , doesPeerExist + , getPeersAddresses + , initPeers + , removePeer + ) where + +import Control.Concurrent.STM (atomically) +import Control.Concurrent.STM.TVar +import Data.Map.Strict (Map) +import qualified Data.Map.Strict as M +import Data.Set (Set) +import qualified Data.Set as S +import Data.Text (Text) + +import Cardano.Tracer.Types (NodeId) + +type PeerAddress = Text +type PeersForNode = Set PeerAddress +type Peers = TVar (Map NodeId PeersForNode) + +initPeers :: IO Peers +initPeers = newTVarIO M.empty + +addPeer + :: Peers + -> NodeId + -> PeerAddress + -> IO () +addPeer peers nodeId peerAddr = atomically $ + modifyTVar' peers $ \currentPeers -> + case M.lookup nodeId currentPeers of + Nothing -> + M.insert nodeId (S.singleton peerAddr) currentPeers + Just peersForNode -> + M.adjust (const $ S.insert peerAddr peersForNode) nodeId currentPeers + +removePeer + :: Peers + -> NodeId + -> PeerAddress + -> IO () +removePeer peers nodeId peerAddr = atomically $ + modifyTVar' peers $ \currentPeers -> + case M.lookup nodeId currentPeers of + Nothing -> currentPeers + Just peersForNode -> + M.adjust (const $ S.delete peerAddr peersForNode) nodeId currentPeers + +doesPeerExist + :: Peers + -> NodeId + -> PeerAddress + -> IO Bool +doesPeerExist peers nodeId peerAddr = do + peers' <- readTVarIO peers + case M.lookup nodeId peers' of + Nothing -> return False + Just peersForNode -> return $ S.member peerAddr peersForNode + +getPeersAddresses + :: Peers + -> NodeId + -> IO (Set PeerAddress) +getPeersAddresses peers nodeId = do + peers' <- readTVarIO peers + case M.lookup nodeId peers' of + Nothing -> return S.empty + Just peersForNode -> return peersForNode diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/TraceObjects.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/TraceObjects.hs new file mode 100644 index 00000000000..eaf352defed --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/State/TraceObjects.hs @@ -0,0 +1,62 @@ +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE OverloadedStrings #-} + +module Cardano.Tracer.Handlers.RTView.State.TraceObjects + ( Namespace + , SavedTraceObjects + , TraceObjectInfo + , initSavedTraceObjects + , saveTraceObjects + ) where + +import Control.Concurrent.STM (atomically) +import Control.Concurrent.STM.TVar (TVar, modifyTVar', newTVarIO) +import Control.Monad (unless) +import Data.Map.Strict (Map) +import qualified Data.Map.Strict as M +import Data.Maybe (mapMaybe) +import Data.Text (Text, intercalate) +import Data.Time.Clock (UTCTime) + +import Cardano.Logging (TraceObject (..), SeverityS) + +import Cardano.Tracer.Types (NodeId) + +type Namespace = Text +type TraceObjectInfo = (Text, SeverityS, UTCTime) + +-- | We have to store 'TraceObject's received from the node, +-- to be able to update corresponding elements (on the web page) +-- using the values extracted from these 'TraceObject's. +type SavedForNode = Map Namespace TraceObjectInfo +type SavedTraceObjects = TVar (Map NodeId SavedForNode) + +initSavedTraceObjects :: IO SavedTraceObjects +initSavedTraceObjects = newTVarIO M.empty + +saveTraceObjects :: SavedTraceObjects -> NodeId -> [TraceObject] -> IO () +saveTraceObjects savedTraceObjects nodeId traceObjects = + unless (null itemsToSave) $ atomically $ modifyTVar' savedTraceObjects $ \savedTO -> + case M.lookup nodeId savedTO of + Nothing -> + M.insert nodeId (M.fromList itemsToSave) savedTO + Just savedTOForThisNode -> + M.adjust (const $ savedTOForThisNode `updateSavedBy` itemsToSave) nodeId savedTO + where + itemsToSave = mapMaybe getTOValue traceObjects + + getTOValue TraceObject{toNamespace, toHuman, toMachine, toSeverity, toTimestamp} = + case (toNamespace, toHuman, toMachine) of + ([], _, _) -> Nothing + (ns, Just msg, Nothing) -> Just (mkName ns, (msg, toSeverity, toTimestamp)) + (ns, Nothing, Just msg) -> Just (mkName ns, (msg, toSeverity, toTimestamp)) + (ns, Just msg, Just _) -> Just (mkName ns, (msg, toSeverity, toTimestamp)) + _ -> Nothing + + mkName = intercalate "." + + -- Update saved 'TraceObject's by new ones: existing value will be replaced. + updateSavedBy = go + where + go saved [] = saved + go saved ((ns, toI):others) = M.insert ns toI saved `go` others diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/System.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/System.hs new file mode 100644 index 00000000000..7fcb471923e --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/System.hs @@ -0,0 +1,39 @@ +{-# LANGUAGE CPP #-} + +module Cardano.Tracer.Handlers.RTView.System + ( getPathToChartsConfig + , getPathToThemeConfig + , getProcessId + ) where + +import Data.Word (Word32) +import Graphics.UI.Threepenny.Core +import System.Directory +import System.FilePath (()) + +#if defined(mingw32_HOST_OS) +import System.Win32.Process (getCurrentProcessId) +#else +import System.Posix.Process (getProcessID) +import System.Posix.Types (CPid (..)) +#endif + +getProcessId :: UI Word32 +getProcessId = +#if defined(mingw32_HOST_OS) + liftIO getCurrentProcessId +#else + do CPid pid <- liftIO getProcessID + return $ fromIntegral pid +#endif + +getPathToChartsConfig, getPathToThemeConfig :: IO FilePath +getPathToChartsConfig = getPathToConfig "charts" +getPathToThemeConfig = getPathToConfig "theme" + +getPathToConfig :: FilePath -> IO FilePath +getPathToConfig configName = do + configDir <- getXdgDirectory XdgConfig "" + let pathToRTViewConfigDir = configDir "cardano-rt-view" + createDirectoryIfMissing True pathToRTViewConfigDir + return $ pathToRTViewConfigDir configName diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/CSS/Bulma.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/CSS/Bulma.hs new file mode 100644 index 00000000000..c2ee553f2c7 --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/CSS/Bulma.hs @@ -0,0 +1,40 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Cardano.Tracer.Handlers.RTView.UI.CSS.Bulma + ( bulmaCSS + , bulmaTooltipCSS + , bulmaPageloaderCSS + , bulmaSwitchCSS + , bulmaDividerCSS + ) where + +import Data.String.QQ + +-- | To avoid run-time dependency from the static content, embed Bulma in the page's header. +bulmaCSS :: String +bulmaCSS = [s| +/*! bulma.io v0.9.3 | MIT License | github.com/jgthms/bulma */.button,.file-cta,.file-name,.input,.pagination-ellipsis,.pagination-link,.pagination-next,.pagination-previous,.select select,.textarea{-moz-appearance:none;-webkit-appearance:none;align-items:center;border:1px solid transparent;border-radius:4px;box-shadow:none;display:inline-flex;font-size:1rem;height:2.5em;justify-content:flex-start;line-height:1.5;padding-bottom:calc(.5em - 1px);padding-left:calc(.75em - 1px);padding-right:calc(.75em - 1px);padding-top:calc(.5em - 1px);position:relative;vertical-align:top}.button:active,.button:focus,.file-cta:active,.file-cta:focus,.file-name:active,.file-name:focus,.input:active,.input:focus,.is-active.button,.is-active.file-cta,.is-active.file-name,.is-active.input,.is-active.pagination-ellipsis,.is-active.pagination-link,.is-active.pagination-next,.is-active.pagination-previous,.is-active.textarea,.is-focused.button,.is-focused.file-cta,.is-focused.file-name,.is-focused.input,.is-focused.pagination-ellipsis,.is-focused.pagination-link,.is-focused.pagination-next,.is-focused.pagination-previous,.is-focused.textarea,.pagination-ellipsis:active,.pagination-ellipsis:focus,.pagination-link:active,.pagination-link:focus,.pagination-next:active,.pagination-next:focus,.pagination-previous:active,.pagination-previous:focus,.select select.is-active,.select select.is-focused,.select select:active,.select select:focus,.textarea:active,.textarea:focus{outline:0}.button[disabled],.file-cta[disabled],.file-name[disabled],.input[disabled],.pagination-ellipsis[disabled],.pagination-link[disabled],.pagination-next[disabled],.pagination-previous[disabled],.select fieldset[disabled] select,.select select[disabled],.textarea[disabled],fieldset[disabled] .button,fieldset[disabled] .file-cta,fieldset[disabled] .file-name,fieldset[disabled] .input,fieldset[disabled] .pagination-ellipsis,fieldset[disabled] .pagination-link,fieldset[disabled] .pagination-next,fieldset[disabled] .pagination-previous,fieldset[disabled] .select select,fieldset[disabled] .textarea{cursor:not-allowed}.breadcrumb,.button,.file,.is-unselectable,.pagination-ellipsis,.pagination-link,.pagination-next,.pagination-previous,.tabs{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.navbar-link:not(.is-arrowless)::after,.select:not(.is-multiple):not(.is-loading)::after{border:3px solid transparent;border-radius:2px;border-right:0;border-top:0;content:" ";display:block;height:.625em;margin-top:-.4375em;pointer-events:none;position:absolute;top:50%;transform:rotate(-45deg);transform-origin:center;width:.625em}.block:not(:last-child),.box:not(:last-child),.breadcrumb:not(:last-child),.content:not(:last-child),.level:not(:last-child),.message:not(:last-child),.notification:not(:last-child),.pagination:not(:last-child),.progress:not(:last-child),.subtitle:not(:last-child),.table-container:not(:last-child),.table:not(:last-child),.tabs:not(:last-child),.title:not(:last-child){margin-bottom:1.5rem}.delete,.modal-close{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-moz-appearance:none;-webkit-appearance:none;background-color:rgba(10,10,10,.2);border:none;border-radius:9999px;cursor:pointer;pointer-events:auto;display:inline-block;flex-grow:0;flex-shrink:0;font-size:0;height:20px;max-height:20px;max-width:20px;min-height:20px;min-width:20px;outline:0;position:relative;vertical-align:top;width:20px}.delete::after,.delete::before,.modal-close::after,.modal-close::before{background-color:#fff;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}.delete::before,.modal-close::before{height:2px;width:50%}.delete::after,.modal-close::after{height:50%;width:2px}.delete:focus,.delete:hover,.modal-close:focus,.modal-close:hover{background-color:rgba(10,10,10,.3)}.delete:active,.modal-close:active{background-color:rgba(10,10,10,.4)}.is-small.delete,.is-small.modal-close{height:16px;max-height:16px;max-width:16px;min-height:16px;min-width:16px;width:16px}.is-medium.delete,.is-medium.modal-close{height:24px;max-height:24px;max-width:24px;min-height:24px;min-width:24px;width:24px}.is-large.delete,.is-large.modal-close{height:32px;max-height:32px;max-width:32px;min-height:32px;min-width:32px;width:32px}.button.is-loading::after,.control.is-loading::after,.loader,.select.is-loading::after{-webkit-animation:spinAround .5s infinite linear;animation:spinAround .5s infinite linear;border:2px solid #dbdbdb;border-radius:9999px;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;position:relative;width:1em}.hero-video,.image.is-16by9 .has-ratio,.image.is-16by9 img,.image.is-1by1 .has-ratio,.image.is-1by1 img,.image.is-1by2 .has-ratio,.image.is-1by2 img,.image.is-1by3 .has-ratio,.image.is-1by3 img,.image.is-2by1 .has-ratio,.image.is-2by1 img,.image.is-2by3 .has-ratio,.image.is-2by3 img,.image.is-3by1 .has-ratio,.image.is-3by1 img,.image.is-3by2 .has-ratio,.image.is-3by2 img,.image.is-3by4 .has-ratio,.image.is-3by4 img,.image.is-3by5 .has-ratio,.image.is-3by5 img,.image.is-4by3 .has-ratio,.image.is-4by3 img,.image.is-4by5 .has-ratio,.image.is-4by5 img,.image.is-5by3 .has-ratio,.image.is-5by3 img,.image.is-5by4 .has-ratio,.image.is-5by4 img,.image.is-9by16 .has-ratio,.image.is-9by16 img,.image.is-square .has-ratio,.image.is-square img,.is-overlay,.modal,.modal-background{bottom:0;left:0;position:absolute;right:0;top:0}.navbar-burger{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:0 0;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0}/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */blockquote,body,dd,dl,dt,fieldset,figure,h1,h2,h3,h4,h5,h6,hr,html,iframe,legend,li,ol,p,pre,textarea,ul{margin:0;padding:0}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:400}ul{list-style:none}button,input,select,textarea{margin:0}html{box-sizing:border-box}*,::after,::before{box-sizing:inherit}img,video{height:auto;max-width:100%}iframe{border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}td:not([align]),th:not([align]){text-align:inherit}html{background-color:#fff;font-size:16px;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;min-width:300px;overflow-x:hidden;overflow-y:scroll;text-rendering:optimizeLegibility;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%}article,aside,figure,footer,header,hgroup,section{display:block}body,button,input,optgroup,select,textarea{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif}code,pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto;font-family:monospace}body{color:#4a4a4a;font-size:1em;font-weight:400;line-height:1.5}a{color:#485fc7;cursor:pointer;text-decoration:none}a strong{color:currentColor}a:hover{color:#363636}code{background-color:#f5f5f5;color:#da1039;font-size:.875em;font-weight:400;padding:.25em .5em .25em}hr{background-color:#f5f5f5;border:none;display:block;height:2px;margin:1.5rem 0}img{height:auto;max-width:100%}input[type=checkbox],input[type=radio]{vertical-align:baseline}small{font-size:.875em}span{font-style:inherit;font-weight:inherit}strong{color:#363636;font-weight:700}fieldset{border:none}pre{-webkit-overflow-scrolling:touch;background-color:#f5f5f5;color:#4a4a4a;font-size:.875em;overflow-x:auto;padding:1.25rem 1.5rem;white-space:pre;word-wrap:normal}pre code{background-color:transparent;color:currentColor;font-size:1em;padding:0}table td,table th{vertical-align:top}table td:not([align]),table th:not([align]){text-align:inherit}table th{color:#363636}@-webkit-keyframes spinAround{from{transform:rotate(0)}to{transform:rotate(359deg)}}@keyframes spinAround{from{transform:rotate(0)}to{transform:rotate(359deg)}}.box{background-color:#fff;border-radius:6px;box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.02);color:#4a4a4a;display:block;padding:1.25rem}a.box:focus,a.box:hover{box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px #485fc7}a.box:active{box-shadow:inset 0 1px 2px rgba(10,10,10,.2),0 0 0 1px #485fc7}.button{background-color:#fff;border-color:#dbdbdb;border-width:1px;color:#363636;cursor:pointer;justify-content:center;padding-bottom:calc(.5em - 1px);padding-left:1em;padding-right:1em;padding-top:calc(.5em - 1px);text-align:center;white-space:nowrap}.button strong{color:inherit}.button .icon,.button .icon.is-large,.button .icon.is-medium,.button .icon.is-small{height:1.5em;width:1.5em}.button .icon:first-child:not(:last-child){margin-left:calc(-.5em - 1px);margin-right:.25em}.button .icon:last-child:not(:first-child){margin-left:.25em;margin-right:calc(-.5em - 1px)}.button .icon:first-child:last-child{margin-left:calc(-.5em - 1px);margin-right:calc(-.5em - 1px)}.button.is-hovered,.button:hover{border-color:#b5b5b5;color:#363636}.button.is-focused,.button:focus{border-color:#485fc7;color:#363636}.button.is-focused:not(:active),.button:focus:not(:active){box-shadow:0 0 0 .125em rgba(72,95,199,.25)}.button.is-active,.button:active{border-color:#4a4a4a;color:#363636}.button.is-text{background-color:transparent;border-color:transparent;color:#4a4a4a;text-decoration:underline}.button.is-text.is-focused,.button.is-text.is-hovered,.button.is-text:focus,.button.is-text:hover{background-color:#f5f5f5;color:#363636}.button.is-text.is-active,.button.is-text:active{background-color:#e8e8e8;color:#363636}.button.is-text[disabled],fieldset[disabled] .button.is-text{background-color:transparent;border-color:transparent;box-shadow:none}.button.is-ghost{background:0 0;border-color:transparent;color:#485fc7;text-decoration:none}.button.is-ghost.is-hovered,.button.is-ghost:hover{color:#485fc7;text-decoration:underline}.button.is-white{background-color:#fff;border-color:transparent;color:#0a0a0a}.button.is-white.is-hovered,.button.is-white:hover{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}.button.is-white.is-focused,.button.is-white:focus{border-color:transparent;color:#0a0a0a}.button.is-white.is-focused:not(:active),.button.is-white:focus:not(:active){box-shadow:0 0 0 .125em rgba(255,255,255,.25)}.button.is-white.is-active,.button.is-white:active{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}.button.is-white[disabled],fieldset[disabled] .button.is-white{background-color:#fff;border-color:transparent;box-shadow:none}.button.is-white.is-inverted{background-color:#0a0a0a;color:#fff}.button.is-white.is-inverted.is-hovered,.button.is-white.is-inverted:hover{background-color:#000}.button.is-white.is-inverted[disabled],fieldset[disabled] .button.is-white.is-inverted{background-color:#0a0a0a;border-color:transparent;box-shadow:none;color:#fff}.button.is-white.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a!important}.button.is-white.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-white.is-outlined.is-focused,.button.is-white.is-outlined.is-hovered,.button.is-white.is-outlined:focus,.button.is-white.is-outlined:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}.button.is-white.is-outlined.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-white.is-outlined.is-loading.is-focused::after,.button.is-white.is-outlined.is-loading.is-hovered::after,.button.is-white.is-outlined.is-loading:focus::after,.button.is-white.is-outlined.is-loading:hover::after{border-color:transparent transparent #0a0a0a #0a0a0a!important}.button.is-white.is-outlined[disabled],fieldset[disabled] .button.is-white.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}.button.is-white.is-inverted.is-outlined.is-focused,.button.is-white.is-inverted.is-outlined.is-hovered,.button.is-white.is-inverted.is-outlined:focus,.button.is-white.is-inverted.is-outlined:hover{background-color:#0a0a0a;color:#fff}.button.is-white.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-white.is-inverted.is-outlined.is-loading:focus::after,.button.is-white.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-white.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-white.is-inverted.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}.button.is-black{background-color:#0a0a0a;border-color:transparent;color:#fff}.button.is-black.is-hovered,.button.is-black:hover{background-color:#040404;border-color:transparent;color:#fff}.button.is-black.is-focused,.button.is-black:focus{border-color:transparent;color:#fff}.button.is-black.is-focused:not(:active),.button.is-black:focus:not(:active){box-shadow:0 0 0 .125em rgba(10,10,10,.25)}.button.is-black.is-active,.button.is-black:active{background-color:#000;border-color:transparent;color:#fff}.button.is-black[disabled],fieldset[disabled] .button.is-black{background-color:#0a0a0a;border-color:transparent;box-shadow:none}.button.is-black.is-inverted{background-color:#fff;color:#0a0a0a}.button.is-black.is-inverted.is-hovered,.button.is-black.is-inverted:hover{background-color:#f2f2f2}.button.is-black.is-inverted[disabled],fieldset[disabled] .button.is-black.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#0a0a0a}.button.is-black.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;color:#0a0a0a}.button.is-black.is-outlined.is-focused,.button.is-black.is-outlined.is-hovered,.button.is-black.is-outlined:focus,.button.is-black.is-outlined:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.button.is-black.is-outlined.is-loading::after{border-color:transparent transparent #0a0a0a #0a0a0a!important}.button.is-black.is-outlined.is-loading.is-focused::after,.button.is-black.is-outlined.is-loading.is-hovered::after,.button.is-black.is-outlined.is-loading:focus::after,.button.is-black.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-black.is-outlined[disabled],fieldset[disabled] .button.is-black.is-outlined{background-color:transparent;border-color:#0a0a0a;box-shadow:none;color:#0a0a0a}.button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-black.is-inverted.is-outlined.is-focused,.button.is-black.is-inverted.is-outlined.is-hovered,.button.is-black.is-inverted.is-outlined:focus,.button.is-black.is-inverted.is-outlined:hover{background-color:#fff;color:#0a0a0a}.button.is-black.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-black.is-inverted.is-outlined.is-loading:focus::after,.button.is-black.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #0a0a0a #0a0a0a!important}.button.is-black.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-black.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-light{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light.is-hovered,.button.is-light:hover{background-color:#eee;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light.is-focused,.button.is-light:focus{border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light.is-focused:not(:active),.button.is-light:focus:not(:active){box-shadow:0 0 0 .125em rgba(245,245,245,.25)}.button.is-light.is-active,.button.is-light:active{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-light[disabled],fieldset[disabled] .button.is-light{background-color:#f5f5f5;border-color:transparent;box-shadow:none}.button.is-light.is-inverted{background-color:rgba(0,0,0,.7);color:#f5f5f5}.button.is-light.is-inverted.is-hovered,.button.is-light.is-inverted:hover{background-color:rgba(0,0,0,.7)}.button.is-light.is-inverted[disabled],fieldset[disabled] .button.is-light.is-inverted{background-color:rgba(0,0,0,.7);border-color:transparent;box-shadow:none;color:#f5f5f5}.button.is-light.is-loading::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7)!important}.button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;color:#f5f5f5}.button.is-light.is-outlined.is-focused,.button.is-light.is-outlined.is-hovered,.button.is-light.is-outlined:focus,.button.is-light.is-outlined:hover{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,.7)}.button.is-light.is-outlined.is-loading::after{border-color:transparent transparent #f5f5f5 #f5f5f5!important}.button.is-light.is-outlined.is-loading.is-focused::after,.button.is-light.is-outlined.is-loading.is-hovered::after,.button.is-light.is-outlined.is-loading:focus::after,.button.is-light.is-outlined.is-loading:hover::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7)!important}.button.is-light.is-outlined[disabled],fieldset[disabled] .button.is-light.is-outlined{background-color:transparent;border-color:#f5f5f5;box-shadow:none;color:#f5f5f5}.button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);color:rgba(0,0,0,.7)}.button.is-light.is-inverted.is-outlined.is-focused,.button.is-light.is-inverted.is-outlined.is-hovered,.button.is-light.is-inverted.is-outlined:focus,.button.is-light.is-inverted.is-outlined:hover{background-color:rgba(0,0,0,.7);color:#f5f5f5}.button.is-light.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-light.is-inverted.is-outlined.is-loading:focus::after,.button.is-light.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #f5f5f5 #f5f5f5!important}.button.is-light.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-light.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);box-shadow:none;color:rgba(0,0,0,.7)}.button.is-dark{background-color:#363636;border-color:transparent;color:#fff}.button.is-dark.is-hovered,.button.is-dark:hover{background-color:#2f2f2f;border-color:transparent;color:#fff}.button.is-dark.is-focused,.button.is-dark:focus{border-color:transparent;color:#fff}.button.is-dark.is-focused:not(:active),.button.is-dark:focus:not(:active){box-shadow:0 0 0 .125em rgba(54,54,54,.25)}.button.is-dark.is-active,.button.is-dark:active{background-color:#292929;border-color:transparent;color:#fff}.button.is-dark[disabled],fieldset[disabled] .button.is-dark{background-color:#363636;border-color:transparent;box-shadow:none}.button.is-dark.is-inverted{background-color:#fff;color:#363636}.button.is-dark.is-inverted.is-hovered,.button.is-dark.is-inverted:hover{background-color:#f2f2f2}.button.is-dark.is-inverted[disabled],fieldset[disabled] .button.is-dark.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#363636}.button.is-dark.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-dark.is-outlined{background-color:transparent;border-color:#363636;color:#363636}.button.is-dark.is-outlined.is-focused,.button.is-dark.is-outlined.is-hovered,.button.is-dark.is-outlined:focus,.button.is-dark.is-outlined:hover{background-color:#363636;border-color:#363636;color:#fff}.button.is-dark.is-outlined.is-loading::after{border-color:transparent transparent #363636 #363636!important}.button.is-dark.is-outlined.is-loading.is-focused::after,.button.is-dark.is-outlined.is-loading.is-hovered::after,.button.is-dark.is-outlined.is-loading:focus::after,.button.is-dark.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-dark.is-outlined[disabled],fieldset[disabled] .button.is-dark.is-outlined{background-color:transparent;border-color:#363636;box-shadow:none;color:#363636}.button.is-dark.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-dark.is-inverted.is-outlined.is-focused,.button.is-dark.is-inverted.is-outlined.is-hovered,.button.is-dark.is-inverted.is-outlined:focus,.button.is-dark.is-inverted.is-outlined:hover{background-color:#fff;color:#363636}.button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-dark.is-inverted.is-outlined.is-loading:focus::after,.button.is-dark.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #363636 #363636!important}.button.is-dark.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-dark.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-primary{background-color:#00d1b2;border-color:transparent;color:#fff}.button.is-primary.is-hovered,.button.is-primary:hover{background-color:#00c4a7;border-color:transparent;color:#fff}.button.is-primary.is-focused,.button.is-primary:focus{border-color:transparent;color:#fff}.button.is-primary.is-focused:not(:active),.button.is-primary:focus:not(:active){box-shadow:0 0 0 .125em rgba(0,209,178,.25)}.button.is-primary.is-active,.button.is-primary:active{background-color:#00b89c;border-color:transparent;color:#fff}.button.is-primary[disabled],fieldset[disabled] .button.is-primary{background-color:#00d1b2;border-color:transparent;box-shadow:none}.button.is-primary.is-inverted{background-color:#fff;color:#00d1b2}.button.is-primary.is-inverted.is-hovered,.button.is-primary.is-inverted:hover{background-color:#f2f2f2}.button.is-primary.is-inverted[disabled],fieldset[disabled] .button.is-primary.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#00d1b2}.button.is-primary.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-primary.is-outlined{background-color:transparent;border-color:#00d1b2;color:#00d1b2}.button.is-primary.is-outlined.is-focused,.button.is-primary.is-outlined.is-hovered,.button.is-primary.is-outlined:focus,.button.is-primary.is-outlined:hover{background-color:#00d1b2;border-color:#00d1b2;color:#fff}.button.is-primary.is-outlined.is-loading::after{border-color:transparent transparent #00d1b2 #00d1b2!important}.button.is-primary.is-outlined.is-loading.is-focused::after,.button.is-primary.is-outlined.is-loading.is-hovered::after,.button.is-primary.is-outlined.is-loading:focus::after,.button.is-primary.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-primary.is-outlined[disabled],fieldset[disabled] .button.is-primary.is-outlined{background-color:transparent;border-color:#00d1b2;box-shadow:none;color:#00d1b2}.button.is-primary.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-primary.is-inverted.is-outlined.is-focused,.button.is-primary.is-inverted.is-outlined.is-hovered,.button.is-primary.is-inverted.is-outlined:focus,.button.is-primary.is-inverted.is-outlined:hover{background-color:#fff;color:#00d1b2}.button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-primary.is-inverted.is-outlined.is-loading:focus::after,.button.is-primary.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #00d1b2 #00d1b2!important}.button.is-primary.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-primary.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-primary.is-light{background-color:#ebfffc;color:#00947e}.button.is-primary.is-light.is-hovered,.button.is-primary.is-light:hover{background-color:#defffa;border-color:transparent;color:#00947e}.button.is-primary.is-light.is-active,.button.is-primary.is-light:active{background-color:#d1fff8;border-color:transparent;color:#00947e}.button.is-link{background-color:#485fc7;border-color:transparent;color:#fff}.button.is-link.is-hovered,.button.is-link:hover{background-color:#3e56c4;border-color:transparent;color:#fff}.button.is-link.is-focused,.button.is-link:focus{border-color:transparent;color:#fff}.button.is-link.is-focused:not(:active),.button.is-link:focus:not(:active){box-shadow:0 0 0 .125em rgba(72,95,199,.25)}.button.is-link.is-active,.button.is-link:active{background-color:#3a51bb;border-color:transparent;color:#fff}.button.is-link[disabled],fieldset[disabled] .button.is-link{background-color:#485fc7;border-color:transparent;box-shadow:none}.button.is-link.is-inverted{background-color:#fff;color:#485fc7}.button.is-link.is-inverted.is-hovered,.button.is-link.is-inverted:hover{background-color:#f2f2f2}.button.is-link.is-inverted[disabled],fieldset[disabled] .button.is-link.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#485fc7}.button.is-link.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-link.is-outlined{background-color:transparent;border-color:#485fc7;color:#485fc7}.button.is-link.is-outlined.is-focused,.button.is-link.is-outlined.is-hovered,.button.is-link.is-outlined:focus,.button.is-link.is-outlined:hover{background-color:#485fc7;border-color:#485fc7;color:#fff}.button.is-link.is-outlined.is-loading::after{border-color:transparent transparent #485fc7 #485fc7!important}.button.is-link.is-outlined.is-loading.is-focused::after,.button.is-link.is-outlined.is-loading.is-hovered::after,.button.is-link.is-outlined.is-loading:focus::after,.button.is-link.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-link.is-outlined[disabled],fieldset[disabled] .button.is-link.is-outlined{background-color:transparent;border-color:#485fc7;box-shadow:none;color:#485fc7}.button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-link.is-inverted.is-outlined.is-focused,.button.is-link.is-inverted.is-outlined.is-hovered,.button.is-link.is-inverted.is-outlined:focus,.button.is-link.is-inverted.is-outlined:hover{background-color:#fff;color:#485fc7}.button.is-link.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-link.is-inverted.is-outlined.is-loading:focus::after,.button.is-link.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #485fc7 #485fc7!important}.button.is-link.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-link.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-link.is-light{background-color:#eff1fa;color:#3850b7}.button.is-link.is-light.is-hovered,.button.is-link.is-light:hover{background-color:#e6e9f7;border-color:transparent;color:#3850b7}.button.is-link.is-light.is-active,.button.is-link.is-light:active{background-color:#dce0f4;border-color:transparent;color:#3850b7}.button.is-info{background-color:#3e8ed0;border-color:transparent;color:#fff}.button.is-info.is-hovered,.button.is-info:hover{background-color:#3488ce;border-color:transparent;color:#fff}.button.is-info.is-focused,.button.is-info:focus{border-color:transparent;color:#fff}.button.is-info.is-focused:not(:active),.button.is-info:focus:not(:active){box-shadow:0 0 0 .125em rgba(62,142,208,.25)}.button.is-info.is-active,.button.is-info:active{background-color:#3082c5;border-color:transparent;color:#fff}.button.is-info[disabled],fieldset[disabled] .button.is-info{background-color:#3e8ed0;border-color:transparent;box-shadow:none}.button.is-info.is-inverted{background-color:#fff;color:#3e8ed0}.button.is-info.is-inverted.is-hovered,.button.is-info.is-inverted:hover{background-color:#f2f2f2}.button.is-info.is-inverted[disabled],fieldset[disabled] .button.is-info.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#3e8ed0}.button.is-info.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-info.is-outlined{background-color:transparent;border-color:#3e8ed0;color:#3e8ed0}.button.is-info.is-outlined.is-focused,.button.is-info.is-outlined.is-hovered,.button.is-info.is-outlined:focus,.button.is-info.is-outlined:hover{background-color:#3e8ed0;border-color:#3e8ed0;color:#fff}.button.is-info.is-outlined.is-loading::after{border-color:transparent transparent #3e8ed0 #3e8ed0!important}.button.is-info.is-outlined.is-loading.is-focused::after,.button.is-info.is-outlined.is-loading.is-hovered::after,.button.is-info.is-outlined.is-loading:focus::after,.button.is-info.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-info.is-outlined[disabled],fieldset[disabled] .button.is-info.is-outlined{background-color:transparent;border-color:#3e8ed0;box-shadow:none;color:#3e8ed0}.button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-info.is-inverted.is-outlined.is-focused,.button.is-info.is-inverted.is-outlined.is-hovered,.button.is-info.is-inverted.is-outlined:focus,.button.is-info.is-inverted.is-outlined:hover{background-color:#fff;color:#3e8ed0}.button.is-info.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-info.is-inverted.is-outlined.is-loading:focus::after,.button.is-info.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #3e8ed0 #3e8ed0!important}.button.is-info.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-info.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-info.is-light{background-color:#eff5fb;color:#296fa8}.button.is-info.is-light.is-hovered,.button.is-info.is-light:hover{background-color:#e4eff9;border-color:transparent;color:#296fa8}.button.is-info.is-light.is-active,.button.is-info.is-light:active{background-color:#dae9f6;border-color:transparent;color:#296fa8}.button.is-success{background-color:#48c78e;border-color:transparent;color:#fff}.button.is-success.is-hovered,.button.is-success:hover{background-color:#3ec487;border-color:transparent;color:#fff}.button.is-success.is-focused,.button.is-success:focus{border-color:transparent;color:#fff}.button.is-success.is-focused:not(:active),.button.is-success:focus:not(:active){box-shadow:0 0 0 .125em rgba(72,199,142,.25)}.button.is-success.is-active,.button.is-success:active{background-color:#3abb81;border-color:transparent;color:#fff}.button.is-success[disabled],fieldset[disabled] .button.is-success{background-color:#48c78e;border-color:transparent;box-shadow:none}.button.is-success.is-inverted{background-color:#fff;color:#48c78e}.button.is-success.is-inverted.is-hovered,.button.is-success.is-inverted:hover{background-color:#f2f2f2}.button.is-success.is-inverted[disabled],fieldset[disabled] .button.is-success.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#48c78e}.button.is-success.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-success.is-outlined{background-color:transparent;border-color:#48c78e;color:#48c78e}.button.is-success.is-outlined.is-focused,.button.is-success.is-outlined.is-hovered,.button.is-success.is-outlined:focus,.button.is-success.is-outlined:hover{background-color:#48c78e;border-color:#48c78e;color:#fff}.button.is-success.is-outlined.is-loading::after{border-color:transparent transparent #48c78e #48c78e!important}.button.is-success.is-outlined.is-loading.is-focused::after,.button.is-success.is-outlined.is-loading.is-hovered::after,.button.is-success.is-outlined.is-loading:focus::after,.button.is-success.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-success.is-outlined[disabled],fieldset[disabled] .button.is-success.is-outlined{background-color:transparent;border-color:#48c78e;box-shadow:none;color:#48c78e}.button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-success.is-inverted.is-outlined.is-focused,.button.is-success.is-inverted.is-outlined.is-hovered,.button.is-success.is-inverted.is-outlined:focus,.button.is-success.is-inverted.is-outlined:hover{background-color:#fff;color:#48c78e}.button.is-success.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-success.is-inverted.is-outlined.is-loading:focus::after,.button.is-success.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #48c78e #48c78e!important}.button.is-success.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-success.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-success.is-light{background-color:#effaf5;color:#257953}.button.is-success.is-light.is-hovered,.button.is-success.is-light:hover{background-color:#e6f7ef;border-color:transparent;color:#257953}.button.is-success.is-light.is-active,.button.is-success.is-light:active{background-color:#dcf4e9;border-color:transparent;color:#257953}.button.is-warning{background-color:#ffe08a;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning.is-hovered,.button.is-warning:hover{background-color:#ffdc7d;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning.is-focused,.button.is-warning:focus{border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning.is-focused:not(:active),.button.is-warning:focus:not(:active){box-shadow:0 0 0 .125em rgba(255,224,138,.25)}.button.is-warning.is-active,.button.is-warning:active{background-color:#ffd970;border-color:transparent;color:rgba(0,0,0,.7)}.button.is-warning[disabled],fieldset[disabled] .button.is-warning{background-color:#ffe08a;border-color:transparent;box-shadow:none}.button.is-warning.is-inverted{background-color:rgba(0,0,0,.7);color:#ffe08a}.button.is-warning.is-inverted.is-hovered,.button.is-warning.is-inverted:hover{background-color:rgba(0,0,0,.7)}.button.is-warning.is-inverted[disabled],fieldset[disabled] .button.is-warning.is-inverted{background-color:rgba(0,0,0,.7);border-color:transparent;box-shadow:none;color:#ffe08a}.button.is-warning.is-loading::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7)!important}.button.is-warning.is-outlined{background-color:transparent;border-color:#ffe08a;color:#ffe08a}.button.is-warning.is-outlined.is-focused,.button.is-warning.is-outlined.is-hovered,.button.is-warning.is-outlined:focus,.button.is-warning.is-outlined:hover{background-color:#ffe08a;border-color:#ffe08a;color:rgba(0,0,0,.7)}.button.is-warning.is-outlined.is-loading::after{border-color:transparent transparent #ffe08a #ffe08a!important}.button.is-warning.is-outlined.is-loading.is-focused::after,.button.is-warning.is-outlined.is-loading.is-hovered::after,.button.is-warning.is-outlined.is-loading:focus::after,.button.is-warning.is-outlined.is-loading:hover::after{border-color:transparent transparent rgba(0,0,0,.7) rgba(0,0,0,.7)!important}.button.is-warning.is-outlined[disabled],fieldset[disabled] .button.is-warning.is-outlined{background-color:transparent;border-color:#ffe08a;box-shadow:none;color:#ffe08a}.button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);color:rgba(0,0,0,.7)}.button.is-warning.is-inverted.is-outlined.is-focused,.button.is-warning.is-inverted.is-outlined.is-hovered,.button.is-warning.is-inverted.is-outlined:focus,.button.is-warning.is-inverted.is-outlined:hover{background-color:rgba(0,0,0,.7);color:#ffe08a}.button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-warning.is-inverted.is-outlined.is-loading:focus::after,.button.is-warning.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #ffe08a #ffe08a!important}.button.is-warning.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-warning.is-inverted.is-outlined{background-color:transparent;border-color:rgba(0,0,0,.7);box-shadow:none;color:rgba(0,0,0,.7)}.button.is-warning.is-light{background-color:#fffaeb;color:#946c00}.button.is-warning.is-light.is-hovered,.button.is-warning.is-light:hover{background-color:#fff6de;border-color:transparent;color:#946c00}.button.is-warning.is-light.is-active,.button.is-warning.is-light:active{background-color:#fff3d1;border-color:transparent;color:#946c00}.button.is-danger{background-color:#f14668;border-color:transparent;color:#fff}.button.is-danger.is-hovered,.button.is-danger:hover{background-color:#f03a5f;border-color:transparent;color:#fff}.button.is-danger.is-focused,.button.is-danger:focus{border-color:transparent;color:#fff}.button.is-danger.is-focused:not(:active),.button.is-danger:focus:not(:active){box-shadow:0 0 0 .125em rgba(241,70,104,.25)}.button.is-danger.is-active,.button.is-danger:active{background-color:#ef2e55;border-color:transparent;color:#fff}.button.is-danger[disabled],fieldset[disabled] .button.is-danger{background-color:#f14668;border-color:transparent;box-shadow:none}.button.is-danger.is-inverted{background-color:#fff;color:#f14668}.button.is-danger.is-inverted.is-hovered,.button.is-danger.is-inverted:hover{background-color:#f2f2f2}.button.is-danger.is-inverted[disabled],fieldset[disabled] .button.is-danger.is-inverted{background-color:#fff;border-color:transparent;box-shadow:none;color:#f14668}.button.is-danger.is-loading::after{border-color:transparent transparent #fff #fff!important}.button.is-danger.is-outlined{background-color:transparent;border-color:#f14668;color:#f14668}.button.is-danger.is-outlined.is-focused,.button.is-danger.is-outlined.is-hovered,.button.is-danger.is-outlined:focus,.button.is-danger.is-outlined:hover{background-color:#f14668;border-color:#f14668;color:#fff}.button.is-danger.is-outlined.is-loading::after{border-color:transparent transparent #f14668 #f14668!important}.button.is-danger.is-outlined.is-loading.is-focused::after,.button.is-danger.is-outlined.is-loading.is-hovered::after,.button.is-danger.is-outlined.is-loading:focus::after,.button.is-danger.is-outlined.is-loading:hover::after{border-color:transparent transparent #fff #fff!important}.button.is-danger.is-outlined[disabled],fieldset[disabled] .button.is-danger.is-outlined{background-color:transparent;border-color:#f14668;box-shadow:none;color:#f14668}.button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;color:#fff}.button.is-danger.is-inverted.is-outlined.is-focused,.button.is-danger.is-inverted.is-outlined.is-hovered,.button.is-danger.is-inverted.is-outlined:focus,.button.is-danger.is-inverted.is-outlined:hover{background-color:#fff;color:#f14668}.button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after,.button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after,.button.is-danger.is-inverted.is-outlined.is-loading:focus::after,.button.is-danger.is-inverted.is-outlined.is-loading:hover::after{border-color:transparent transparent #f14668 #f14668!important}.button.is-danger.is-inverted.is-outlined[disabled],fieldset[disabled] .button.is-danger.is-inverted.is-outlined{background-color:transparent;border-color:#fff;box-shadow:none;color:#fff}.button.is-danger.is-light{background-color:#feecf0;color:#cc0f35}.button.is-danger.is-light.is-hovered,.button.is-danger.is-light:hover{background-color:#fde0e6;border-color:transparent;color:#cc0f35}.button.is-danger.is-light.is-active,.button.is-danger.is-light:active{background-color:#fcd4dc;border-color:transparent;color:#cc0f35}.button.is-small{font-size:.75rem}.button.is-small:not(.is-rounded){border-radius:2px}.button.is-normal{font-size:1rem}.button.is-medium{font-size:1.25rem}.button.is-large{font-size:1.5rem}.button[disabled],fieldset[disabled] .button{background-color:#fff;border-color:#dbdbdb;box-shadow:none;opacity:.5}.button.is-fullwidth{display:flex;width:100%}.button.is-loading{color:transparent!important;pointer-events:none}.button.is-loading::after{position:absolute;left:calc(50% - (1em * .5));top:calc(50% - (1em * .5));position:absolute!important}.button.is-static{background-color:#f5f5f5;border-color:#dbdbdb;color:#7a7a7a;box-shadow:none;pointer-events:none}.button.is-rounded{border-radius:9999px;padding-left:calc(1em + .25em);padding-right:calc(1em + .25em)}.buttons{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}.buttons .button{margin-bottom:.5rem}.buttons .button:not(:last-child):not(.is-fullwidth){margin-right:.5rem}.buttons:last-child{margin-bottom:-.5rem}.buttons:not(:last-child){margin-bottom:1rem}.buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large){font-size:.75rem}.buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large):not(.is-rounded){border-radius:2px}.buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large){font-size:1.25rem}.buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium){font-size:1.5rem}.buttons.has-addons .button:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.buttons.has-addons .button:not(:last-child){border-bottom-right-radius:0;border-top-right-radius:0;margin-right:-1px}.buttons.has-addons .button:last-child{margin-right:0}.buttons.has-addons .button.is-hovered,.buttons.has-addons .button:hover{z-index:2}.buttons.has-addons .button.is-active,.buttons.has-addons .button.is-focused,.buttons.has-addons .button.is-selected,.buttons.has-addons .button:active,.buttons.has-addons .button:focus{z-index:3}.buttons.has-addons .button.is-active:hover,.buttons.has-addons .button.is-focused:hover,.buttons.has-addons .button.is-selected:hover,.buttons.has-addons .button:active:hover,.buttons.has-addons .button:focus:hover{z-index:4}.buttons.has-addons .button.is-expanded{flex-grow:1;flex-shrink:1}.buttons.is-centered{justify-content:center}.buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth){margin-left:.25rem;margin-right:.25rem}.buttons.is-right{justify-content:flex-end}.buttons.is-right:not(.has-addons) .button:not(.is-fullwidth){margin-left:.25rem;margin-right:.25rem}.container{flex-grow:1;margin:0 auto;position:relative;width:auto}.container.is-fluid{max-width:none!important;padding-left:32px;padding-right:32px;width:100%}@media screen and (min-width:1024px){.container{max-width:960px}}@media screen and (max-width:1215px){.container.is-widescreen:not(.is-max-desktop){max-width:1152px}}@media screen and (max-width:1407px){.container.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}@media screen and (min-width:1216px){.container:not(.is-max-desktop){max-width:1152px}}@media screen and (min-width:1408px){.container:not(.is-max-desktop):not(.is-max-widescreen){max-width:1344px}}.content li+li{margin-top:.25em}.content blockquote:not(:last-child),.content dl:not(:last-child),.content ol:not(:last-child),.content p:not(:last-child),.content pre:not(:last-child),.content table:not(:last-child),.content ul:not(:last-child){margin-bottom:1em}.content h1,.content h2,.content h3,.content h4,.content h5,.content h6{color:#363636;font-weight:600;line-height:1.125}.content h1{font-size:2em;margin-bottom:.5em}.content h1:not(:first-child){margin-top:1em}.content h2{font-size:1.75em;margin-bottom:.5714em}.content h2:not(:first-child){margin-top:1.1428em}.content h3{font-size:1.5em;margin-bottom:.6666em}.content h3:not(:first-child){margin-top:1.3333em}.content h4{font-size:1.25em;margin-bottom:.8em}.content h5{font-size:1.125em;margin-bottom:.8888em}.content h6{font-size:1em;margin-bottom:1em}.content blockquote{background-color:#f5f5f5;border-left:5px solid #dbdbdb;padding:1.25em 1.5em}.content ol{list-style-position:outside;margin-left:2em;margin-top:1em}.content ol:not([type]){list-style-type:decimal}.content ol:not([type]).is-lower-alpha{list-style-type:lower-alpha}.content ol:not([type]).is-lower-roman{list-style-type:lower-roman}.content ol:not([type]).is-upper-alpha{list-style-type:upper-alpha}.content ol:not([type]).is-upper-roman{list-style-type:upper-roman}.content ul{list-style:disc outside;margin-left:2em;margin-top:1em}.content ul ul{list-style-type:circle;margin-top:.5em}.content ul ul ul{list-style-type:square}.content dd{margin-left:2em}.content figure{margin-left:2em;margin-right:2em;text-align:center}.content figure:not(:first-child){margin-top:2em}.content figure:not(:last-child){margin-bottom:2em}.content figure img{display:inline-block}.content figure figcaption{font-style:italic}.content pre{-webkit-overflow-scrolling:touch;overflow-x:auto;padding:1.25em 1.5em;white-space:pre;word-wrap:normal}.content sub,.content sup{font-size:75%}.content table{width:100%}.content table td,.content table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:.5em .75em;vertical-align:top}.content table th{color:#363636}.content table th:not([align]){text-align:inherit}.content table thead td,.content table thead th{border-width:0 0 2px;color:#363636}.content table tfoot td,.content table tfoot th{border-width:2px 0 0;color:#363636}.content table tbody tr:last-child td,.content table tbody tr:last-child th{border-bottom-width:0}.content .tabs li+li{margin-top:0}.content.is-small{font-size:.75rem}.content.is-normal{font-size:1rem}.content.is-medium{font-size:1.25rem}.content.is-large{font-size:1.5rem}.icon{align-items:center;display:inline-flex;justify-content:center;height:1.5rem;width:1.5rem}.icon.is-small{height:1rem;width:1rem}.icon.is-medium{height:2rem;width:2rem}.icon.is-large{height:3rem;width:3rem}.icon-text{align-items:flex-start;color:inherit;display:inline-flex;flex-wrap:wrap;line-height:1.5rem;vertical-align:top}.icon-text .icon{flex-grow:0;flex-shrink:0}.icon-text .icon:not(:last-child){margin-right:.25em}.icon-text .icon:not(:first-child){margin-left:.25em}div.icon-text{display:flex}.image{display:block;position:relative}.image img{display:block;height:auto;width:100%}.image img.is-rounded{border-radius:9999px}.image.is-fullwidth{width:100%}.image.is-16by9 .has-ratio,.image.is-16by9 img,.image.is-1by1 .has-ratio,.image.is-1by1 img,.image.is-1by2 .has-ratio,.image.is-1by2 img,.image.is-1by3 .has-ratio,.image.is-1by3 img,.image.is-2by1 .has-ratio,.image.is-2by1 img,.image.is-2by3 .has-ratio,.image.is-2by3 img,.image.is-3by1 .has-ratio,.image.is-3by1 img,.image.is-3by2 .has-ratio,.image.is-3by2 img,.image.is-3by4 .has-ratio,.image.is-3by4 img,.image.is-3by5 .has-ratio,.image.is-3by5 img,.image.is-4by3 .has-ratio,.image.is-4by3 img,.image.is-4by5 .has-ratio,.image.is-4by5 img,.image.is-5by3 .has-ratio,.image.is-5by3 img,.image.is-5by4 .has-ratio,.image.is-5by4 img,.image.is-9by16 .has-ratio,.image.is-9by16 img,.image.is-square .has-ratio,.image.is-square img{height:100%;width:100%}.image.is-1by1,.image.is-square{padding-top:100%}.image.is-5by4{padding-top:80%}.image.is-4by3{padding-top:75%}.image.is-3by2{padding-top:66.6666%}.image.is-5by3{padding-top:60%}.image.is-16by9{padding-top:56.25%}.image.is-2by1{padding-top:50%}.image.is-3by1{padding-top:33.3333%}.image.is-4by5{padding-top:125%}.image.is-3by4{padding-top:133.3333%}.image.is-2by3{padding-top:150%}.image.is-3by5{padding-top:166.6666%}.image.is-9by16{padding-top:177.7777%}.image.is-1by2{padding-top:200%}.image.is-1by3{padding-top:300%}.image.is-16x16{height:16px;width:16px}.image.is-24x24{height:24px;width:24px}.image.is-32x32{height:32px;width:32px}.image.is-48x48{height:48px;width:48px}.image.is-64x64{height:64px;width:64px}.image.is-96x96{height:96px;width:96px}.image.is-128x128{height:128px;width:128px}.notification{background-color:#f5f5f5;border-radius:4px;position:relative;padding:1.25rem 2.5rem 1.25rem 1.5rem}.notification a:not(.button):not(.dropdown-item){color:currentColor;text-decoration:underline}.notification strong{color:currentColor}.notification code,.notification pre{background:#fff}.notification pre code{background:0 0}.notification>.delete{right:.5rem;position:absolute;top:.5rem}.notification .content,.notification .subtitle,.notification .title{color:currentColor}.notification.is-white{background-color:#fff;color:#0a0a0a}.notification.is-black{background-color:#0a0a0a;color:#fff}.notification.is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.notification.is-dark{background-color:#363636;color:#fff}.notification.is-primary{background-color:#00d1b2;color:#fff}.notification.is-primary.is-light{background-color:#ebfffc;color:#00947e}.notification.is-link{background-color:#485fc7;color:#fff}.notification.is-link.is-light{background-color:#eff1fa;color:#3850b7}.notification.is-info{background-color:#3e8ed0;color:#fff}.notification.is-info.is-light{background-color:#eff5fb;color:#296fa8}.notification.is-success{background-color:#48c78e;color:#fff}.notification.is-success.is-light{background-color:#effaf5;color:#257953}.notification.is-warning{background-color:#ffe08a;color:rgba(0,0,0,.7)}.notification.is-warning.is-light{background-color:#fffaeb;color:#946c00}.notification.is-danger{background-color:#f14668;color:#fff}.notification.is-danger.is-light{background-color:#feecf0;color:#cc0f35}.progress{-moz-appearance:none;-webkit-appearance:none;border:none;border-radius:9999px;display:block;height:1rem;overflow:hidden;padding:0;width:100%}.progress::-webkit-progress-bar{background-color:#ededed}.progress::-webkit-progress-value{background-color:#4a4a4a}.progress::-moz-progress-bar{background-color:#4a4a4a}.progress::-ms-fill{background-color:#4a4a4a;border:none}.progress.is-white::-webkit-progress-value{background-color:#fff}.progress.is-white::-moz-progress-bar{background-color:#fff}.progress.is-white::-ms-fill{background-color:#fff}.progress.is-white:indeterminate{background-image:linear-gradient(to right,#fff 30%,#ededed 30%)}.progress.is-black::-webkit-progress-value{background-color:#0a0a0a}.progress.is-black::-moz-progress-bar{background-color:#0a0a0a}.progress.is-black::-ms-fill{background-color:#0a0a0a}.progress.is-black:indeterminate{background-image:linear-gradient(to right,#0a0a0a 30%,#ededed 30%)}.progress.is-light::-webkit-progress-value{background-color:#f5f5f5}.progress.is-light::-moz-progress-bar{background-color:#f5f5f5}.progress.is-light::-ms-fill{background-color:#f5f5f5}.progress.is-light:indeterminate{background-image:linear-gradient(to right,#f5f5f5 30%,#ededed 30%)}.progress.is-dark::-webkit-progress-value{background-color:#363636}.progress.is-dark::-moz-progress-bar{background-color:#363636}.progress.is-dark::-ms-fill{background-color:#363636}.progress.is-dark:indeterminate{background-image:linear-gradient(to right,#363636 30%,#ededed 30%)}.progress.is-primary::-webkit-progress-value{background-color:#00d1b2}.progress.is-primary::-moz-progress-bar{background-color:#00d1b2}.progress.is-primary::-ms-fill{background-color:#00d1b2}.progress.is-primary:indeterminate{background-image:linear-gradient(to right,#00d1b2 30%,#ededed 30%)}.progress.is-link::-webkit-progress-value{background-color:#485fc7}.progress.is-link::-moz-progress-bar{background-color:#485fc7}.progress.is-link::-ms-fill{background-color:#485fc7}.progress.is-link:indeterminate{background-image:linear-gradient(to right,#485fc7 30%,#ededed 30%)}.progress.is-info::-webkit-progress-value{background-color:#3e8ed0}.progress.is-info::-moz-progress-bar{background-color:#3e8ed0}.progress.is-info::-ms-fill{background-color:#3e8ed0}.progress.is-info:indeterminate{background-image:linear-gradient(to right,#3e8ed0 30%,#ededed 30%)}.progress.is-success::-webkit-progress-value{background-color:#48c78e}.progress.is-success::-moz-progress-bar{background-color:#48c78e}.progress.is-success::-ms-fill{background-color:#48c78e}.progress.is-success:indeterminate{background-image:linear-gradient(to right,#48c78e 30%,#ededed 30%)}.progress.is-warning::-webkit-progress-value{background-color:#ffe08a}.progress.is-warning::-moz-progress-bar{background-color:#ffe08a}.progress.is-warning::-ms-fill{background-color:#ffe08a}.progress.is-warning:indeterminate{background-image:linear-gradient(to right,#ffe08a 30%,#ededed 30%)}.progress.is-danger::-webkit-progress-value{background-color:#f14668}.progress.is-danger::-moz-progress-bar{background-color:#f14668}.progress.is-danger::-ms-fill{background-color:#f14668}.progress.is-danger:indeterminate{background-image:linear-gradient(to right,#f14668 30%,#ededed 30%)}.progress:indeterminate{-webkit-animation-duration:1.5s;animation-duration:1.5s;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-name:moveIndeterminate;animation-name:moveIndeterminate;-webkit-animation-timing-function:linear;animation-timing-function:linear;background-color:#ededed;background-image:linear-gradient(to right,#4a4a4a 30%,#ededed 30%);background-position:top left;background-repeat:no-repeat;background-size:150% 150%}.progress:indeterminate::-webkit-progress-bar{background-color:transparent}.progress:indeterminate::-moz-progress-bar{background-color:transparent}.progress:indeterminate::-ms-fill{animation-name:none}.progress.is-small{height:.75rem}.progress.is-medium{height:1.25rem}.progress.is-large{height:1.5rem}@-webkit-keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}@keyframes moveIndeterminate{from{background-position:200% 0}to{background-position:-200% 0}}.table{background-color:#fff;color:#363636}.table td,.table th{border:1px solid #dbdbdb;border-width:0 0 1px;padding:.5em .75em;vertical-align:top}.table td.is-white,.table th.is-white{background-color:#fff;border-color:#fff;color:#0a0a0a}.table td.is-black,.table th.is-black{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.table td.is-light,.table th.is-light{background-color:#f5f5f5;border-color:#f5f5f5;color:rgba(0,0,0,.7)}.table td.is-dark,.table th.is-dark{background-color:#363636;border-color:#363636;color:#fff}.table td.is-primary,.table th.is-primary{background-color:#00d1b2;border-color:#00d1b2;color:#fff}.table td.is-link,.table th.is-link{background-color:#485fc7;border-color:#485fc7;color:#fff}.table td.is-info,.table th.is-info{background-color:#3e8ed0;border-color:#3e8ed0;color:#fff}.table td.is-success,.table th.is-success{background-color:#48c78e;border-color:#48c78e;color:#fff}.table td.is-warning,.table th.is-warning{background-color:#ffe08a;border-color:#ffe08a;color:rgba(0,0,0,.7)}.table td.is-danger,.table th.is-danger{background-color:#f14668;border-color:#f14668;color:#fff}.table td.is-narrow,.table th.is-narrow{white-space:nowrap;width:1%}.table td.is-selected,.table th.is-selected{background-color:#00d1b2;color:#fff}.table td.is-selected a,.table td.is-selected strong,.table th.is-selected a,.table th.is-selected strong{color:currentColor}.table td.is-vcentered,.table th.is-vcentered{vertical-align:middle}.table th{color:#363636}.table th:not([align]){text-align:inherit}.table tr.is-selected{background-color:#00d1b2;color:#fff}.table tr.is-selected a,.table tr.is-selected strong{color:currentColor}.table tr.is-selected td,.table tr.is-selected th{border-color:#fff;color:currentColor}.table thead{background-color:transparent}.table thead td,.table thead th{border-width:0 0 2px;color:#363636}.table tfoot{background-color:transparent}.table tfoot td,.table tfoot th{border-width:2px 0 0;color:#363636}.table tbody{background-color:transparent}.table tbody tr:last-child td,.table tbody tr:last-child th{border-bottom-width:0}.table.is-bordered td,.table.is-bordered th{border-width:1px}.table.is-bordered tr:last-child td,.table.is-bordered tr:last-child th{border-bottom-width:1px}.table.is-fullwidth{width:100%}.table.is-hoverable tbody tr:not(.is-selected):hover{background-color:#fafafa}.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover{background-color:#fafafa}.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even){background-color:#f5f5f5}.table.is-narrow td,.table.is-narrow th{padding:.25em .5em}.table.is-striped tbody tr:not(.is-selected):nth-child(even){background-color:#fafafa}.table-container{-webkit-overflow-scrolling:touch;overflow:auto;overflow-y:hidden;max-width:100%}.tags{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-start}.tags .tag{margin-bottom:.5rem}.tags .tag:not(:last-child){margin-right:.5rem}.tags:last-child{margin-bottom:-.5rem}.tags:not(:last-child){margin-bottom:1rem}.tags.are-medium .tag:not(.is-normal):not(.is-large){font-size:1rem}.tags.are-large .tag:not(.is-normal):not(.is-medium){font-size:1.25rem}.tags.is-centered{justify-content:center}.tags.is-centered .tag{margin-right:.25rem;margin-left:.25rem}.tags.is-right{justify-content:flex-end}.tags.is-right .tag:not(:first-child){margin-left:.5rem}.tags.is-right .tag:not(:last-child){margin-right:0}.tags.has-addons .tag{margin-right:0}.tags.has-addons .tag:not(:first-child){margin-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.tags.has-addons .tag:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.tag:not(body){align-items:center;background-color:#f5f5f5;border-radius:4px;color:#4a4a4a;display:inline-flex;font-size:.75rem;height:2em;justify-content:center;line-height:1.5;padding-left:.75em;padding-right:.75em;white-space:nowrap}.tag:not(body) .delete{margin-left:.25rem;margin-right:-.375rem}.tag:not(body).is-white{background-color:#fff;color:#0a0a0a}.tag:not(body).is-black{background-color:#0a0a0a;color:#fff}.tag:not(body).is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.tag:not(body).is-dark{background-color:#363636;color:#fff}.tag:not(body).is-primary{background-color:#00d1b2;color:#fff}.tag:not(body).is-primary.is-light{background-color:#ebfffc;color:#00947e}.tag:not(body).is-link{background-color:#485fc7;color:#fff}.tag:not(body).is-link.is-light{background-color:#eff1fa;color:#3850b7}.tag:not(body).is-info{background-color:#3e8ed0;color:#fff}.tag:not(body).is-info.is-light{background-color:#eff5fb;color:#296fa8}.tag:not(body).is-success{background-color:#48c78e;color:#fff}.tag:not(body).is-success.is-light{background-color:#effaf5;color:#257953}.tag:not(body).is-warning{background-color:#ffe08a;color:rgba(0,0,0,.7)}.tag:not(body).is-warning.is-light{background-color:#fffaeb;color:#946c00}.tag:not(body).is-danger{background-color:#f14668;color:#fff}.tag:not(body).is-danger.is-light{background-color:#feecf0;color:#cc0f35}.tag:not(body).is-normal{font-size:.75rem}.tag:not(body).is-medium{font-size:1rem}.tag:not(body).is-large{font-size:1.25rem}.tag:not(body) .icon:first-child:not(:last-child){margin-left:-.375em;margin-right:.1875em}.tag:not(body) .icon:last-child:not(:first-child){margin-left:.1875em;margin-right:-.375em}.tag:not(body) .icon:first-child:last-child{margin-left:-.375em;margin-right:-.375em}.tag:not(body).is-delete{margin-left:1px;padding:0;position:relative;width:2em}.tag:not(body).is-delete::after,.tag:not(body).is-delete::before{background-color:currentColor;content:"";display:block;left:50%;position:absolute;top:50%;transform:translateX(-50%) translateY(-50%) rotate(45deg);transform-origin:center center}.tag:not(body).is-delete::before{height:1px;width:50%}.tag:not(body).is-delete::after{height:50%;width:1px}.tag:not(body).is-delete:focus,.tag:not(body).is-delete:hover{background-color:#e8e8e8}.tag:not(body).is-delete:active{background-color:#dbdbdb}.tag:not(body).is-rounded{border-radius:9999px}a.tag:hover{text-decoration:underline}.subtitle,.title{word-break:break-word}.subtitle em,.subtitle span,.title em,.title span{font-weight:inherit}.subtitle sub,.title sub{font-size:.75em}.subtitle sup,.title sup{font-size:.75em}.subtitle .tag,.title .tag{vertical-align:middle}.title{color:#363636;font-size:2rem;font-weight:600;line-height:1.125}.title strong{color:inherit;font-weight:inherit}.title:not(.is-spaced)+.subtitle{margin-top:-1.25rem}.title.is-1{font-size:3rem}.title.is-2{font-size:2.5rem}.title.is-3{font-size:2rem}.title.is-4{font-size:1.5rem}.title.is-5{font-size:1.25rem}.title.is-6{font-size:1rem}.title.is-7{font-size:.75rem}.subtitle{color:#4a4a4a;font-size:1.25rem;font-weight:400;line-height:1.25}.subtitle strong{color:#363636;font-weight:600}.subtitle:not(.is-spaced)+.title{margin-top:-1.25rem}.subtitle.is-1{font-size:3rem}.subtitle.is-2{font-size:2.5rem}.subtitle.is-3{font-size:2rem}.subtitle.is-4{font-size:1.5rem}.subtitle.is-5{font-size:1.25rem}.subtitle.is-6{font-size:1rem}.subtitle.is-7{font-size:.75rem}.heading{display:block;font-size:11px;letter-spacing:1px;margin-bottom:5px;text-transform:uppercase}.number{align-items:center;background-color:#f5f5f5;border-radius:9999px;display:inline-flex;font-size:1.25rem;height:2em;justify-content:center;margin-right:1.5rem;min-width:2.5em;padding:.25rem .5rem;text-align:center;vertical-align:top}.input,.select select,.textarea{background-color:#fff;border-color:#dbdbdb;border-radius:4px;color:#363636}.input::-moz-placeholder,.select select::-moz-placeholder,.textarea::-moz-placeholder{color:rgba(54,54,54,.3)}.input::-webkit-input-placeholder,.select select::-webkit-input-placeholder,.textarea::-webkit-input-placeholder{color:rgba(54,54,54,.3)}.input:-moz-placeholder,.select select:-moz-placeholder,.textarea:-moz-placeholder{color:rgba(54,54,54,.3)}.input:-ms-input-placeholder,.select select:-ms-input-placeholder,.textarea:-ms-input-placeholder{color:rgba(54,54,54,.3)}.input:hover,.is-hovered.input,.is-hovered.textarea,.select select.is-hovered,.select select:hover,.textarea:hover{border-color:#b5b5b5}.input:active,.input:focus,.is-active.input,.is-active.textarea,.is-focused.input,.is-focused.textarea,.select select.is-active,.select select.is-focused,.select select:active,.select select:focus,.textarea:active,.textarea:focus{border-color:#485fc7;box-shadow:0 0 0 .125em rgba(72,95,199,.25)}.input[disabled],.select fieldset[disabled] select,.select select[disabled],.textarea[disabled],fieldset[disabled] .input,fieldset[disabled] .select select,fieldset[disabled] .textarea{background-color:#f5f5f5;border-color:#f5f5f5;box-shadow:none;color:#7a7a7a}.input[disabled]::-moz-placeholder,.select fieldset[disabled] select::-moz-placeholder,.select select[disabled]::-moz-placeholder,.textarea[disabled]::-moz-placeholder,fieldset[disabled] .input::-moz-placeholder,fieldset[disabled] .select select::-moz-placeholder,fieldset[disabled] .textarea::-moz-placeholder{color:rgba(122,122,122,.3)}.input[disabled]::-webkit-input-placeholder,.select fieldset[disabled] select::-webkit-input-placeholder,.select select[disabled]::-webkit-input-placeholder,.textarea[disabled]::-webkit-input-placeholder,fieldset[disabled] .input::-webkit-input-placeholder,fieldset[disabled] .select select::-webkit-input-placeholder,fieldset[disabled] .textarea::-webkit-input-placeholder{color:rgba(122,122,122,.3)}.input[disabled]:-moz-placeholder,.select fieldset[disabled] select:-moz-placeholder,.select select[disabled]:-moz-placeholder,.textarea[disabled]:-moz-placeholder,fieldset[disabled] .input:-moz-placeholder,fieldset[disabled] .select select:-moz-placeholder,fieldset[disabled] .textarea:-moz-placeholder{color:rgba(122,122,122,.3)}.input[disabled]:-ms-input-placeholder,.select fieldset[disabled] select:-ms-input-placeholder,.select select[disabled]:-ms-input-placeholder,.textarea[disabled]:-ms-input-placeholder,fieldset[disabled] .input:-ms-input-placeholder,fieldset[disabled] .select select:-ms-input-placeholder,fieldset[disabled] .textarea:-ms-input-placeholder{color:rgba(122,122,122,.3)}.input,.textarea{box-shadow:inset 0 .0625em .125em rgba(10,10,10,.05);max-width:100%;width:100%}.input[readonly],.textarea[readonly]{box-shadow:none}.is-white.input,.is-white.textarea{border-color:#fff}.is-white.input:active,.is-white.input:focus,.is-white.is-active.input,.is-white.is-active.textarea,.is-white.is-focused.input,.is-white.is-focused.textarea,.is-white.textarea:active,.is-white.textarea:focus{box-shadow:0 0 0 .125em rgba(255,255,255,.25)}.is-black.input,.is-black.textarea{border-color:#0a0a0a}.is-black.input:active,.is-black.input:focus,.is-black.is-active.input,.is-black.is-active.textarea,.is-black.is-focused.input,.is-black.is-focused.textarea,.is-black.textarea:active,.is-black.textarea:focus{box-shadow:0 0 0 .125em rgba(10,10,10,.25)}.is-light.input,.is-light.textarea{border-color:#f5f5f5}.is-light.input:active,.is-light.input:focus,.is-light.is-active.input,.is-light.is-active.textarea,.is-light.is-focused.input,.is-light.is-focused.textarea,.is-light.textarea:active,.is-light.textarea:focus{box-shadow:0 0 0 .125em rgba(245,245,245,.25)}.is-dark.input,.is-dark.textarea{border-color:#363636}.is-dark.input:active,.is-dark.input:focus,.is-dark.is-active.input,.is-dark.is-active.textarea,.is-dark.is-focused.input,.is-dark.is-focused.textarea,.is-dark.textarea:active,.is-dark.textarea:focus{box-shadow:0 0 0 .125em rgba(54,54,54,.25)}.is-primary.input,.is-primary.textarea{border-color:#00d1b2}.is-primary.input:active,.is-primary.input:focus,.is-primary.is-active.input,.is-primary.is-active.textarea,.is-primary.is-focused.input,.is-primary.is-focused.textarea,.is-primary.textarea:active,.is-primary.textarea:focus{box-shadow:0 0 0 .125em rgba(0,209,178,.25)}.is-link.input,.is-link.textarea{border-color:#485fc7}.is-link.input:active,.is-link.input:focus,.is-link.is-active.input,.is-link.is-active.textarea,.is-link.is-focused.input,.is-link.is-focused.textarea,.is-link.textarea:active,.is-link.textarea:focus{box-shadow:0 0 0 .125em rgba(72,95,199,.25)}.is-info.input,.is-info.textarea{border-color:#3e8ed0}.is-info.input:active,.is-info.input:focus,.is-info.is-active.input,.is-info.is-active.textarea,.is-info.is-focused.input,.is-info.is-focused.textarea,.is-info.textarea:active,.is-info.textarea:focus{box-shadow:0 0 0 .125em rgba(62,142,208,.25)}.is-success.input,.is-success.textarea{border-color:#48c78e}.is-success.input:active,.is-success.input:focus,.is-success.is-active.input,.is-success.is-active.textarea,.is-success.is-focused.input,.is-success.is-focused.textarea,.is-success.textarea:active,.is-success.textarea:focus{box-shadow:0 0 0 .125em rgba(72,199,142,.25)}.is-warning.input,.is-warning.textarea{border-color:#ffe08a}.is-warning.input:active,.is-warning.input:focus,.is-warning.is-active.input,.is-warning.is-active.textarea,.is-warning.is-focused.input,.is-warning.is-focused.textarea,.is-warning.textarea:active,.is-warning.textarea:focus{box-shadow:0 0 0 .125em rgba(255,224,138,.25)}.is-danger.input,.is-danger.textarea{border-color:#f14668}.is-danger.input:active,.is-danger.input:focus,.is-danger.is-active.input,.is-danger.is-active.textarea,.is-danger.is-focused.input,.is-danger.is-focused.textarea,.is-danger.textarea:active,.is-danger.textarea:focus{box-shadow:0 0 0 .125em rgba(241,70,104,.25)}.is-small.input,.is-small.textarea{border-radius:2px;font-size:.75rem}.is-medium.input,.is-medium.textarea{font-size:1.25rem}.is-large.input,.is-large.textarea{font-size:1.5rem}.is-fullwidth.input,.is-fullwidth.textarea{display:block;width:100%}.is-inline.input,.is-inline.textarea{display:inline;width:auto}.input.is-rounded{border-radius:9999px;padding-left:calc(calc(.75em - 1px) + .375em);padding-right:calc(calc(.75em - 1px) + .375em)}.input.is-static{background-color:transparent;border-color:transparent;box-shadow:none;padding-left:0;padding-right:0}.textarea{display:block;max-width:100%;min-width:100%;padding:calc(.75em - 1px);resize:vertical}.textarea:not([rows]){max-height:40em;min-height:8em}.textarea[rows]{height:initial}.textarea.has-fixed-size{resize:none}.checkbox,.radio{cursor:pointer;display:inline-block;line-height:1.25;position:relative}.checkbox input,.radio input{cursor:pointer}.checkbox:hover,.radio:hover{color:#363636}.checkbox input[disabled],.checkbox[disabled],.radio input[disabled],.radio[disabled],fieldset[disabled] .checkbox,fieldset[disabled] .radio{color:#7a7a7a;cursor:not-allowed}.radio+.radio{margin-left:.5em}.select{display:inline-block;max-width:100%;position:relative;vertical-align:top}.select:not(.is-multiple){height:2.5em}.select:not(.is-multiple):not(.is-loading)::after{border-color:#485fc7;right:1.125em;z-index:4}.select.is-rounded select{border-radius:9999px;padding-left:1em}.select select{cursor:pointer;display:block;font-size:1em;max-width:100%;outline:0}.select select::-ms-expand{display:none}.select select[disabled]:hover,fieldset[disabled] .select select:hover{border-color:#f5f5f5}.select select:not([multiple]){padding-right:2.5em}.select select[multiple]{height:auto;padding:0}.select select[multiple] option{padding:.5em 1em}.select:not(.is-multiple):not(.is-loading):hover::after{border-color:#363636}.select.is-white:not(:hover)::after{border-color:#fff}.select.is-white select{border-color:#fff}.select.is-white select.is-hovered,.select.is-white select:hover{border-color:#f2f2f2}.select.is-white select.is-active,.select.is-white select.is-focused,.select.is-white select:active,.select.is-white select:focus{box-shadow:0 0 0 .125em rgba(255,255,255,.25)}.select.is-black:not(:hover)::after{border-color:#0a0a0a}.select.is-black select{border-color:#0a0a0a}.select.is-black select.is-hovered,.select.is-black select:hover{border-color:#000}.select.is-black select.is-active,.select.is-black select.is-focused,.select.is-black select:active,.select.is-black select:focus{box-shadow:0 0 0 .125em rgba(10,10,10,.25)}.select.is-light:not(:hover)::after{border-color:#f5f5f5}.select.is-light select{border-color:#f5f5f5}.select.is-light select.is-hovered,.select.is-light select:hover{border-color:#e8e8e8}.select.is-light select.is-active,.select.is-light select.is-focused,.select.is-light select:active,.select.is-light select:focus{box-shadow:0 0 0 .125em rgba(245,245,245,.25)}.select.is-dark:not(:hover)::after{border-color:#363636}.select.is-dark select{border-color:#363636}.select.is-dark select.is-hovered,.select.is-dark select:hover{border-color:#292929}.select.is-dark select.is-active,.select.is-dark select.is-focused,.select.is-dark select:active,.select.is-dark select:focus{box-shadow:0 0 0 .125em rgba(54,54,54,.25)}.select.is-primary:not(:hover)::after{border-color:#00d1b2}.select.is-primary select{border-color:#00d1b2}.select.is-primary select.is-hovered,.select.is-primary select:hover{border-color:#00b89c}.select.is-primary select.is-active,.select.is-primary select.is-focused,.select.is-primary select:active,.select.is-primary select:focus{box-shadow:0 0 0 .125em rgba(0,209,178,.25)}.select.is-link:not(:hover)::after{border-color:#485fc7}.select.is-link select{border-color:#485fc7}.select.is-link select.is-hovered,.select.is-link select:hover{border-color:#3a51bb}.select.is-link select.is-active,.select.is-link select.is-focused,.select.is-link select:active,.select.is-link select:focus{box-shadow:0 0 0 .125em rgba(72,95,199,.25)}.select.is-info:not(:hover)::after{border-color:#3e8ed0}.select.is-info select{border-color:#3e8ed0}.select.is-info select.is-hovered,.select.is-info select:hover{border-color:#3082c5}.select.is-info select.is-active,.select.is-info select.is-focused,.select.is-info select:active,.select.is-info select:focus{box-shadow:0 0 0 .125em rgba(62,142,208,.25)}.select.is-success:not(:hover)::after{border-color:#48c78e}.select.is-success select{border-color:#48c78e}.select.is-success select.is-hovered,.select.is-success select:hover{border-color:#3abb81}.select.is-success select.is-active,.select.is-success select.is-focused,.select.is-success select:active,.select.is-success select:focus{box-shadow:0 0 0 .125em rgba(72,199,142,.25)}.select.is-warning:not(:hover)::after{border-color:#ffe08a}.select.is-warning select{border-color:#ffe08a}.select.is-warning select.is-hovered,.select.is-warning select:hover{border-color:#ffd970}.select.is-warning select.is-active,.select.is-warning select.is-focused,.select.is-warning select:active,.select.is-warning select:focus{box-shadow:0 0 0 .125em rgba(255,224,138,.25)}.select.is-danger:not(:hover)::after{border-color:#f14668}.select.is-danger select{border-color:#f14668}.select.is-danger select.is-hovered,.select.is-danger select:hover{border-color:#ef2e55}.select.is-danger select.is-active,.select.is-danger select.is-focused,.select.is-danger select:active,.select.is-danger select:focus{box-shadow:0 0 0 .125em rgba(241,70,104,.25)}.select.is-small{border-radius:2px;font-size:.75rem}.select.is-medium{font-size:1.25rem}.select.is-large{font-size:1.5rem}.select.is-disabled::after{border-color:#7a7a7a}.select.is-fullwidth{width:100%}.select.is-fullwidth select{width:100%}.select.is-loading::after{margin-top:0;position:absolute;right:.625em;top:.625em;transform:none}.select.is-loading.is-small:after{font-size:.75rem}.select.is-loading.is-medium:after{font-size:1.25rem}.select.is-loading.is-large:after{font-size:1.5rem}.file{align-items:stretch;display:flex;justify-content:flex-start;position:relative}.file.is-white .file-cta{background-color:#fff;border-color:transparent;color:#0a0a0a}.file.is-white.is-hovered .file-cta,.file.is-white:hover .file-cta{background-color:#f9f9f9;border-color:transparent;color:#0a0a0a}.file.is-white.is-focused .file-cta,.file.is-white:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(255,255,255,.25);color:#0a0a0a}.file.is-white.is-active .file-cta,.file.is-white:active .file-cta{background-color:#f2f2f2;border-color:transparent;color:#0a0a0a}.file.is-black .file-cta{background-color:#0a0a0a;border-color:transparent;color:#fff}.file.is-black.is-hovered .file-cta,.file.is-black:hover .file-cta{background-color:#040404;border-color:transparent;color:#fff}.file.is-black.is-focused .file-cta,.file.is-black:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(10,10,10,.25);color:#fff}.file.is-black.is-active .file-cta,.file.is-black:active .file-cta{background-color:#000;border-color:transparent;color:#fff}.file.is-light .file-cta{background-color:#f5f5f5;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-light.is-hovered .file-cta,.file.is-light:hover .file-cta{background-color:#eee;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-light.is-focused .file-cta,.file.is-light:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(245,245,245,.25);color:rgba(0,0,0,.7)}.file.is-light.is-active .file-cta,.file.is-light:active .file-cta{background-color:#e8e8e8;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-dark .file-cta{background-color:#363636;border-color:transparent;color:#fff}.file.is-dark.is-hovered .file-cta,.file.is-dark:hover .file-cta{background-color:#2f2f2f;border-color:transparent;color:#fff}.file.is-dark.is-focused .file-cta,.file.is-dark:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(54,54,54,.25);color:#fff}.file.is-dark.is-active .file-cta,.file.is-dark:active .file-cta{background-color:#292929;border-color:transparent;color:#fff}.file.is-primary .file-cta{background-color:#00d1b2;border-color:transparent;color:#fff}.file.is-primary.is-hovered .file-cta,.file.is-primary:hover .file-cta{background-color:#00c4a7;border-color:transparent;color:#fff}.file.is-primary.is-focused .file-cta,.file.is-primary:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(0,209,178,.25);color:#fff}.file.is-primary.is-active .file-cta,.file.is-primary:active .file-cta{background-color:#00b89c;border-color:transparent;color:#fff}.file.is-link .file-cta{background-color:#485fc7;border-color:transparent;color:#fff}.file.is-link.is-hovered .file-cta,.file.is-link:hover .file-cta{background-color:#3e56c4;border-color:transparent;color:#fff}.file.is-link.is-focused .file-cta,.file.is-link:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(72,95,199,.25);color:#fff}.file.is-link.is-active .file-cta,.file.is-link:active .file-cta{background-color:#3a51bb;border-color:transparent;color:#fff}.file.is-info .file-cta{background-color:#3e8ed0;border-color:transparent;color:#fff}.file.is-info.is-hovered .file-cta,.file.is-info:hover .file-cta{background-color:#3488ce;border-color:transparent;color:#fff}.file.is-info.is-focused .file-cta,.file.is-info:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(62,142,208,.25);color:#fff}.file.is-info.is-active .file-cta,.file.is-info:active .file-cta{background-color:#3082c5;border-color:transparent;color:#fff}.file.is-success .file-cta{background-color:#48c78e;border-color:transparent;color:#fff}.file.is-success.is-hovered .file-cta,.file.is-success:hover .file-cta{background-color:#3ec487;border-color:transparent;color:#fff}.file.is-success.is-focused .file-cta,.file.is-success:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(72,199,142,.25);color:#fff}.file.is-success.is-active .file-cta,.file.is-success:active .file-cta{background-color:#3abb81;border-color:transparent;color:#fff}.file.is-warning .file-cta{background-color:#ffe08a;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-warning.is-hovered .file-cta,.file.is-warning:hover .file-cta{background-color:#ffdc7d;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-warning.is-focused .file-cta,.file.is-warning:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(255,224,138,.25);color:rgba(0,0,0,.7)}.file.is-warning.is-active .file-cta,.file.is-warning:active .file-cta{background-color:#ffd970;border-color:transparent;color:rgba(0,0,0,.7)}.file.is-danger .file-cta{background-color:#f14668;border-color:transparent;color:#fff}.file.is-danger.is-hovered .file-cta,.file.is-danger:hover .file-cta{background-color:#f03a5f;border-color:transparent;color:#fff}.file.is-danger.is-focused .file-cta,.file.is-danger:focus .file-cta{border-color:transparent;box-shadow:0 0 .5em rgba(241,70,104,.25);color:#fff}.file.is-danger.is-active .file-cta,.file.is-danger:active .file-cta{background-color:#ef2e55;border-color:transparent;color:#fff}.file.is-small{font-size:.75rem}.file.is-normal{font-size:1rem}.file.is-medium{font-size:1.25rem}.file.is-medium .file-icon .fa{font-size:21px}.file.is-large{font-size:1.5rem}.file.is-large .file-icon .fa{font-size:28px}.file.has-name .file-cta{border-bottom-right-radius:0;border-top-right-radius:0}.file.has-name .file-name{border-bottom-left-radius:0;border-top-left-radius:0}.file.has-name.is-empty .file-cta{border-radius:4px}.file.has-name.is-empty .file-name{display:none}.file.is-boxed .file-label{flex-direction:column}.file.is-boxed .file-cta{flex-direction:column;height:auto;padding:1em 3em}.file.is-boxed .file-name{border-width:0 1px 1px}.file.is-boxed .file-icon{height:1.5em;width:1.5em}.file.is-boxed .file-icon .fa{font-size:21px}.file.is-boxed.is-small .file-icon .fa{font-size:14px}.file.is-boxed.is-medium .file-icon .fa{font-size:28px}.file.is-boxed.is-large .file-icon .fa{font-size:35px}.file.is-boxed.has-name .file-cta{border-radius:4px 4px 0 0}.file.is-boxed.has-name .file-name{border-radius:0 0 4px 4px;border-width:0 1px 1px}.file.is-centered{justify-content:center}.file.is-fullwidth .file-label{width:100%}.file.is-fullwidth .file-name{flex-grow:1;max-width:none}.file.is-right{justify-content:flex-end}.file.is-right .file-cta{border-radius:0 4px 4px 0}.file.is-right .file-name{border-radius:4px 0 0 4px;border-width:1px 0 1px 1px;order:-1}.file-label{align-items:stretch;display:flex;cursor:pointer;justify-content:flex-start;overflow:hidden;position:relative}.file-label:hover .file-cta{background-color:#eee;color:#363636}.file-label:hover .file-name{border-color:#d5d5d5}.file-label:active .file-cta{background-color:#e8e8e8;color:#363636}.file-label:active .file-name{border-color:#cfcfcf}.file-input{height:100%;left:0;opacity:0;outline:0;position:absolute;top:0;width:100%}.file-cta,.file-name{border-color:#dbdbdb;border-radius:4px;font-size:1em;padding-left:1em;padding-right:1em;white-space:nowrap}.file-cta{background-color:#f5f5f5;color:#4a4a4a}.file-name{border-color:#dbdbdb;border-style:solid;border-width:1px 1px 1px 0;display:block;max-width:16em;overflow:hidden;text-align:inherit;text-overflow:ellipsis}.file-icon{align-items:center;display:flex;height:1em;justify-content:center;margin-right:.5em;width:1em}.file-icon .fa{font-size:14px}.label{color:#363636;display:block;font-size:1rem;font-weight:700}.label:not(:last-child){margin-bottom:.5em}.label.is-small{font-size:.75rem}.label.is-medium{font-size:1.25rem}.label.is-large{font-size:1.5rem}.help{display:block;font-size:.75rem;margin-top:.25rem}.help.is-white{color:#fff}.help.is-black{color:#0a0a0a}.help.is-light{color:#f5f5f5}.help.is-dark{color:#363636}.help.is-primary{color:#00d1b2}.help.is-link{color:#485fc7}.help.is-info{color:#3e8ed0}.help.is-success{color:#48c78e}.help.is-warning{color:#ffe08a}.help.is-danger{color:#f14668}.field:not(:last-child){margin-bottom:.75rem}.field.has-addons{display:flex;justify-content:flex-start}.field.has-addons .control:not(:last-child){margin-right:-1px}.field.has-addons .control:not(:first-child):not(:last-child) .button,.field.has-addons .control:not(:first-child):not(:last-child) .input,.field.has-addons .control:not(:first-child):not(:last-child) .select select{border-radius:0}.field.has-addons .control:first-child:not(:only-child) .button,.field.has-addons .control:first-child:not(:only-child) .input,.field.has-addons .control:first-child:not(:only-child) .select select{border-bottom-right-radius:0;border-top-right-radius:0}.field.has-addons .control:last-child:not(:only-child) .button,.field.has-addons .control:last-child:not(:only-child) .input,.field.has-addons .control:last-child:not(:only-child) .select select{border-bottom-left-radius:0;border-top-left-radius:0}.field.has-addons .control .button:not([disabled]).is-hovered,.field.has-addons .control .button:not([disabled]):hover,.field.has-addons .control .input:not([disabled]).is-hovered,.field.has-addons .control .input:not([disabled]):hover,.field.has-addons .control .select select:not([disabled]).is-hovered,.field.has-addons .control .select select:not([disabled]):hover{z-index:2}.field.has-addons .control .button:not([disabled]).is-active,.field.has-addons .control .button:not([disabled]).is-focused,.field.has-addons .control .button:not([disabled]):active,.field.has-addons .control .button:not([disabled]):focus,.field.has-addons .control .input:not([disabled]).is-active,.field.has-addons .control .input:not([disabled]).is-focused,.field.has-addons .control .input:not([disabled]):active,.field.has-addons .control .input:not([disabled]):focus,.field.has-addons .control .select select:not([disabled]).is-active,.field.has-addons .control .select select:not([disabled]).is-focused,.field.has-addons .control .select select:not([disabled]):active,.field.has-addons .control .select select:not([disabled]):focus{z-index:3}.field.has-addons .control .button:not([disabled]).is-active:hover,.field.has-addons .control .button:not([disabled]).is-focused:hover,.field.has-addons .control .button:not([disabled]):active:hover,.field.has-addons .control .button:not([disabled]):focus:hover,.field.has-addons .control .input:not([disabled]).is-active:hover,.field.has-addons .control .input:not([disabled]).is-focused:hover,.field.has-addons .control .input:not([disabled]):active:hover,.field.has-addons .control .input:not([disabled]):focus:hover,.field.has-addons .control .select select:not([disabled]).is-active:hover,.field.has-addons .control .select select:not([disabled]).is-focused:hover,.field.has-addons .control .select select:not([disabled]):active:hover,.field.has-addons .control .select select:not([disabled]):focus:hover{z-index:4}.field.has-addons .control.is-expanded{flex-grow:1;flex-shrink:1}.field.has-addons.has-addons-centered{justify-content:center}.field.has-addons.has-addons-right{justify-content:flex-end}.field.has-addons.has-addons-fullwidth .control{flex-grow:1;flex-shrink:0}.field.is-grouped{display:flex;justify-content:flex-start}.field.is-grouped>.control{flex-shrink:0}.field.is-grouped>.control:not(:last-child){margin-bottom:0;margin-right:.75rem}.field.is-grouped>.control.is-expanded{flex-grow:1;flex-shrink:1}.field.is-grouped.is-grouped-centered{justify-content:center}.field.is-grouped.is-grouped-right{justify-content:flex-end}.field.is-grouped.is-grouped-multiline{flex-wrap:wrap}.field.is-grouped.is-grouped-multiline>.control:last-child,.field.is-grouped.is-grouped-multiline>.control:not(:last-child){margin-bottom:.75rem}.field.is-grouped.is-grouped-multiline:last-child{margin-bottom:-.75rem}.field.is-grouped.is-grouped-multiline:not(:last-child){margin-bottom:0}@media screen and (min-width:769px),print{.field.is-horizontal{display:flex}}.field-label .label{font-size:inherit}@media screen and (max-width:768px){.field-label{margin-bottom:.5rem}}@media screen and (min-width:769px),print{.field-label{flex-basis:0;flex-grow:1;flex-shrink:0;margin-right:1.5rem;text-align:right}.field-label.is-small{font-size:.75rem;padding-top:.375em}.field-label.is-normal{padding-top:.375em}.field-label.is-medium{font-size:1.25rem;padding-top:.375em}.field-label.is-large{font-size:1.5rem;padding-top:.375em}}.field-body .field .field{margin-bottom:0}@media screen and (min-width:769px),print{.field-body{display:flex;flex-basis:0;flex-grow:5;flex-shrink:1}.field-body .field{margin-bottom:0}.field-body>.field{flex-shrink:1}.field-body>.field:not(.is-narrow){flex-grow:1}.field-body>.field:not(:last-child){margin-right:.75rem}}.control{box-sizing:border-box;clear:both;font-size:1rem;position:relative;text-align:inherit}.control.has-icons-left .input:focus~.icon,.control.has-icons-left .select:focus~.icon,.control.has-icons-right .input:focus~.icon,.control.has-icons-right .select:focus~.icon{color:#4a4a4a}.control.has-icons-left .input.is-small~.icon,.control.has-icons-left .select.is-small~.icon,.control.has-icons-right .input.is-small~.icon,.control.has-icons-right .select.is-small~.icon{font-size:.75rem}.control.has-icons-left .input.is-medium~.icon,.control.has-icons-left .select.is-medium~.icon,.control.has-icons-right .input.is-medium~.icon,.control.has-icons-right .select.is-medium~.icon{font-size:1.25rem}.control.has-icons-left .input.is-large~.icon,.control.has-icons-left .select.is-large~.icon,.control.has-icons-right .input.is-large~.icon,.control.has-icons-right .select.is-large~.icon{font-size:1.5rem}.control.has-icons-left .icon,.control.has-icons-right .icon{color:#dbdbdb;height:2.5em;pointer-events:none;position:absolute;top:0;width:2.5em;z-index:4}.control.has-icons-left .input,.control.has-icons-left .select select{padding-left:2.5em}.control.has-icons-left .icon.is-left{left:0}.control.has-icons-right .input,.control.has-icons-right .select select{padding-right:2.5em}.control.has-icons-right .icon.is-right{right:0}.control.is-loading::after{position:absolute!important;right:.625em;top:.625em;z-index:4}.control.is-loading.is-small:after{font-size:.75rem}.control.is-loading.is-medium:after{font-size:1.25rem}.control.is-loading.is-large:after{font-size:1.5rem}.breadcrumb{font-size:1rem;white-space:nowrap}.breadcrumb a{align-items:center;color:#485fc7;display:flex;justify-content:center;padding:0 .75em}.breadcrumb a:hover{color:#363636}.breadcrumb li{align-items:center;display:flex}.breadcrumb li:first-child a{padding-left:0}.breadcrumb li.is-active a{color:#363636;cursor:default;pointer-events:none}.breadcrumb li+li::before{color:#b5b5b5;content:"\0002f"}.breadcrumb ol,.breadcrumb ul{align-items:flex-start;display:flex;flex-wrap:wrap;justify-content:flex-start}.breadcrumb .icon:first-child{margin-right:.5em}.breadcrumb .icon:last-child{margin-left:.5em}.breadcrumb.is-centered ol,.breadcrumb.is-centered ul{justify-content:center}.breadcrumb.is-right ol,.breadcrumb.is-right ul{justify-content:flex-end}.breadcrumb.is-small{font-size:.75rem}.breadcrumb.is-medium{font-size:1.25rem}.breadcrumb.is-large{font-size:1.5rem}.breadcrumb.has-arrow-separator li+li::before{content:"\02192"}.breadcrumb.has-bullet-separator li+li::before{content:"\02022"}.breadcrumb.has-dot-separator li+li::before{content:"\000b7"}.breadcrumb.has-succeeds-separator li+li::before{content:"\0227B"}.card{background-color:#fff;border-radius:.25rem;box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.02);color:#4a4a4a;max-width:100%;position:relative}.card-content:first-child,.card-footer:first-child,.card-header:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card-content:last-child,.card-footer:last-child,.card-header:last-child{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}.card-header{background-color:transparent;align-items:stretch;box-shadow:0 .125em .25em rgba(10,10,10,.1);display:flex}.card-header-title{align-items:center;color:#363636;display:flex;flex-grow:1;font-weight:700;padding:.75rem 1rem}.card-header-title.is-centered{justify-content:center}.card-header-icon{-moz-appearance:none;-webkit-appearance:none;appearance:none;background:0 0;border:none;color:currentColor;font-family:inherit;font-size:1em;margin:0;padding:0;align-items:center;cursor:pointer;display:flex;justify-content:center;padding:.75rem 1rem}.card-image{display:block;position:relative}.card-image:first-child img{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.card-image:last-child img{border-bottom-left-radius:.25rem;border-bottom-right-radius:.25rem}.card-content{background-color:transparent;padding:1.5rem}.card-footer{background-color:transparent;border-top:1px solid #ededed;align-items:stretch;display:flex}.card-footer-item{align-items:center;display:flex;flex-basis:0;flex-grow:1;flex-shrink:0;justify-content:center;padding:.75rem}.card-footer-item:not(:last-child){border-right:1px solid #ededed}.card .media:not(:last-child){margin-bottom:1.5rem}.dropdown{display:inline-flex;position:relative;vertical-align:top}.dropdown.is-active .dropdown-menu,.dropdown.is-hoverable:hover .dropdown-menu{display:block}.dropdown.is-right .dropdown-menu{left:auto;right:0}.dropdown.is-up .dropdown-menu{bottom:100%;padding-bottom:4px;padding-top:initial;top:auto}.dropdown-menu{display:none;left:0;min-width:12rem;padding-top:4px;position:absolute;top:100%;z-index:20}.dropdown-content{background-color:#fff;border-radius:4px;box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.02);padding-bottom:.5rem;padding-top:.5rem}.dropdown-item{color:#4a4a4a;display:block;font-size:.875rem;line-height:1.5;padding:.375rem 1rem;position:relative}a.dropdown-item,button.dropdown-item{padding-right:3rem;text-align:inherit;white-space:nowrap;width:100%}a.dropdown-item:hover,button.dropdown-item:hover{background-color:#f5f5f5;color:#0a0a0a}a.dropdown-item.is-active,button.dropdown-item.is-active{background-color:#485fc7;color:#fff}.dropdown-divider{background-color:#ededed;border:none;display:block;height:1px;margin:.5rem 0}.level{align-items:center;justify-content:space-between}.level code{border-radius:4px}.level img{display:inline-block;vertical-align:top}.level.is-mobile{display:flex}.level.is-mobile .level-left,.level.is-mobile .level-right{display:flex}.level.is-mobile .level-left+.level-right{margin-top:0}.level.is-mobile .level-item:not(:last-child){margin-bottom:0;margin-right:.75rem}.level.is-mobile .level-item:not(.is-narrow){flex-grow:1}@media screen and (min-width:769px),print{.level{display:flex}.level>.level-item:not(.is-narrow){flex-grow:1}}.level-item{align-items:center;display:flex;flex-basis:auto;flex-grow:0;flex-shrink:0;justify-content:center}.level-item .subtitle,.level-item .title{margin-bottom:0}@media screen and (max-width:768px){.level-item:not(:last-child){margin-bottom:.75rem}}.level-left,.level-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.level-left .level-item.is-flexible,.level-right .level-item.is-flexible{flex-grow:1}@media screen and (min-width:769px),print{.level-left .level-item:not(:last-child),.level-right .level-item:not(:last-child){margin-right:.75rem}}.level-left{align-items:center;justify-content:flex-start}@media screen and (max-width:768px){.level-left+.level-right{margin-top:1.5rem}}@media screen and (min-width:769px),print{.level-left{display:flex}}.level-right{align-items:center;justify-content:flex-end}@media screen and (min-width:769px),print{.level-right{display:flex}}.media{align-items:flex-start;display:flex;text-align:inherit}.media .content:not(:last-child){margin-bottom:.75rem}.media .media{border-top:1px solid rgba(219,219,219,.5);display:flex;padding-top:.75rem}.media .media .content:not(:last-child),.media .media .control:not(:last-child){margin-bottom:.5rem}.media .media .media{padding-top:.5rem}.media .media .media+.media{margin-top:.5rem}.media+.media{border-top:1px solid rgba(219,219,219,.5);margin-top:1rem;padding-top:1rem}.media.is-large+.media{margin-top:1.5rem;padding-top:1.5rem}.media-left,.media-right{flex-basis:auto;flex-grow:0;flex-shrink:0}.media-left{margin-right:1rem}.media-right{margin-left:1rem}.media-content{flex-basis:auto;flex-grow:1;flex-shrink:1;text-align:inherit}@media screen and (max-width:768px){.media-content{overflow-x:auto}}.menu{font-size:1rem}.menu.is-small{font-size:.75rem}.menu.is-medium{font-size:1.25rem}.menu.is-large{font-size:1.5rem}.menu-list{line-height:1.25}.menu-list a{border-radius:2px;color:#4a4a4a;display:block;padding:.5em .75em}.menu-list a:hover{background-color:#f5f5f5;color:#363636}.menu-list a.is-active{background-color:#485fc7;color:#fff}.menu-list li ul{border-left:1px solid #dbdbdb;margin:.75em;padding-left:.75em}.menu-label{color:#7a7a7a;font-size:.75em;letter-spacing:.1em;text-transform:uppercase}.menu-label:not(:first-child){margin-top:1em}.menu-label:not(:last-child){margin-bottom:1em}.message{background-color:#f5f5f5;border-radius:4px;font-size:1rem}.message strong{color:currentColor}.message a:not(.button):not(.tag):not(.dropdown-item){color:currentColor;text-decoration:underline}.message.is-small{font-size:.75rem}.message.is-medium{font-size:1.25rem}.message.is-large{font-size:1.5rem}.message.is-white{background-color:#fff}.message.is-white .message-header{background-color:#fff;color:#0a0a0a}.message.is-white .message-body{border-color:#fff}.message.is-black{background-color:#fafafa}.message.is-black .message-header{background-color:#0a0a0a;color:#fff}.message.is-black .message-body{border-color:#0a0a0a}.message.is-light{background-color:#fafafa}.message.is-light .message-header{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.message.is-light .message-body{border-color:#f5f5f5}.message.is-dark{background-color:#fafafa}.message.is-dark .message-header{background-color:#363636;color:#fff}.message.is-dark .message-body{border-color:#363636}.message.is-primary{background-color:#ebfffc}.message.is-primary .message-header{background-color:#00d1b2;color:#fff}.message.is-primary .message-body{border-color:#00d1b2;color:#00947e}.message.is-link{background-color:#eff1fa}.message.is-link .message-header{background-color:#485fc7;color:#fff}.message.is-link .message-body{border-color:#485fc7;color:#3850b7}.message.is-info{background-color:#eff5fb}.message.is-info .message-header{background-color:#3e8ed0;color:#fff}.message.is-info .message-body{border-color:#3e8ed0;color:#296fa8}.message.is-success{background-color:#effaf5}.message.is-success .message-header{background-color:#48c78e;color:#fff}.message.is-success .message-body{border-color:#48c78e;color:#257953}.message.is-warning{background-color:#fffaeb}.message.is-warning .message-header{background-color:#ffe08a;color:rgba(0,0,0,.7)}.message.is-warning .message-body{border-color:#ffe08a;color:#946c00}.message.is-danger{background-color:#feecf0}.message.is-danger .message-header{background-color:#f14668;color:#fff}.message.is-danger .message-body{border-color:#f14668;color:#cc0f35}.message-header{align-items:center;background-color:#4a4a4a;border-radius:4px 4px 0 0;color:#fff;display:flex;font-weight:700;justify-content:space-between;line-height:1.25;padding:.75em 1em;position:relative}.message-header .delete{flex-grow:0;flex-shrink:0;margin-left:.75em}.message-header+.message-body{border-width:0;border-top-left-radius:0;border-top-right-radius:0}.message-body{border-color:#dbdbdb;border-radius:4px;border-style:solid;border-width:0 0 0 4px;color:#4a4a4a;padding:1.25em 1.5em}.message-body code,.message-body pre{background-color:#fff}.message-body pre code{background-color:transparent}.modal{align-items:center;display:none;flex-direction:column;justify-content:center;overflow:hidden;position:fixed;z-index:40}.modal.is-active{display:flex}.modal-background{background-color:rgba(10,10,10,.86)}.modal-card,.modal-content{margin:0 20px;max-height:calc(100vh - 160px);overflow:auto;position:relative;width:100%}@media screen and (min-width:769px){.modal-card,.modal-content{margin:0 auto;max-height:calc(100vh - 40px);width:640px}}.modal-close{background:0 0;height:40px;position:fixed;right:20px;top:20px;width:40px}.modal-card{display:flex;flex-direction:column;max-height:calc(100vh - 40px);overflow:hidden;-ms-overflow-y:visible}.modal-card-foot,.modal-card-head{align-items:center;background-color:#f5f5f5;display:flex;flex-shrink:0;justify-content:flex-start;padding:20px;position:relative}.modal-card-head{border-bottom:1px solid #dbdbdb;border-top-left-radius:6px;border-top-right-radius:6px}.modal-card-title{color:#363636;flex-grow:1;flex-shrink:0;font-size:1.5rem;line-height:1}.modal-card-foot{border-bottom-left-radius:6px;border-bottom-right-radius:6px;border-top:1px solid #dbdbdb}.modal-card-foot .button:not(:last-child){margin-right:.5em}.modal-card-body{-webkit-overflow-scrolling:touch;background-color:#fff;flex-grow:1;flex-shrink:1;overflow:auto;padding:20px}.navbar{background-color:#fff;min-height:3.25rem;position:relative;z-index:30}.navbar.is-white{background-color:#fff;color:#0a0a0a}.navbar.is-white .navbar-brand .navbar-link,.navbar.is-white .navbar-brand>.navbar-item{color:#0a0a0a}.navbar.is-white .navbar-brand .navbar-link.is-active,.navbar.is-white .navbar-brand .navbar-link:focus,.navbar.is-white .navbar-brand .navbar-link:hover,.navbar.is-white .navbar-brand>a.navbar-item.is-active,.navbar.is-white .navbar-brand>a.navbar-item:focus,.navbar.is-white .navbar-brand>a.navbar-item:hover{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-brand .navbar-link::after{border-color:#0a0a0a}.navbar.is-white .navbar-burger{color:#0a0a0a}@media screen and (min-width:1024px){.navbar.is-white .navbar-end .navbar-link,.navbar.is-white .navbar-end>.navbar-item,.navbar.is-white .navbar-start .navbar-link,.navbar.is-white .navbar-start>.navbar-item{color:#0a0a0a}.navbar.is-white .navbar-end .navbar-link.is-active,.navbar.is-white .navbar-end .navbar-link:focus,.navbar.is-white .navbar-end .navbar-link:hover,.navbar.is-white .navbar-end>a.navbar-item.is-active,.navbar.is-white .navbar-end>a.navbar-item:focus,.navbar.is-white .navbar-end>a.navbar-item:hover,.navbar.is-white .navbar-start .navbar-link.is-active,.navbar.is-white .navbar-start .navbar-link:focus,.navbar.is-white .navbar-start .navbar-link:hover,.navbar.is-white .navbar-start>a.navbar-item.is-active,.navbar.is-white .navbar-start>a.navbar-item:focus,.navbar.is-white .navbar-start>a.navbar-item:hover{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-end .navbar-link::after,.navbar.is-white .navbar-start .navbar-link::after{border-color:#0a0a0a}.navbar.is-white .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-white .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-white .navbar-item.has-dropdown:hover .navbar-link{background-color:#f2f2f2;color:#0a0a0a}.navbar.is-white .navbar-dropdown a.navbar-item.is-active{background-color:#fff;color:#0a0a0a}}.navbar.is-black{background-color:#0a0a0a;color:#fff}.navbar.is-black .navbar-brand .navbar-link,.navbar.is-black .navbar-brand>.navbar-item{color:#fff}.navbar.is-black .navbar-brand .navbar-link.is-active,.navbar.is-black .navbar-brand .navbar-link:focus,.navbar.is-black .navbar-brand .navbar-link:hover,.navbar.is-black .navbar-brand>a.navbar-item.is-active,.navbar.is-black .navbar-brand>a.navbar-item:focus,.navbar.is-black .navbar-brand>a.navbar-item:hover{background-color:#000;color:#fff}.navbar.is-black .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-black .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-black .navbar-end .navbar-link,.navbar.is-black .navbar-end>.navbar-item,.navbar.is-black .navbar-start .navbar-link,.navbar.is-black .navbar-start>.navbar-item{color:#fff}.navbar.is-black .navbar-end .navbar-link.is-active,.navbar.is-black .navbar-end .navbar-link:focus,.navbar.is-black .navbar-end .navbar-link:hover,.navbar.is-black .navbar-end>a.navbar-item.is-active,.navbar.is-black .navbar-end>a.navbar-item:focus,.navbar.is-black .navbar-end>a.navbar-item:hover,.navbar.is-black .navbar-start .navbar-link.is-active,.navbar.is-black .navbar-start .navbar-link:focus,.navbar.is-black .navbar-start .navbar-link:hover,.navbar.is-black .navbar-start>a.navbar-item.is-active,.navbar.is-black .navbar-start>a.navbar-item:focus,.navbar.is-black .navbar-start>a.navbar-item:hover{background-color:#000;color:#fff}.navbar.is-black .navbar-end .navbar-link::after,.navbar.is-black .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-black .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-black .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-black .navbar-item.has-dropdown:hover .navbar-link{background-color:#000;color:#fff}.navbar.is-black .navbar-dropdown a.navbar-item.is-active{background-color:#0a0a0a;color:#fff}}.navbar.is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-brand .navbar-link,.navbar.is-light .navbar-brand>.navbar-item{color:rgba(0,0,0,.7)}.navbar.is-light .navbar-brand .navbar-link.is-active,.navbar.is-light .navbar-brand .navbar-link:focus,.navbar.is-light .navbar-brand .navbar-link:hover,.navbar.is-light .navbar-brand>a.navbar-item.is-active,.navbar.is-light .navbar-brand>a.navbar-item:focus,.navbar.is-light .navbar-brand>a.navbar-item:hover{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-light .navbar-burger{color:rgba(0,0,0,.7)}@media screen and (min-width:1024px){.navbar.is-light .navbar-end .navbar-link,.navbar.is-light .navbar-end>.navbar-item,.navbar.is-light .navbar-start .navbar-link,.navbar.is-light .navbar-start>.navbar-item{color:rgba(0,0,0,.7)}.navbar.is-light .navbar-end .navbar-link.is-active,.navbar.is-light .navbar-end .navbar-link:focus,.navbar.is-light .navbar-end .navbar-link:hover,.navbar.is-light .navbar-end>a.navbar-item.is-active,.navbar.is-light .navbar-end>a.navbar-item:focus,.navbar.is-light .navbar-end>a.navbar-item:hover,.navbar.is-light .navbar-start .navbar-link.is-active,.navbar.is-light .navbar-start .navbar-link:focus,.navbar.is-light .navbar-start .navbar-link:hover,.navbar.is-light .navbar-start>a.navbar-item.is-active,.navbar.is-light .navbar-start>a.navbar-item:focus,.navbar.is-light .navbar-start>a.navbar-item:hover{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-end .navbar-link::after,.navbar.is-light .navbar-start .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-light .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-light .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-light .navbar-item.has-dropdown:hover .navbar-link{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.navbar.is-light .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:rgba(0,0,0,.7)}}.navbar.is-dark{background-color:#363636;color:#fff}.navbar.is-dark .navbar-brand .navbar-link,.navbar.is-dark .navbar-brand>.navbar-item{color:#fff}.navbar.is-dark .navbar-brand .navbar-link.is-active,.navbar.is-dark .navbar-brand .navbar-link:focus,.navbar.is-dark .navbar-brand .navbar-link:hover,.navbar.is-dark .navbar-brand>a.navbar-item.is-active,.navbar.is-dark .navbar-brand>a.navbar-item:focus,.navbar.is-dark .navbar-brand>a.navbar-item:hover{background-color:#292929;color:#fff}.navbar.is-dark .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-dark .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-dark .navbar-end .navbar-link,.navbar.is-dark .navbar-end>.navbar-item,.navbar.is-dark .navbar-start .navbar-link,.navbar.is-dark .navbar-start>.navbar-item{color:#fff}.navbar.is-dark .navbar-end .navbar-link.is-active,.navbar.is-dark .navbar-end .navbar-link:focus,.navbar.is-dark .navbar-end .navbar-link:hover,.navbar.is-dark .navbar-end>a.navbar-item.is-active,.navbar.is-dark .navbar-end>a.navbar-item:focus,.navbar.is-dark .navbar-end>a.navbar-item:hover,.navbar.is-dark .navbar-start .navbar-link.is-active,.navbar.is-dark .navbar-start .navbar-link:focus,.navbar.is-dark .navbar-start .navbar-link:hover,.navbar.is-dark .navbar-start>a.navbar-item.is-active,.navbar.is-dark .navbar-start>a.navbar-item:focus,.navbar.is-dark .navbar-start>a.navbar-item:hover{background-color:#292929;color:#fff}.navbar.is-dark .navbar-end .navbar-link::after,.navbar.is-dark .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-dark .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-dark .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-dark .navbar-item.has-dropdown:hover .navbar-link{background-color:#292929;color:#fff}.navbar.is-dark .navbar-dropdown a.navbar-item.is-active{background-color:#363636;color:#fff}}.navbar.is-primary{background-color:#00d1b2;color:#fff}.navbar.is-primary .navbar-brand .navbar-link,.navbar.is-primary .navbar-brand>.navbar-item{color:#fff}.navbar.is-primary .navbar-brand .navbar-link.is-active,.navbar.is-primary .navbar-brand .navbar-link:focus,.navbar.is-primary .navbar-brand .navbar-link:hover,.navbar.is-primary .navbar-brand>a.navbar-item.is-active,.navbar.is-primary .navbar-brand>a.navbar-item:focus,.navbar.is-primary .navbar-brand>a.navbar-item:hover{background-color:#00b89c;color:#fff}.navbar.is-primary .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-primary .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-primary .navbar-end .navbar-link,.navbar.is-primary .navbar-end>.navbar-item,.navbar.is-primary .navbar-start .navbar-link,.navbar.is-primary .navbar-start>.navbar-item{color:#fff}.navbar.is-primary .navbar-end .navbar-link.is-active,.navbar.is-primary .navbar-end .navbar-link:focus,.navbar.is-primary .navbar-end .navbar-link:hover,.navbar.is-primary .navbar-end>a.navbar-item.is-active,.navbar.is-primary .navbar-end>a.navbar-item:focus,.navbar.is-primary .navbar-end>a.navbar-item:hover,.navbar.is-primary .navbar-start .navbar-link.is-active,.navbar.is-primary .navbar-start .navbar-link:focus,.navbar.is-primary .navbar-start .navbar-link:hover,.navbar.is-primary .navbar-start>a.navbar-item.is-active,.navbar.is-primary .navbar-start>a.navbar-item:focus,.navbar.is-primary .navbar-start>a.navbar-item:hover{background-color:#00b89c;color:#fff}.navbar.is-primary .navbar-end .navbar-link::after,.navbar.is-primary .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-primary .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-primary .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-primary .navbar-item.has-dropdown:hover .navbar-link{background-color:#00b89c;color:#fff}.navbar.is-primary .navbar-dropdown a.navbar-item.is-active{background-color:#00d1b2;color:#fff}}.navbar.is-link{background-color:#485fc7;color:#fff}.navbar.is-link .navbar-brand .navbar-link,.navbar.is-link .navbar-brand>.navbar-item{color:#fff}.navbar.is-link .navbar-brand .navbar-link.is-active,.navbar.is-link .navbar-brand .navbar-link:focus,.navbar.is-link .navbar-brand .navbar-link:hover,.navbar.is-link .navbar-brand>a.navbar-item.is-active,.navbar.is-link .navbar-brand>a.navbar-item:focus,.navbar.is-link .navbar-brand>a.navbar-item:hover{background-color:#3a51bb;color:#fff}.navbar.is-link .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-link .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-link .navbar-end .navbar-link,.navbar.is-link .navbar-end>.navbar-item,.navbar.is-link .navbar-start .navbar-link,.navbar.is-link .navbar-start>.navbar-item{color:#fff}.navbar.is-link .navbar-end .navbar-link.is-active,.navbar.is-link .navbar-end .navbar-link:focus,.navbar.is-link .navbar-end .navbar-link:hover,.navbar.is-link .navbar-end>a.navbar-item.is-active,.navbar.is-link .navbar-end>a.navbar-item:focus,.navbar.is-link .navbar-end>a.navbar-item:hover,.navbar.is-link .navbar-start .navbar-link.is-active,.navbar.is-link .navbar-start .navbar-link:focus,.navbar.is-link .navbar-start .navbar-link:hover,.navbar.is-link .navbar-start>a.navbar-item.is-active,.navbar.is-link .navbar-start>a.navbar-item:focus,.navbar.is-link .navbar-start>a.navbar-item:hover{background-color:#3a51bb;color:#fff}.navbar.is-link .navbar-end .navbar-link::after,.navbar.is-link .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-link .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-link .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-link .navbar-item.has-dropdown:hover .navbar-link{background-color:#3a51bb;color:#fff}.navbar.is-link .navbar-dropdown a.navbar-item.is-active{background-color:#485fc7;color:#fff}}.navbar.is-info{background-color:#3e8ed0;color:#fff}.navbar.is-info .navbar-brand .navbar-link,.navbar.is-info .navbar-brand>.navbar-item{color:#fff}.navbar.is-info .navbar-brand .navbar-link.is-active,.navbar.is-info .navbar-brand .navbar-link:focus,.navbar.is-info .navbar-brand .navbar-link:hover,.navbar.is-info .navbar-brand>a.navbar-item.is-active,.navbar.is-info .navbar-brand>a.navbar-item:focus,.navbar.is-info .navbar-brand>a.navbar-item:hover{background-color:#3082c5;color:#fff}.navbar.is-info .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-info .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-info .navbar-end .navbar-link,.navbar.is-info .navbar-end>.navbar-item,.navbar.is-info .navbar-start .navbar-link,.navbar.is-info .navbar-start>.navbar-item{color:#fff}.navbar.is-info .navbar-end .navbar-link.is-active,.navbar.is-info .navbar-end .navbar-link:focus,.navbar.is-info .navbar-end .navbar-link:hover,.navbar.is-info .navbar-end>a.navbar-item.is-active,.navbar.is-info .navbar-end>a.navbar-item:focus,.navbar.is-info .navbar-end>a.navbar-item:hover,.navbar.is-info .navbar-start .navbar-link.is-active,.navbar.is-info .navbar-start .navbar-link:focus,.navbar.is-info .navbar-start .navbar-link:hover,.navbar.is-info .navbar-start>a.navbar-item.is-active,.navbar.is-info .navbar-start>a.navbar-item:focus,.navbar.is-info .navbar-start>a.navbar-item:hover{background-color:#3082c5;color:#fff}.navbar.is-info .navbar-end .navbar-link::after,.navbar.is-info .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-info .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-info .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-info .navbar-item.has-dropdown:hover .navbar-link{background-color:#3082c5;color:#fff}.navbar.is-info .navbar-dropdown a.navbar-item.is-active{background-color:#3e8ed0;color:#fff}}.navbar.is-success{background-color:#48c78e;color:#fff}.navbar.is-success .navbar-brand .navbar-link,.navbar.is-success .navbar-brand>.navbar-item{color:#fff}.navbar.is-success .navbar-brand .navbar-link.is-active,.navbar.is-success .navbar-brand .navbar-link:focus,.navbar.is-success .navbar-brand .navbar-link:hover,.navbar.is-success .navbar-brand>a.navbar-item.is-active,.navbar.is-success .navbar-brand>a.navbar-item:focus,.navbar.is-success .navbar-brand>a.navbar-item:hover{background-color:#3abb81;color:#fff}.navbar.is-success .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-success .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-success .navbar-end .navbar-link,.navbar.is-success .navbar-end>.navbar-item,.navbar.is-success .navbar-start .navbar-link,.navbar.is-success .navbar-start>.navbar-item{color:#fff}.navbar.is-success .navbar-end .navbar-link.is-active,.navbar.is-success .navbar-end .navbar-link:focus,.navbar.is-success .navbar-end .navbar-link:hover,.navbar.is-success .navbar-end>a.navbar-item.is-active,.navbar.is-success .navbar-end>a.navbar-item:focus,.navbar.is-success .navbar-end>a.navbar-item:hover,.navbar.is-success .navbar-start .navbar-link.is-active,.navbar.is-success .navbar-start .navbar-link:focus,.navbar.is-success .navbar-start .navbar-link:hover,.navbar.is-success .navbar-start>a.navbar-item.is-active,.navbar.is-success .navbar-start>a.navbar-item:focus,.navbar.is-success .navbar-start>a.navbar-item:hover{background-color:#3abb81;color:#fff}.navbar.is-success .navbar-end .navbar-link::after,.navbar.is-success .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-success .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-success .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-success .navbar-item.has-dropdown:hover .navbar-link{background-color:#3abb81;color:#fff}.navbar.is-success .navbar-dropdown a.navbar-item.is-active{background-color:#48c78e;color:#fff}}.navbar.is-warning{background-color:#ffe08a;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-brand .navbar-link,.navbar.is-warning .navbar-brand>.navbar-item{color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-brand .navbar-link.is-active,.navbar.is-warning .navbar-brand .navbar-link:focus,.navbar.is-warning .navbar-brand .navbar-link:hover,.navbar.is-warning .navbar-brand>a.navbar-item.is-active,.navbar.is-warning .navbar-brand>a.navbar-item:focus,.navbar.is-warning .navbar-brand>a.navbar-item:hover{background-color:#ffd970;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-brand .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-burger{color:rgba(0,0,0,.7)}@media screen and (min-width:1024px){.navbar.is-warning .navbar-end .navbar-link,.navbar.is-warning .navbar-end>.navbar-item,.navbar.is-warning .navbar-start .navbar-link,.navbar.is-warning .navbar-start>.navbar-item{color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-end .navbar-link.is-active,.navbar.is-warning .navbar-end .navbar-link:focus,.navbar.is-warning .navbar-end .navbar-link:hover,.navbar.is-warning .navbar-end>a.navbar-item.is-active,.navbar.is-warning .navbar-end>a.navbar-item:focus,.navbar.is-warning .navbar-end>a.navbar-item:hover,.navbar.is-warning .navbar-start .navbar-link.is-active,.navbar.is-warning .navbar-start .navbar-link:focus,.navbar.is-warning .navbar-start .navbar-link:hover,.navbar.is-warning .navbar-start>a.navbar-item.is-active,.navbar.is-warning .navbar-start>a.navbar-item:focus,.navbar.is-warning .navbar-start>a.navbar-item:hover{background-color:#ffd970;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-end .navbar-link::after,.navbar.is-warning .navbar-start .navbar-link::after{border-color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-warning .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-warning .navbar-item.has-dropdown:hover .navbar-link{background-color:#ffd970;color:rgba(0,0,0,.7)}.navbar.is-warning .navbar-dropdown a.navbar-item.is-active{background-color:#ffe08a;color:rgba(0,0,0,.7)}}.navbar.is-danger{background-color:#f14668;color:#fff}.navbar.is-danger .navbar-brand .navbar-link,.navbar.is-danger .navbar-brand>.navbar-item{color:#fff}.navbar.is-danger .navbar-brand .navbar-link.is-active,.navbar.is-danger .navbar-brand .navbar-link:focus,.navbar.is-danger .navbar-brand .navbar-link:hover,.navbar.is-danger .navbar-brand>a.navbar-item.is-active,.navbar.is-danger .navbar-brand>a.navbar-item:focus,.navbar.is-danger .navbar-brand>a.navbar-item:hover{background-color:#ef2e55;color:#fff}.navbar.is-danger .navbar-brand .navbar-link::after{border-color:#fff}.navbar.is-danger .navbar-burger{color:#fff}@media screen and (min-width:1024px){.navbar.is-danger .navbar-end .navbar-link,.navbar.is-danger .navbar-end>.navbar-item,.navbar.is-danger .navbar-start .navbar-link,.navbar.is-danger .navbar-start>.navbar-item{color:#fff}.navbar.is-danger .navbar-end .navbar-link.is-active,.navbar.is-danger .navbar-end .navbar-link:focus,.navbar.is-danger .navbar-end .navbar-link:hover,.navbar.is-danger .navbar-end>a.navbar-item.is-active,.navbar.is-danger .navbar-end>a.navbar-item:focus,.navbar.is-danger .navbar-end>a.navbar-item:hover,.navbar.is-danger .navbar-start .navbar-link.is-active,.navbar.is-danger .navbar-start .navbar-link:focus,.navbar.is-danger .navbar-start .navbar-link:hover,.navbar.is-danger .navbar-start>a.navbar-item.is-active,.navbar.is-danger .navbar-start>a.navbar-item:focus,.navbar.is-danger .navbar-start>a.navbar-item:hover{background-color:#ef2e55;color:#fff}.navbar.is-danger .navbar-end .navbar-link::after,.navbar.is-danger .navbar-start .navbar-link::after{border-color:#fff}.navbar.is-danger .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-danger .navbar-item.has-dropdown:focus .navbar-link,.navbar.is-danger .navbar-item.has-dropdown:hover .navbar-link{background-color:#ef2e55;color:#fff}.navbar.is-danger .navbar-dropdown a.navbar-item.is-active{background-color:#f14668;color:#fff}}.navbar>.container{align-items:stretch;display:flex;min-height:3.25rem;width:100%}.navbar.has-shadow{box-shadow:0 2px 0 0 #f5f5f5}.navbar.is-fixed-bottom,.navbar.is-fixed-top{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom{bottom:0}.navbar.is-fixed-bottom.has-shadow{box-shadow:0 -2px 0 0 #f5f5f5}.navbar.is-fixed-top{top:0}body.has-navbar-fixed-top,html.has-navbar-fixed-top{padding-top:3.25rem}body.has-navbar-fixed-bottom,html.has-navbar-fixed-bottom{padding-bottom:3.25rem}.navbar-brand,.navbar-tabs{align-items:stretch;display:flex;flex-shrink:0;min-height:3.25rem}.navbar-brand a.navbar-item:focus,.navbar-brand a.navbar-item:hover{background-color:transparent}.navbar-tabs{-webkit-overflow-scrolling:touch;max-width:100vw;overflow-x:auto;overflow-y:hidden}.navbar-burger{color:#4a4a4a;cursor:pointer;display:block;height:3.25rem;position:relative;width:3.25rem;margin-left:auto}.navbar-burger span{background-color:currentColor;display:block;height:1px;left:calc(50% - 8px);position:absolute;transform-origin:center;transition-duration:86ms;transition-property:background-color,opacity,transform;transition-timing-function:ease-out;width:16px}.navbar-burger span:nth-child(1){top:calc(50% - 6px)}.navbar-burger span:nth-child(2){top:calc(50% - 1px)}.navbar-burger span:nth-child(3){top:calc(50% + 4px)}.navbar-burger:hover{background-color:rgba(0,0,0,.05)}.navbar-burger.is-active span:nth-child(1){transform:translateY(5px) rotate(45deg)}.navbar-burger.is-active span:nth-child(2){opacity:0}.navbar-burger.is-active span:nth-child(3){transform:translateY(-5px) rotate(-45deg)}.navbar-menu{display:none}.navbar-item,.navbar-link{color:#4a4a4a;display:block;line-height:1.5;padding:.5rem .75rem;position:relative}.navbar-item .icon:only-child,.navbar-link .icon:only-child{margin-left:-.25rem;margin-right:-.25rem}.navbar-link,a.navbar-item{cursor:pointer}.navbar-link.is-active,.navbar-link:focus,.navbar-link:focus-within,.navbar-link:hover,a.navbar-item.is-active,a.navbar-item:focus,a.navbar-item:focus-within,a.navbar-item:hover{background-color:#fafafa;color:#485fc7}.navbar-item{flex-grow:0;flex-shrink:0}.navbar-item img{max-height:1.75rem}.navbar-item.has-dropdown{padding:0}.navbar-item.is-expanded{flex-grow:1;flex-shrink:1}.navbar-item.is-tab{border-bottom:1px solid transparent;min-height:3.25rem;padding-bottom:calc(.5rem - 1px)}.navbar-item.is-tab:focus,.navbar-item.is-tab:hover{background-color:transparent;border-bottom-color:#485fc7}.navbar-item.is-tab.is-active{background-color:transparent;border-bottom-color:#485fc7;border-bottom-style:solid;border-bottom-width:3px;color:#485fc7;padding-bottom:calc(.5rem - 3px)}.navbar-content{flex-grow:1;flex-shrink:1}.navbar-link:not(.is-arrowless){padding-right:2.5em}.navbar-link:not(.is-arrowless)::after{border-color:#485fc7;margin-top:-.375em;right:1.125em}.navbar-dropdown{font-size:.875rem;padding-bottom:.5rem;padding-top:.5rem}.navbar-dropdown .navbar-item{padding-left:1.5rem;padding-right:1.5rem}.navbar-divider{background-color:#f5f5f5;border:none;display:none;height:2px;margin:.5rem 0}@media screen and (max-width:1023px){.navbar>.container{display:block}.navbar-brand .navbar-item,.navbar-tabs .navbar-item{align-items:center;display:flex}.navbar-link::after{display:none}.navbar-menu{background-color:#fff;box-shadow:0 8px 16px rgba(10,10,10,.1);padding:.5rem 0}.navbar-menu.is-active{display:block}.navbar.is-fixed-bottom-touch,.navbar.is-fixed-top-touch{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom-touch{bottom:0}.navbar.is-fixed-bottom-touch.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,.1)}.navbar.is-fixed-top-touch{top:0}.navbar.is-fixed-top .navbar-menu,.navbar.is-fixed-top-touch .navbar-menu{-webkit-overflow-scrolling:touch;max-height:calc(100vh - 3.25rem);overflow:auto}body.has-navbar-fixed-top-touch,html.has-navbar-fixed-top-touch{padding-top:3.25rem}body.has-navbar-fixed-bottom-touch,html.has-navbar-fixed-bottom-touch{padding-bottom:3.25rem}}@media screen and (min-width:1024px){.navbar,.navbar-end,.navbar-menu,.navbar-start{align-items:stretch;display:flex}.navbar{min-height:3.25rem}.navbar.is-spaced{padding:1rem 2rem}.navbar.is-spaced .navbar-end,.navbar.is-spaced .navbar-start{align-items:center}.navbar.is-spaced .navbar-link,.navbar.is-spaced a.navbar-item{border-radius:4px}.navbar.is-transparent .navbar-link.is-active,.navbar.is-transparent .navbar-link:focus,.navbar.is-transparent .navbar-link:hover,.navbar.is-transparent a.navbar-item.is-active,.navbar.is-transparent a.navbar-item:focus,.navbar.is-transparent a.navbar-item:hover{background-color:transparent!important}.navbar.is-transparent .navbar-item.has-dropdown.is-active .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:focus-within .navbar-link,.navbar.is-transparent .navbar-item.has-dropdown.is-hoverable:hover .navbar-link{background-color:transparent!important}.navbar.is-transparent .navbar-dropdown a.navbar-item:focus,.navbar.is-transparent .navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#0a0a0a}.navbar.is-transparent .navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#485fc7}.navbar-burger{display:none}.navbar-item,.navbar-link{align-items:center;display:flex}.navbar-item.has-dropdown{align-items:stretch}.navbar-item.has-dropdown-up .navbar-link::after{transform:rotate(135deg) translate(.25em,-.25em)}.navbar-item.has-dropdown-up .navbar-dropdown{border-bottom:2px solid #dbdbdb;border-radius:6px 6px 0 0;border-top:none;bottom:100%;box-shadow:0 -8px 8px rgba(10,10,10,.1);top:auto}.navbar-item.is-active .navbar-dropdown,.navbar-item.is-hoverable:focus .navbar-dropdown,.navbar-item.is-hoverable:focus-within .navbar-dropdown,.navbar-item.is-hoverable:hover .navbar-dropdown{display:block}.navbar-item.is-active .navbar-dropdown.is-boxed,.navbar-item.is-hoverable:focus .navbar-dropdown.is-boxed,.navbar-item.is-hoverable:focus-within .navbar-dropdown.is-boxed,.navbar-item.is-hoverable:hover .navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-item.is-active .navbar-dropdown,.navbar.is-spaced .navbar-item.is-hoverable:focus .navbar-dropdown,.navbar.is-spaced .navbar-item.is-hoverable:focus-within .navbar-dropdown,.navbar.is-spaced .navbar-item.is-hoverable:hover .navbar-dropdown{opacity:1;pointer-events:auto;transform:translateY(0)}.navbar-menu{flex-grow:1;flex-shrink:0}.navbar-start{justify-content:flex-start;margin-right:auto}.navbar-end{justify-content:flex-end;margin-left:auto}.navbar-dropdown{background-color:#fff;border-bottom-left-radius:6px;border-bottom-right-radius:6px;border-top:2px solid #dbdbdb;box-shadow:0 8px 8px rgba(10,10,10,.1);display:none;font-size:.875rem;left:0;min-width:100%;position:absolute;top:100%;z-index:20}.navbar-dropdown .navbar-item{padding:.375rem 1rem;white-space:nowrap}.navbar-dropdown a.navbar-item{padding-right:3rem}.navbar-dropdown a.navbar-item:focus,.navbar-dropdown a.navbar-item:hover{background-color:#f5f5f5;color:#0a0a0a}.navbar-dropdown a.navbar-item.is-active{background-color:#f5f5f5;color:#485fc7}.navbar-dropdown.is-boxed,.navbar.is-spaced .navbar-dropdown{border-radius:6px;border-top:none;box-shadow:0 8px 8px rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.1);display:block;opacity:0;pointer-events:none;top:calc(100% + (-4px));transform:translateY(-5px);transition-duration:86ms;transition-property:opacity,transform}.navbar-dropdown.is-right{left:auto;right:0}.navbar-divider{display:block}.container>.navbar .navbar-brand,.navbar>.container .navbar-brand{margin-left:-.75rem}.container>.navbar .navbar-menu,.navbar>.container .navbar-menu{margin-right:-.75rem}.navbar.is-fixed-bottom-desktop,.navbar.is-fixed-top-desktop{left:0;position:fixed;right:0;z-index:30}.navbar.is-fixed-bottom-desktop{bottom:0}.navbar.is-fixed-bottom-desktop.has-shadow{box-shadow:0 -2px 3px rgba(10,10,10,.1)}.navbar.is-fixed-top-desktop{top:0}body.has-navbar-fixed-top-desktop,html.has-navbar-fixed-top-desktop{padding-top:3.25rem}body.has-navbar-fixed-bottom-desktop,html.has-navbar-fixed-bottom-desktop{padding-bottom:3.25rem}body.has-spaced-navbar-fixed-top,html.has-spaced-navbar-fixed-top{padding-top:5.25rem}body.has-spaced-navbar-fixed-bottom,html.has-spaced-navbar-fixed-bottom{padding-bottom:5.25rem}.navbar-link.is-active,a.navbar-item.is-active{color:#0a0a0a}.navbar-link.is-active:not(:focus):not(:hover),a.navbar-item.is-active:not(:focus):not(:hover){background-color:transparent}.navbar-item.has-dropdown.is-active .navbar-link,.navbar-item.has-dropdown:focus .navbar-link,.navbar-item.has-dropdown:hover .navbar-link{background-color:#fafafa}}.hero.is-fullheight-with-navbar{min-height:calc(100vh - 3.25rem)}.pagination{font-size:1rem;margin:-.25rem}.pagination.is-small{font-size:.75rem}.pagination.is-medium{font-size:1.25rem}.pagination.is-large{font-size:1.5rem}.pagination.is-rounded .pagination-next,.pagination.is-rounded .pagination-previous{padding-left:1em;padding-right:1em;border-radius:9999px}.pagination.is-rounded .pagination-link{border-radius:9999px}.pagination,.pagination-list{align-items:center;display:flex;justify-content:center;text-align:center}.pagination-ellipsis,.pagination-link,.pagination-next,.pagination-previous{font-size:1em;justify-content:center;margin:.25rem;padding-left:.5em;padding-right:.5em;text-align:center}.pagination-link,.pagination-next,.pagination-previous{border-color:#dbdbdb;color:#363636;min-width:2.5em}.pagination-link:hover,.pagination-next:hover,.pagination-previous:hover{border-color:#b5b5b5;color:#363636}.pagination-link:focus,.pagination-next:focus,.pagination-previous:focus{border-color:#485fc7}.pagination-link:active,.pagination-next:active,.pagination-previous:active{box-shadow:inset 0 1px 2px rgba(10,10,10,.2)}.pagination-link[disabled],.pagination-next[disabled],.pagination-previous[disabled]{background-color:#dbdbdb;border-color:#dbdbdb;box-shadow:none;color:#7a7a7a;opacity:.5}.pagination-next,.pagination-previous{padding-left:.75em;padding-right:.75em;white-space:nowrap}.pagination-link.is-current{background-color:#485fc7;border-color:#485fc7;color:#fff}.pagination-ellipsis{color:#b5b5b5;pointer-events:none}.pagination-list{flex-wrap:wrap}.pagination-list li{list-style:none}@media screen and (max-width:768px){.pagination{flex-wrap:wrap}.pagination-next,.pagination-previous{flex-grow:1;flex-shrink:1}.pagination-list li{flex-grow:1;flex-shrink:1}}@media screen and (min-width:769px),print{.pagination-list{flex-grow:1;flex-shrink:1;justify-content:flex-start;order:1}.pagination-ellipsis,.pagination-link,.pagination-next,.pagination-previous{margin-bottom:0;margin-top:0}.pagination-previous{order:2}.pagination-next{order:3}.pagination{justify-content:space-between;margin-bottom:0;margin-top:0}.pagination.is-centered .pagination-previous{order:1}.pagination.is-centered .pagination-list{justify-content:center;order:2}.pagination.is-centered .pagination-next{order:3}.pagination.is-right .pagination-previous{order:1}.pagination.is-right .pagination-next{order:2}.pagination.is-right .pagination-list{justify-content:flex-end;order:3}}.panel{border-radius:6px;box-shadow:0 .5em 1em -.125em rgba(10,10,10,.1),0 0 0 1px rgba(10,10,10,.02);font-size:1rem}.panel:not(:last-child){margin-bottom:1.5rem}.panel.is-white .panel-heading{background-color:#fff;color:#0a0a0a}.panel.is-white .panel-tabs a.is-active{border-bottom-color:#fff}.panel.is-white .panel-block.is-active .panel-icon{color:#fff}.panel.is-black .panel-heading{background-color:#0a0a0a;color:#fff}.panel.is-black .panel-tabs a.is-active{border-bottom-color:#0a0a0a}.panel.is-black .panel-block.is-active .panel-icon{color:#0a0a0a}.panel.is-light .panel-heading{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.panel.is-light .panel-tabs a.is-active{border-bottom-color:#f5f5f5}.panel.is-light .panel-block.is-active .panel-icon{color:#f5f5f5}.panel.is-dark .panel-heading{background-color:#363636;color:#fff}.panel.is-dark .panel-tabs a.is-active{border-bottom-color:#363636}.panel.is-dark .panel-block.is-active .panel-icon{color:#363636}.panel.is-primary .panel-heading{background-color:#00d1b2;color:#fff}.panel.is-primary .panel-tabs a.is-active{border-bottom-color:#00d1b2}.panel.is-primary .panel-block.is-active .panel-icon{color:#00d1b2}.panel.is-link .panel-heading{background-color:#485fc7;color:#fff}.panel.is-link .panel-tabs a.is-active{border-bottom-color:#485fc7}.panel.is-link .panel-block.is-active .panel-icon{color:#485fc7}.panel.is-info .panel-heading{background-color:#3e8ed0;color:#fff}.panel.is-info .panel-tabs a.is-active{border-bottom-color:#3e8ed0}.panel.is-info .panel-block.is-active .panel-icon{color:#3e8ed0}.panel.is-success .panel-heading{background-color:#48c78e;color:#fff}.panel.is-success .panel-tabs a.is-active{border-bottom-color:#48c78e}.panel.is-success .panel-block.is-active .panel-icon{color:#48c78e}.panel.is-warning .panel-heading{background-color:#ffe08a;color:rgba(0,0,0,.7)}.panel.is-warning .panel-tabs a.is-active{border-bottom-color:#ffe08a}.panel.is-warning .panel-block.is-active .panel-icon{color:#ffe08a}.panel.is-danger .panel-heading{background-color:#f14668;color:#fff}.panel.is-danger .panel-tabs a.is-active{border-bottom-color:#f14668}.panel.is-danger .panel-block.is-active .panel-icon{color:#f14668}.panel-block:not(:last-child),.panel-tabs:not(:last-child){border-bottom:1px solid #ededed}.panel-heading{background-color:#ededed;border-radius:6px 6px 0 0;color:#363636;font-size:1.25em;font-weight:700;line-height:1.25;padding:.75em 1em}.panel-tabs{align-items:flex-end;display:flex;font-size:.875em;justify-content:center}.panel-tabs a{border-bottom:1px solid #dbdbdb;margin-bottom:-1px;padding:.5em}.panel-tabs a.is-active{border-bottom-color:#4a4a4a;color:#363636}.panel-list a{color:#4a4a4a}.panel-list a:hover{color:#485fc7}.panel-block{align-items:center;color:#363636;display:flex;justify-content:flex-start;padding:.5em .75em}.panel-block input[type=checkbox]{margin-right:.75em}.panel-block>.control{flex-grow:1;flex-shrink:1;width:100%}.panel-block.is-wrapped{flex-wrap:wrap}.panel-block.is-active{border-left-color:#485fc7;color:#363636}.panel-block.is-active .panel-icon{color:#485fc7}.panel-block:last-child{border-bottom-left-radius:6px;border-bottom-right-radius:6px}a.panel-block,label.panel-block{cursor:pointer}a.panel-block:hover,label.panel-block:hover{background-color:#f5f5f5}.panel-icon{display:inline-block;font-size:14px;height:1em;line-height:1em;text-align:center;vertical-align:top;width:1em;color:#7a7a7a;margin-right:.75em}.panel-icon .fa{font-size:inherit;line-height:inherit}.tabs{-webkit-overflow-scrolling:touch;align-items:stretch;display:flex;font-size:1rem;justify-content:space-between;overflow:hidden;overflow-x:auto;white-space:nowrap}.tabs a{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;color:#4a4a4a;display:flex;justify-content:center;margin-bottom:-1px;padding:.5em 1em;vertical-align:top}.tabs a:hover{border-bottom-color:#363636;color:#363636}.tabs li{display:block}.tabs li.is-active a{border-bottom-color:#485fc7;color:#485fc7}.tabs ul{align-items:center;border-bottom-color:#dbdbdb;border-bottom-style:solid;border-bottom-width:1px;display:flex;flex-grow:1;flex-shrink:0;justify-content:flex-start}.tabs ul.is-left{padding-right:.75em}.tabs ul.is-center{flex:none;justify-content:center;padding-left:.75em;padding-right:.75em}.tabs ul.is-right{justify-content:flex-end;padding-left:.75em}.tabs .icon:first-child{margin-right:.5em}.tabs .icon:last-child{margin-left:.5em}.tabs.is-centered ul{justify-content:center}.tabs.is-right ul{justify-content:flex-end}.tabs.is-boxed a{border:1px solid transparent;border-radius:4px 4px 0 0}.tabs.is-boxed a:hover{background-color:#f5f5f5;border-bottom-color:#dbdbdb}.tabs.is-boxed li.is-active a{background-color:#fff;border-color:#dbdbdb;border-bottom-color:transparent!important}.tabs.is-fullwidth li{flex-grow:1;flex-shrink:0}.tabs.is-toggle a{border-color:#dbdbdb;border-style:solid;border-width:1px;margin-bottom:0;position:relative}.tabs.is-toggle a:hover{background-color:#f5f5f5;border-color:#b5b5b5;z-index:2}.tabs.is-toggle li+li{margin-left:-1px}.tabs.is-toggle li:first-child a{border-top-left-radius:4px;border-bottom-left-radius:4px}.tabs.is-toggle li:last-child a{border-top-right-radius:4px;border-bottom-right-radius:4px}.tabs.is-toggle li.is-active a{background-color:#485fc7;border-color:#485fc7;color:#fff;z-index:1}.tabs.is-toggle ul{border-bottom:none}.tabs.is-toggle.is-toggle-rounded li:first-child a{border-bottom-left-radius:9999px;border-top-left-radius:9999px;padding-left:1.25em}.tabs.is-toggle.is-toggle-rounded li:last-child a{border-bottom-right-radius:9999px;border-top-right-radius:9999px;padding-right:1.25em}.tabs.is-small{font-size:.75rem}.tabs.is-medium{font-size:1.25rem}.tabs.is-large{font-size:1.5rem}.column{display:block;flex-basis:0;flex-grow:1;flex-shrink:1;padding:.75rem}.columns.is-mobile>.column.is-narrow{flex:none;width:unset}.columns.is-mobile>.column.is-full{flex:none;width:100%}.columns.is-mobile>.column.is-three-quarters{flex:none;width:75%}.columns.is-mobile>.column.is-two-thirds{flex:none;width:66.6666%}.columns.is-mobile>.column.is-half{flex:none;width:50%}.columns.is-mobile>.column.is-one-third{flex:none;width:33.3333%}.columns.is-mobile>.column.is-one-quarter{flex:none;width:25%}.columns.is-mobile>.column.is-one-fifth{flex:none;width:20%}.columns.is-mobile>.column.is-two-fifths{flex:none;width:40%}.columns.is-mobile>.column.is-three-fifths{flex:none;width:60%}.columns.is-mobile>.column.is-four-fifths{flex:none;width:80%}.columns.is-mobile>.column.is-offset-three-quarters{margin-left:75%}.columns.is-mobile>.column.is-offset-two-thirds{margin-left:66.6666%}.columns.is-mobile>.column.is-offset-half{margin-left:50%}.columns.is-mobile>.column.is-offset-one-third{margin-left:33.3333%}.columns.is-mobile>.column.is-offset-one-quarter{margin-left:25%}.columns.is-mobile>.column.is-offset-one-fifth{margin-left:20%}.columns.is-mobile>.column.is-offset-two-fifths{margin-left:40%}.columns.is-mobile>.column.is-offset-three-fifths{margin-left:60%}.columns.is-mobile>.column.is-offset-four-fifths{margin-left:80%}.columns.is-mobile>.column.is-0{flex:none;width:0%}.columns.is-mobile>.column.is-offset-0{margin-left:0}.columns.is-mobile>.column.is-1{flex:none;width:8.33333%}.columns.is-mobile>.column.is-offset-1{margin-left:8.33333%}.columns.is-mobile>.column.is-2{flex:none;width:16.66667%}.columns.is-mobile>.column.is-offset-2{margin-left:16.66667%}.columns.is-mobile>.column.is-3{flex:none;width:25%}.columns.is-mobile>.column.is-offset-3{margin-left:25%}.columns.is-mobile>.column.is-4{flex:none;width:33.33333%}.columns.is-mobile>.column.is-offset-4{margin-left:33.33333%}.columns.is-mobile>.column.is-5{flex:none;width:41.66667%}.columns.is-mobile>.column.is-offset-5{margin-left:41.66667%}.columns.is-mobile>.column.is-6{flex:none;width:50%}.columns.is-mobile>.column.is-offset-6{margin-left:50%}.columns.is-mobile>.column.is-7{flex:none;width:58.33333%}.columns.is-mobile>.column.is-offset-7{margin-left:58.33333%}.columns.is-mobile>.column.is-8{flex:none;width:66.66667%}.columns.is-mobile>.column.is-offset-8{margin-left:66.66667%}.columns.is-mobile>.column.is-9{flex:none;width:75%}.columns.is-mobile>.column.is-offset-9{margin-left:75%}.columns.is-mobile>.column.is-10{flex:none;width:83.33333%}.columns.is-mobile>.column.is-offset-10{margin-left:83.33333%}.columns.is-mobile>.column.is-11{flex:none;width:91.66667%}.columns.is-mobile>.column.is-offset-11{margin-left:91.66667%}.columns.is-mobile>.column.is-12{flex:none;width:100%}.columns.is-mobile>.column.is-offset-12{margin-left:100%}@media screen and (max-width:768px){.column.is-narrow-mobile{flex:none;width:unset}.column.is-full-mobile{flex:none;width:100%}.column.is-three-quarters-mobile{flex:none;width:75%}.column.is-two-thirds-mobile{flex:none;width:66.6666%}.column.is-half-mobile{flex:none;width:50%}.column.is-one-third-mobile{flex:none;width:33.3333%}.column.is-one-quarter-mobile{flex:none;width:25%}.column.is-one-fifth-mobile{flex:none;width:20%}.column.is-two-fifths-mobile{flex:none;width:40%}.column.is-three-fifths-mobile{flex:none;width:60%}.column.is-four-fifths-mobile{flex:none;width:80%}.column.is-offset-three-quarters-mobile{margin-left:75%}.column.is-offset-two-thirds-mobile{margin-left:66.6666%}.column.is-offset-half-mobile{margin-left:50%}.column.is-offset-one-third-mobile{margin-left:33.3333%}.column.is-offset-one-quarter-mobile{margin-left:25%}.column.is-offset-one-fifth-mobile{margin-left:20%}.column.is-offset-two-fifths-mobile{margin-left:40%}.column.is-offset-three-fifths-mobile{margin-left:60%}.column.is-offset-four-fifths-mobile{margin-left:80%}.column.is-0-mobile{flex:none;width:0%}.column.is-offset-0-mobile{margin-left:0}.column.is-1-mobile{flex:none;width:8.33333%}.column.is-offset-1-mobile{margin-left:8.33333%}.column.is-2-mobile{flex:none;width:16.66667%}.column.is-offset-2-mobile{margin-left:16.66667%}.column.is-3-mobile{flex:none;width:25%}.column.is-offset-3-mobile{margin-left:25%}.column.is-4-mobile{flex:none;width:33.33333%}.column.is-offset-4-mobile{margin-left:33.33333%}.column.is-5-mobile{flex:none;width:41.66667%}.column.is-offset-5-mobile{margin-left:41.66667%}.column.is-6-mobile{flex:none;width:50%}.column.is-offset-6-mobile{margin-left:50%}.column.is-7-mobile{flex:none;width:58.33333%}.column.is-offset-7-mobile{margin-left:58.33333%}.column.is-8-mobile{flex:none;width:66.66667%}.column.is-offset-8-mobile{margin-left:66.66667%}.column.is-9-mobile{flex:none;width:75%}.column.is-offset-9-mobile{margin-left:75%}.column.is-10-mobile{flex:none;width:83.33333%}.column.is-offset-10-mobile{margin-left:83.33333%}.column.is-11-mobile{flex:none;width:91.66667%}.column.is-offset-11-mobile{margin-left:91.66667%}.column.is-12-mobile{flex:none;width:100%}.column.is-offset-12-mobile{margin-left:100%}}@media screen and (min-width:769px),print{.column.is-narrow,.column.is-narrow-tablet{flex:none;width:unset}.column.is-full,.column.is-full-tablet{flex:none;width:100%}.column.is-three-quarters,.column.is-three-quarters-tablet{flex:none;width:75%}.column.is-two-thirds,.column.is-two-thirds-tablet{flex:none;width:66.6666%}.column.is-half,.column.is-half-tablet{flex:none;width:50%}.column.is-one-third,.column.is-one-third-tablet{flex:none;width:33.3333%}.column.is-one-quarter,.column.is-one-quarter-tablet{flex:none;width:25%}.column.is-one-fifth,.column.is-one-fifth-tablet{flex:none;width:20%}.column.is-two-fifths,.column.is-two-fifths-tablet{flex:none;width:40%}.column.is-three-fifths,.column.is-three-fifths-tablet{flex:none;width:60%}.column.is-four-fifths,.column.is-four-fifths-tablet{flex:none;width:80%}.column.is-offset-three-quarters,.column.is-offset-three-quarters-tablet{margin-left:75%}.column.is-offset-two-thirds,.column.is-offset-two-thirds-tablet{margin-left:66.6666%}.column.is-offset-half,.column.is-offset-half-tablet{margin-left:50%}.column.is-offset-one-third,.column.is-offset-one-third-tablet{margin-left:33.3333%}.column.is-offset-one-quarter,.column.is-offset-one-quarter-tablet{margin-left:25%}.column.is-offset-one-fifth,.column.is-offset-one-fifth-tablet{margin-left:20%}.column.is-offset-two-fifths,.column.is-offset-two-fifths-tablet{margin-left:40%}.column.is-offset-three-fifths,.column.is-offset-three-fifths-tablet{margin-left:60%}.column.is-offset-four-fifths,.column.is-offset-four-fifths-tablet{margin-left:80%}.column.is-0,.column.is-0-tablet{flex:none;width:0%}.column.is-offset-0,.column.is-offset-0-tablet{margin-left:0}.column.is-1,.column.is-1-tablet{flex:none;width:8.33333%}.column.is-offset-1,.column.is-offset-1-tablet{margin-left:8.33333%}.column.is-2,.column.is-2-tablet{flex:none;width:16.66667%}.column.is-offset-2,.column.is-offset-2-tablet{margin-left:16.66667%}.column.is-3,.column.is-3-tablet{flex:none;width:25%}.column.is-offset-3,.column.is-offset-3-tablet{margin-left:25%}.column.is-4,.column.is-4-tablet{flex:none;width:33.33333%}.column.is-offset-4,.column.is-offset-4-tablet{margin-left:33.33333%}.column.is-5,.column.is-5-tablet{flex:none;width:41.66667%}.column.is-offset-5,.column.is-offset-5-tablet{margin-left:41.66667%}.column.is-6,.column.is-6-tablet{flex:none;width:50%}.column.is-offset-6,.column.is-offset-6-tablet{margin-left:50%}.column.is-7,.column.is-7-tablet{flex:none;width:58.33333%}.column.is-offset-7,.column.is-offset-7-tablet{margin-left:58.33333%}.column.is-8,.column.is-8-tablet{flex:none;width:66.66667%}.column.is-offset-8,.column.is-offset-8-tablet{margin-left:66.66667%}.column.is-9,.column.is-9-tablet{flex:none;width:75%}.column.is-offset-9,.column.is-offset-9-tablet{margin-left:75%}.column.is-10,.column.is-10-tablet{flex:none;width:83.33333%}.column.is-offset-10,.column.is-offset-10-tablet{margin-left:83.33333%}.column.is-11,.column.is-11-tablet{flex:none;width:91.66667%}.column.is-offset-11,.column.is-offset-11-tablet{margin-left:91.66667%}.column.is-12,.column.is-12-tablet{flex:none;width:100%}.column.is-offset-12,.column.is-offset-12-tablet{margin-left:100%}}@media screen and (max-width:1023px){.column.is-narrow-touch{flex:none;width:unset}.column.is-full-touch{flex:none;width:100%}.column.is-three-quarters-touch{flex:none;width:75%}.column.is-two-thirds-touch{flex:none;width:66.6666%}.column.is-half-touch{flex:none;width:50%}.column.is-one-third-touch{flex:none;width:33.3333%}.column.is-one-quarter-touch{flex:none;width:25%}.column.is-one-fifth-touch{flex:none;width:20%}.column.is-two-fifths-touch{flex:none;width:40%}.column.is-three-fifths-touch{flex:none;width:60%}.column.is-four-fifths-touch{flex:none;width:80%}.column.is-offset-three-quarters-touch{margin-left:75%}.column.is-offset-two-thirds-touch{margin-left:66.6666%}.column.is-offset-half-touch{margin-left:50%}.column.is-offset-one-third-touch{margin-left:33.3333%}.column.is-offset-one-quarter-touch{margin-left:25%}.column.is-offset-one-fifth-touch{margin-left:20%}.column.is-offset-two-fifths-touch{margin-left:40%}.column.is-offset-three-fifths-touch{margin-left:60%}.column.is-offset-four-fifths-touch{margin-left:80%}.column.is-0-touch{flex:none;width:0%}.column.is-offset-0-touch{margin-left:0}.column.is-1-touch{flex:none;width:8.33333%}.column.is-offset-1-touch{margin-left:8.33333%}.column.is-2-touch{flex:none;width:16.66667%}.column.is-offset-2-touch{margin-left:16.66667%}.column.is-3-touch{flex:none;width:25%}.column.is-offset-3-touch{margin-left:25%}.column.is-4-touch{flex:none;width:33.33333%}.column.is-offset-4-touch{margin-left:33.33333%}.column.is-5-touch{flex:none;width:41.66667%}.column.is-offset-5-touch{margin-left:41.66667%}.column.is-6-touch{flex:none;width:50%}.column.is-offset-6-touch{margin-left:50%}.column.is-7-touch{flex:none;width:58.33333%}.column.is-offset-7-touch{margin-left:58.33333%}.column.is-8-touch{flex:none;width:66.66667%}.column.is-offset-8-touch{margin-left:66.66667%}.column.is-9-touch{flex:none;width:75%}.column.is-offset-9-touch{margin-left:75%}.column.is-10-touch{flex:none;width:83.33333%}.column.is-offset-10-touch{margin-left:83.33333%}.column.is-11-touch{flex:none;width:91.66667%}.column.is-offset-11-touch{margin-left:91.66667%}.column.is-12-touch{flex:none;width:100%}.column.is-offset-12-touch{margin-left:100%}}@media screen and (min-width:1024px){.column.is-narrow-desktop{flex:none;width:unset}.column.is-full-desktop{flex:none;width:100%}.column.is-three-quarters-desktop{flex:none;width:75%}.column.is-two-thirds-desktop{flex:none;width:66.6666%}.column.is-half-desktop{flex:none;width:50%}.column.is-one-third-desktop{flex:none;width:33.3333%}.column.is-one-quarter-desktop{flex:none;width:25%}.column.is-one-fifth-desktop{flex:none;width:20%}.column.is-two-fifths-desktop{flex:none;width:40%}.column.is-three-fifths-desktop{flex:none;width:60%}.column.is-four-fifths-desktop{flex:none;width:80%}.column.is-offset-three-quarters-desktop{margin-left:75%}.column.is-offset-two-thirds-desktop{margin-left:66.6666%}.column.is-offset-half-desktop{margin-left:50%}.column.is-offset-one-third-desktop{margin-left:33.3333%}.column.is-offset-one-quarter-desktop{margin-left:25%}.column.is-offset-one-fifth-desktop{margin-left:20%}.column.is-offset-two-fifths-desktop{margin-left:40%}.column.is-offset-three-fifths-desktop{margin-left:60%}.column.is-offset-four-fifths-desktop{margin-left:80%}.column.is-0-desktop{flex:none;width:0%}.column.is-offset-0-desktop{margin-left:0}.column.is-1-desktop{flex:none;width:8.33333%}.column.is-offset-1-desktop{margin-left:8.33333%}.column.is-2-desktop{flex:none;width:16.66667%}.column.is-offset-2-desktop{margin-left:16.66667%}.column.is-3-desktop{flex:none;width:25%}.column.is-offset-3-desktop{margin-left:25%}.column.is-4-desktop{flex:none;width:33.33333%}.column.is-offset-4-desktop{margin-left:33.33333%}.column.is-5-desktop{flex:none;width:41.66667%}.column.is-offset-5-desktop{margin-left:41.66667%}.column.is-6-desktop{flex:none;width:50%}.column.is-offset-6-desktop{margin-left:50%}.column.is-7-desktop{flex:none;width:58.33333%}.column.is-offset-7-desktop{margin-left:58.33333%}.column.is-8-desktop{flex:none;width:66.66667%}.column.is-offset-8-desktop{margin-left:66.66667%}.column.is-9-desktop{flex:none;width:75%}.column.is-offset-9-desktop{margin-left:75%}.column.is-10-desktop{flex:none;width:83.33333%}.column.is-offset-10-desktop{margin-left:83.33333%}.column.is-11-desktop{flex:none;width:91.66667%}.column.is-offset-11-desktop{margin-left:91.66667%}.column.is-12-desktop{flex:none;width:100%}.column.is-offset-12-desktop{margin-left:100%}}@media screen and (min-width:1216px){.column.is-narrow-widescreen{flex:none;width:unset}.column.is-full-widescreen{flex:none;width:100%}.column.is-three-quarters-widescreen{flex:none;width:75%}.column.is-two-thirds-widescreen{flex:none;width:66.6666%}.column.is-half-widescreen{flex:none;width:50%}.column.is-one-third-widescreen{flex:none;width:33.3333%}.column.is-one-quarter-widescreen{flex:none;width:25%}.column.is-one-fifth-widescreen{flex:none;width:20%}.column.is-two-fifths-widescreen{flex:none;width:40%}.column.is-three-fifths-widescreen{flex:none;width:60%}.column.is-four-fifths-widescreen{flex:none;width:80%}.column.is-offset-three-quarters-widescreen{margin-left:75%}.column.is-offset-two-thirds-widescreen{margin-left:66.6666%}.column.is-offset-half-widescreen{margin-left:50%}.column.is-offset-one-third-widescreen{margin-left:33.3333%}.column.is-offset-one-quarter-widescreen{margin-left:25%}.column.is-offset-one-fifth-widescreen{margin-left:20%}.column.is-offset-two-fifths-widescreen{margin-left:40%}.column.is-offset-three-fifths-widescreen{margin-left:60%}.column.is-offset-four-fifths-widescreen{margin-left:80%}.column.is-0-widescreen{flex:none;width:0%}.column.is-offset-0-widescreen{margin-left:0}.column.is-1-widescreen{flex:none;width:8.33333%}.column.is-offset-1-widescreen{margin-left:8.33333%}.column.is-2-widescreen{flex:none;width:16.66667%}.column.is-offset-2-widescreen{margin-left:16.66667%}.column.is-3-widescreen{flex:none;width:25%}.column.is-offset-3-widescreen{margin-left:25%}.column.is-4-widescreen{flex:none;width:33.33333%}.column.is-offset-4-widescreen{margin-left:33.33333%}.column.is-5-widescreen{flex:none;width:41.66667%}.column.is-offset-5-widescreen{margin-left:41.66667%}.column.is-6-widescreen{flex:none;width:50%}.column.is-offset-6-widescreen{margin-left:50%}.column.is-7-widescreen{flex:none;width:58.33333%}.column.is-offset-7-widescreen{margin-left:58.33333%}.column.is-8-widescreen{flex:none;width:66.66667%}.column.is-offset-8-widescreen{margin-left:66.66667%}.column.is-9-widescreen{flex:none;width:75%}.column.is-offset-9-widescreen{margin-left:75%}.column.is-10-widescreen{flex:none;width:83.33333%}.column.is-offset-10-widescreen{margin-left:83.33333%}.column.is-11-widescreen{flex:none;width:91.66667%}.column.is-offset-11-widescreen{margin-left:91.66667%}.column.is-12-widescreen{flex:none;width:100%}.column.is-offset-12-widescreen{margin-left:100%}}@media screen and (min-width:1408px){.column.is-narrow-fullhd{flex:none;width:unset}.column.is-full-fullhd{flex:none;width:100%}.column.is-three-quarters-fullhd{flex:none;width:75%}.column.is-two-thirds-fullhd{flex:none;width:66.6666%}.column.is-half-fullhd{flex:none;width:50%}.column.is-one-third-fullhd{flex:none;width:33.3333%}.column.is-one-quarter-fullhd{flex:none;width:25%}.column.is-one-fifth-fullhd{flex:none;width:20%}.column.is-two-fifths-fullhd{flex:none;width:40%}.column.is-three-fifths-fullhd{flex:none;width:60%}.column.is-four-fifths-fullhd{flex:none;width:80%}.column.is-offset-three-quarters-fullhd{margin-left:75%}.column.is-offset-two-thirds-fullhd{margin-left:66.6666%}.column.is-offset-half-fullhd{margin-left:50%}.column.is-offset-one-third-fullhd{margin-left:33.3333%}.column.is-offset-one-quarter-fullhd{margin-left:25%}.column.is-offset-one-fifth-fullhd{margin-left:20%}.column.is-offset-two-fifths-fullhd{margin-left:40%}.column.is-offset-three-fifths-fullhd{margin-left:60%}.column.is-offset-four-fifths-fullhd{margin-left:80%}.column.is-0-fullhd{flex:none;width:0%}.column.is-offset-0-fullhd{margin-left:0}.column.is-1-fullhd{flex:none;width:8.33333%}.column.is-offset-1-fullhd{margin-left:8.33333%}.column.is-2-fullhd{flex:none;width:16.66667%}.column.is-offset-2-fullhd{margin-left:16.66667%}.column.is-3-fullhd{flex:none;width:25%}.column.is-offset-3-fullhd{margin-left:25%}.column.is-4-fullhd{flex:none;width:33.33333%}.column.is-offset-4-fullhd{margin-left:33.33333%}.column.is-5-fullhd{flex:none;width:41.66667%}.column.is-offset-5-fullhd{margin-left:41.66667%}.column.is-6-fullhd{flex:none;width:50%}.column.is-offset-6-fullhd{margin-left:50%}.column.is-7-fullhd{flex:none;width:58.33333%}.column.is-offset-7-fullhd{margin-left:58.33333%}.column.is-8-fullhd{flex:none;width:66.66667%}.column.is-offset-8-fullhd{margin-left:66.66667%}.column.is-9-fullhd{flex:none;width:75%}.column.is-offset-9-fullhd{margin-left:75%}.column.is-10-fullhd{flex:none;width:83.33333%}.column.is-offset-10-fullhd{margin-left:83.33333%}.column.is-11-fullhd{flex:none;width:91.66667%}.column.is-offset-11-fullhd{margin-left:91.66667%}.column.is-12-fullhd{flex:none;width:100%}.column.is-offset-12-fullhd{margin-left:100%}}.columns{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}.columns:last-child{margin-bottom:-.75rem}.columns:not(:last-child){margin-bottom:calc(1.5rem - .75rem)}.columns.is-centered{justify-content:center}.columns.is-gapless{margin-left:0;margin-right:0;margin-top:0}.columns.is-gapless>.column{margin:0;padding:0!important}.columns.is-gapless:not(:last-child){margin-bottom:1.5rem}.columns.is-gapless:last-child{margin-bottom:0}.columns.is-mobile{display:flex}.columns.is-multiline{flex-wrap:wrap}.columns.is-vcentered{align-items:center}@media screen and (min-width:769px),print{.columns:not(.is-desktop){display:flex}}@media screen and (min-width:1024px){.columns.is-desktop{display:flex}}.columns.is-variable{--columnGap:0.75rem;margin-left:calc(-1 * var(--columnGap));margin-right:calc(-1 * var(--columnGap))}.columns.is-variable>.column{padding-left:var(--columnGap);padding-right:var(--columnGap)}.columns.is-variable.is-0{--columnGap:0rem}@media screen and (max-width:768px){.columns.is-variable.is-0-mobile{--columnGap:0rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-0-tablet{--columnGap:0rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-0-tablet-only{--columnGap:0rem}}@media screen and (max-width:1023px){.columns.is-variable.is-0-touch{--columnGap:0rem}}@media screen and (min-width:1024px){.columns.is-variable.is-0-desktop{--columnGap:0rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-0-desktop-only{--columnGap:0rem}}@media screen and (min-width:1216px){.columns.is-variable.is-0-widescreen{--columnGap:0rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-0-widescreen-only{--columnGap:0rem}}@media screen and (min-width:1408px){.columns.is-variable.is-0-fullhd{--columnGap:0rem}}.columns.is-variable.is-1{--columnGap:0.25rem}@media screen and (max-width:768px){.columns.is-variable.is-1-mobile{--columnGap:0.25rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-1-tablet{--columnGap:0.25rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-1-tablet-only{--columnGap:0.25rem}}@media screen and (max-width:1023px){.columns.is-variable.is-1-touch{--columnGap:0.25rem}}@media screen and (min-width:1024px){.columns.is-variable.is-1-desktop{--columnGap:0.25rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-1-desktop-only{--columnGap:0.25rem}}@media screen and (min-width:1216px){.columns.is-variable.is-1-widescreen{--columnGap:0.25rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-1-widescreen-only{--columnGap:0.25rem}}@media screen and (min-width:1408px){.columns.is-variable.is-1-fullhd{--columnGap:0.25rem}}.columns.is-variable.is-2{--columnGap:0.5rem}@media screen and (max-width:768px){.columns.is-variable.is-2-mobile{--columnGap:0.5rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-2-tablet{--columnGap:0.5rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-2-tablet-only{--columnGap:0.5rem}}@media screen and (max-width:1023px){.columns.is-variable.is-2-touch{--columnGap:0.5rem}}@media screen and (min-width:1024px){.columns.is-variable.is-2-desktop{--columnGap:0.5rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-2-desktop-only{--columnGap:0.5rem}}@media screen and (min-width:1216px){.columns.is-variable.is-2-widescreen{--columnGap:0.5rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-2-widescreen-only{--columnGap:0.5rem}}@media screen and (min-width:1408px){.columns.is-variable.is-2-fullhd{--columnGap:0.5rem}}.columns.is-variable.is-3{--columnGap:0.75rem}@media screen and (max-width:768px){.columns.is-variable.is-3-mobile{--columnGap:0.75rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-3-tablet{--columnGap:0.75rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-3-tablet-only{--columnGap:0.75rem}}@media screen and (max-width:1023px){.columns.is-variable.is-3-touch{--columnGap:0.75rem}}@media screen and (min-width:1024px){.columns.is-variable.is-3-desktop{--columnGap:0.75rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-3-desktop-only{--columnGap:0.75rem}}@media screen and (min-width:1216px){.columns.is-variable.is-3-widescreen{--columnGap:0.75rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-3-widescreen-only{--columnGap:0.75rem}}@media screen and (min-width:1408px){.columns.is-variable.is-3-fullhd{--columnGap:0.75rem}}.columns.is-variable.is-4{--columnGap:1rem}@media screen and (max-width:768px){.columns.is-variable.is-4-mobile{--columnGap:1rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-4-tablet{--columnGap:1rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-4-tablet-only{--columnGap:1rem}}@media screen and (max-width:1023px){.columns.is-variable.is-4-touch{--columnGap:1rem}}@media screen and (min-width:1024px){.columns.is-variable.is-4-desktop{--columnGap:1rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-4-desktop-only{--columnGap:1rem}}@media screen and (min-width:1216px){.columns.is-variable.is-4-widescreen{--columnGap:1rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-4-widescreen-only{--columnGap:1rem}}@media screen and (min-width:1408px){.columns.is-variable.is-4-fullhd{--columnGap:1rem}}.columns.is-variable.is-5{--columnGap:1.25rem}@media screen and (max-width:768px){.columns.is-variable.is-5-mobile{--columnGap:1.25rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-5-tablet{--columnGap:1.25rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-5-tablet-only{--columnGap:1.25rem}}@media screen and (max-width:1023px){.columns.is-variable.is-5-touch{--columnGap:1.25rem}}@media screen and (min-width:1024px){.columns.is-variable.is-5-desktop{--columnGap:1.25rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-5-desktop-only{--columnGap:1.25rem}}@media screen and (min-width:1216px){.columns.is-variable.is-5-widescreen{--columnGap:1.25rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-5-widescreen-only{--columnGap:1.25rem}}@media screen and (min-width:1408px){.columns.is-variable.is-5-fullhd{--columnGap:1.25rem}}.columns.is-variable.is-6{--columnGap:1.5rem}@media screen and (max-width:768px){.columns.is-variable.is-6-mobile{--columnGap:1.5rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-6-tablet{--columnGap:1.5rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-6-tablet-only{--columnGap:1.5rem}}@media screen and (max-width:1023px){.columns.is-variable.is-6-touch{--columnGap:1.5rem}}@media screen and (min-width:1024px){.columns.is-variable.is-6-desktop{--columnGap:1.5rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-6-desktop-only{--columnGap:1.5rem}}@media screen and (min-width:1216px){.columns.is-variable.is-6-widescreen{--columnGap:1.5rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-6-widescreen-only{--columnGap:1.5rem}}@media screen and (min-width:1408px){.columns.is-variable.is-6-fullhd{--columnGap:1.5rem}}.columns.is-variable.is-7{--columnGap:1.75rem}@media screen and (max-width:768px){.columns.is-variable.is-7-mobile{--columnGap:1.75rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-7-tablet{--columnGap:1.75rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-7-tablet-only{--columnGap:1.75rem}}@media screen and (max-width:1023px){.columns.is-variable.is-7-touch{--columnGap:1.75rem}}@media screen and (min-width:1024px){.columns.is-variable.is-7-desktop{--columnGap:1.75rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-7-desktop-only{--columnGap:1.75rem}}@media screen and (min-width:1216px){.columns.is-variable.is-7-widescreen{--columnGap:1.75rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-7-widescreen-only{--columnGap:1.75rem}}@media screen and (min-width:1408px){.columns.is-variable.is-7-fullhd{--columnGap:1.75rem}}.columns.is-variable.is-8{--columnGap:2rem}@media screen and (max-width:768px){.columns.is-variable.is-8-mobile{--columnGap:2rem}}@media screen and (min-width:769px),print{.columns.is-variable.is-8-tablet{--columnGap:2rem}}@media screen and (min-width:769px) and (max-width:1023px){.columns.is-variable.is-8-tablet-only{--columnGap:2rem}}@media screen and (max-width:1023px){.columns.is-variable.is-8-touch{--columnGap:2rem}}@media screen and (min-width:1024px){.columns.is-variable.is-8-desktop{--columnGap:2rem}}@media screen and (min-width:1024px) and (max-width:1215px){.columns.is-variable.is-8-desktop-only{--columnGap:2rem}}@media screen and (min-width:1216px){.columns.is-variable.is-8-widescreen{--columnGap:2rem}}@media screen and (min-width:1216px) and (max-width:1407px){.columns.is-variable.is-8-widescreen-only{--columnGap:2rem}}@media screen and (min-width:1408px){.columns.is-variable.is-8-fullhd{--columnGap:2rem}}.tile{align-items:stretch;display:block;flex-basis:0;flex-grow:1;flex-shrink:1;min-height:-webkit-min-content;min-height:-moz-min-content;min-height:min-content}.tile.is-ancestor{margin-left:-.75rem;margin-right:-.75rem;margin-top:-.75rem}.tile.is-ancestor:last-child{margin-bottom:-.75rem}.tile.is-ancestor:not(:last-child){margin-bottom:.75rem}.tile.is-child{margin:0!important}.tile.is-parent{padding:.75rem}.tile.is-vertical{flex-direction:column}.tile.is-vertical>.tile.is-child:not(:last-child){margin-bottom:1.5rem!important}@media screen and (min-width:769px),print{.tile:not(.is-child){display:flex}.tile.is-1{flex:none;width:8.33333%}.tile.is-2{flex:none;width:16.66667%}.tile.is-3{flex:none;width:25%}.tile.is-4{flex:none;width:33.33333%}.tile.is-5{flex:none;width:41.66667%}.tile.is-6{flex:none;width:50%}.tile.is-7{flex:none;width:58.33333%}.tile.is-8{flex:none;width:66.66667%}.tile.is-9{flex:none;width:75%}.tile.is-10{flex:none;width:83.33333%}.tile.is-11{flex:none;width:91.66667%}.tile.is-12{flex:none;width:100%}}.has-text-white{color:#fff!important}a.has-text-white:focus,a.has-text-white:hover{color:#e6e6e6!important}.has-background-white{background-color:#fff!important}.has-text-black{color:#0a0a0a!important}a.has-text-black:focus,a.has-text-black:hover{color:#000!important}.has-background-black{background-color:#0a0a0a!important}.has-text-light{color:#f5f5f5!important}a.has-text-light:focus,a.has-text-light:hover{color:#dbdbdb!important}.has-background-light{background-color:#f5f5f5!important}.has-text-dark{color:#363636!important}a.has-text-dark:focus,a.has-text-dark:hover{color:#1c1c1c!important}.has-background-dark{background-color:#363636!important}.has-text-primary{color:#00d1b2!important}a.has-text-primary:focus,a.has-text-primary:hover{color:#009e86!important}.has-background-primary{background-color:#00d1b2!important}.has-text-primary-light{color:#ebfffc!important}a.has-text-primary-light:focus,a.has-text-primary-light:hover{color:#b8fff4!important}.has-background-primary-light{background-color:#ebfffc!important}.has-text-primary-dark{color:#00947e!important}a.has-text-primary-dark:focus,a.has-text-primary-dark:hover{color:#00c7a9!important}.has-background-primary-dark{background-color:#00947e!important}.has-text-link{color:#485fc7!important}a.has-text-link:focus,a.has-text-link:hover{color:#3449a8!important}.has-background-link{background-color:#485fc7!important}.has-text-link-light{color:#eff1fa!important}a.has-text-link-light:focus,a.has-text-link-light:hover{color:#c8cfee!important}.has-background-link-light{background-color:#eff1fa!important}.has-text-link-dark{color:#3850b7!important}a.has-text-link-dark:focus,a.has-text-link-dark:hover{color:#576dcb!important}.has-background-link-dark{background-color:#3850b7!important}.has-text-info{color:#3e8ed0!important}a.has-text-info:focus,a.has-text-info:hover{color:#2b74b1!important}.has-background-info{background-color:#3e8ed0!important}.has-text-info-light{color:#eff5fb!important}a.has-text-info-light:focus,a.has-text-info-light:hover{color:#c6ddf1!important}.has-background-info-light{background-color:#eff5fb!important}.has-text-info-dark{color:#296fa8!important}a.has-text-info-dark:focus,a.has-text-info-dark:hover{color:#368ace!important}.has-background-info-dark{background-color:#296fa8!important}.has-text-success{color:#48c78e!important}a.has-text-success:focus,a.has-text-success:hover{color:#34a873!important}.has-background-success{background-color:#48c78e!important}.has-text-success-light{color:#effaf5!important}a.has-text-success-light:focus,a.has-text-success-light:hover{color:#c8eedd!important}.has-background-success-light{background-color:#effaf5!important}.has-text-success-dark{color:#257953!important}a.has-text-success-dark:focus,a.has-text-success-dark:hover{color:#31a06e!important}.has-background-success-dark{background-color:#257953!important}.has-text-warning{color:#ffe08a!important}a.has-text-warning:focus,a.has-text-warning:hover{color:#ffd257!important}.has-background-warning{background-color:#ffe08a!important}.has-text-warning-light{color:#fffaeb!important}a.has-text-warning-light:focus,a.has-text-warning-light:hover{color:#ffecb8!important}.has-background-warning-light{background-color:#fffaeb!important}.has-text-warning-dark{color:#946c00!important}a.has-text-warning-dark:focus,a.has-text-warning-dark:hover{color:#c79200!important}.has-background-warning-dark{background-color:#946c00!important}.has-text-danger{color:#f14668!important}a.has-text-danger:focus,a.has-text-danger:hover{color:#ee1742!important}.has-background-danger{background-color:#f14668!important}.has-text-danger-light{color:#feecf0!important}a.has-text-danger-light:focus,a.has-text-danger-light:hover{color:#fabdc9!important}.has-background-danger-light{background-color:#feecf0!important}.has-text-danger-dark{color:#cc0f35!important}a.has-text-danger-dark:focus,a.has-text-danger-dark:hover{color:#ee2049!important}.has-background-danger-dark{background-color:#cc0f35!important}.has-text-black-bis{color:#121212!important}.has-background-black-bis{background-color:#121212!important}.has-text-black-ter{color:#242424!important}.has-background-black-ter{background-color:#242424!important}.has-text-grey-darker{color:#363636!important}.has-background-grey-darker{background-color:#363636!important}.has-text-grey-dark{color:#4a4a4a!important}.has-background-grey-dark{background-color:#4a4a4a!important}.has-text-grey{color:#7a7a7a!important}.has-background-grey{background-color:#7a7a7a!important}.has-text-grey-light{color:#b5b5b5!important}.has-background-grey-light{background-color:#b5b5b5!important}.has-text-grey-lighter{color:#dbdbdb!important}.has-background-grey-lighter{background-color:#dbdbdb!important}.has-text-white-ter{color:#f5f5f5!important}.has-background-white-ter{background-color:#f5f5f5!important}.has-text-white-bis{color:#fafafa!important}.has-background-white-bis{background-color:#fafafa!important}.is-flex-direction-row{flex-direction:row!important}.is-flex-direction-row-reverse{flex-direction:row-reverse!important}.is-flex-direction-column{flex-direction:column!important}.is-flex-direction-column-reverse{flex-direction:column-reverse!important}.is-flex-wrap-nowrap{flex-wrap:nowrap!important}.is-flex-wrap-wrap{flex-wrap:wrap!important}.is-flex-wrap-wrap-reverse{flex-wrap:wrap-reverse!important}.is-justify-content-flex-start{justify-content:flex-start!important}.is-justify-content-flex-end{justify-content:flex-end!important}.is-justify-content-center{justify-content:center!important}.is-justify-content-space-between{justify-content:space-between!important}.is-justify-content-space-around{justify-content:space-around!important}.is-justify-content-space-evenly{justify-content:space-evenly!important}.is-justify-content-start{justify-content:start!important}.is-justify-content-end{justify-content:end!important}.is-justify-content-left{justify-content:left!important}.is-justify-content-right{justify-content:right!important}.is-align-content-flex-start{align-content:flex-start!important}.is-align-content-flex-end{align-content:flex-end!important}.is-align-content-center{align-content:center!important}.is-align-content-space-between{align-content:space-between!important}.is-align-content-space-around{align-content:space-around!important}.is-align-content-space-evenly{align-content:space-evenly!important}.is-align-content-stretch{align-content:stretch!important}.is-align-content-start{align-content:start!important}.is-align-content-end{align-content:end!important}.is-align-content-baseline{align-content:baseline!important}.is-align-items-stretch{align-items:stretch!important}.is-align-items-flex-start{align-items:flex-start!important}.is-align-items-flex-end{align-items:flex-end!important}.is-align-items-center{align-items:center!important}.is-align-items-baseline{align-items:baseline!important}.is-align-items-start{align-items:start!important}.is-align-items-end{align-items:end!important}.is-align-items-self-start{align-items:self-start!important}.is-align-items-self-end{align-items:self-end!important}.is-align-self-auto{align-self:auto!important}.is-align-self-flex-start{align-self:flex-start!important}.is-align-self-flex-end{align-self:flex-end!important}.is-align-self-center{align-self:center!important}.is-align-self-baseline{align-self:baseline!important}.is-align-self-stretch{align-self:stretch!important}.is-flex-grow-0{flex-grow:0!important}.is-flex-grow-1{flex-grow:1!important}.is-flex-grow-2{flex-grow:2!important}.is-flex-grow-3{flex-grow:3!important}.is-flex-grow-4{flex-grow:4!important}.is-flex-grow-5{flex-grow:5!important}.is-flex-shrink-0{flex-shrink:0!important}.is-flex-shrink-1{flex-shrink:1!important}.is-flex-shrink-2{flex-shrink:2!important}.is-flex-shrink-3{flex-shrink:3!important}.is-flex-shrink-4{flex-shrink:4!important}.is-flex-shrink-5{flex-shrink:5!important}.is-clearfix::after{clear:both;content:" ";display:table}.is-pulled-left{float:left!important}.is-pulled-right{float:right!important}.is-radiusless{border-radius:0!important}.is-shadowless{box-shadow:none!important}.is-clickable{cursor:pointer!important;pointer-events:all!important}.is-clipped{overflow:hidden!important}.is-relative{position:relative!important}.is-marginless{margin:0!important}.is-paddingless{padding:0!important}.m-0{margin:0!important}.mt-0{margin-top:0!important}.mr-0{margin-right:0!important}.mb-0{margin-bottom:0!important}.ml-0{margin-left:0!important}.mx-0{margin-left:0!important;margin-right:0!important}.my-0{margin-top:0!important;margin-bottom:0!important}.m-1{margin:.25rem!important}.mt-1{margin-top:.25rem!important}.mr-1{margin-right:.25rem!important}.mb-1{margin-bottom:.25rem!important}.ml-1{margin-left:.25rem!important}.mx-1{margin-left:.25rem!important;margin-right:.25rem!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.m-2{margin:.5rem!important}.mt-2{margin-top:.5rem!important}.mr-2{margin-right:.5rem!important}.mb-2{margin-bottom:.5rem!important}.ml-2{margin-left:.5rem!important}.mx-2{margin-left:.5rem!important;margin-right:.5rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.m-3{margin:.75rem!important}.mt-3{margin-top:.75rem!important}.mr-3{margin-right:.75rem!important}.mb-3{margin-bottom:.75rem!important}.ml-3{margin-left:.75rem!important}.mx-3{margin-left:.75rem!important;margin-right:.75rem!important}.my-3{margin-top:.75rem!important;margin-bottom:.75rem!important}.m-4{margin:1rem!important}.mt-4{margin-top:1rem!important}.mr-4{margin-right:1rem!important}.mb-4{margin-bottom:1rem!important}.ml-4{margin-left:1rem!important}.mx-4{margin-left:1rem!important;margin-right:1rem!important}.my-4{margin-top:1rem!important;margin-bottom:1rem!important}.m-5{margin:1.5rem!important}.mt-5{margin-top:1.5rem!important}.mr-5{margin-right:1.5rem!important}.mb-5{margin-bottom:1.5rem!important}.ml-5{margin-left:1.5rem!important}.mx-5{margin-left:1.5rem!important;margin-right:1.5rem!important}.my-5{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.m-6{margin:3rem!important}.mt-6{margin-top:3rem!important}.mr-6{margin-right:3rem!important}.mb-6{margin-bottom:3rem!important}.ml-6{margin-left:3rem!important}.mx-6{margin-left:3rem!important;margin-right:3rem!important}.my-6{margin-top:3rem!important;margin-bottom:3rem!important}.m-auto{margin:auto!important}.mt-auto{margin-top:auto!important}.mr-auto{margin-right:auto!important}.mb-auto{margin-bottom:auto!important}.ml-auto{margin-left:auto!important}.mx-auto{margin-left:auto!important;margin-right:auto!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.p-0{padding:0!important}.pt-0{padding-top:0!important}.pr-0{padding-right:0!important}.pb-0{padding-bottom:0!important}.pl-0{padding-left:0!important}.px-0{padding-left:0!important;padding-right:0!important}.py-0{padding-top:0!important;padding-bottom:0!important}.p-1{padding:.25rem!important}.pt-1{padding-top:.25rem!important}.pr-1{padding-right:.25rem!important}.pb-1{padding-bottom:.25rem!important}.pl-1{padding-left:.25rem!important}.px-1{padding-left:.25rem!important;padding-right:.25rem!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.p-2{padding:.5rem!important}.pt-2{padding-top:.5rem!important}.pr-2{padding-right:.5rem!important}.pb-2{padding-bottom:.5rem!important}.pl-2{padding-left:.5rem!important}.px-2{padding-left:.5rem!important;padding-right:.5rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.p-3{padding:.75rem!important}.pt-3{padding-top:.75rem!important}.pr-3{padding-right:.75rem!important}.pb-3{padding-bottom:.75rem!important}.pl-3{padding-left:.75rem!important}.px-3{padding-left:.75rem!important;padding-right:.75rem!important}.py-3{padding-top:.75rem!important;padding-bottom:.75rem!important}.p-4{padding:1rem!important}.pt-4{padding-top:1rem!important}.pr-4{padding-right:1rem!important}.pb-4{padding-bottom:1rem!important}.pl-4{padding-left:1rem!important}.px-4{padding-left:1rem!important;padding-right:1rem!important}.py-4{padding-top:1rem!important;padding-bottom:1rem!important}.p-5{padding:1.5rem!important}.pt-5{padding-top:1.5rem!important}.pr-5{padding-right:1.5rem!important}.pb-5{padding-bottom:1.5rem!important}.pl-5{padding-left:1.5rem!important}.px-5{padding-left:1.5rem!important;padding-right:1.5rem!important}.py-5{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.p-6{padding:3rem!important}.pt-6{padding-top:3rem!important}.pr-6{padding-right:3rem!important}.pb-6{padding-bottom:3rem!important}.pl-6{padding-left:3rem!important}.px-6{padding-left:3rem!important;padding-right:3rem!important}.py-6{padding-top:3rem!important;padding-bottom:3rem!important}.p-auto{padding:auto!important}.pt-auto{padding-top:auto!important}.pr-auto{padding-right:auto!important}.pb-auto{padding-bottom:auto!important}.pl-auto{padding-left:auto!important}.px-auto{padding-left:auto!important;padding-right:auto!important}.py-auto{padding-top:auto!important;padding-bottom:auto!important}.is-size-1{font-size:3rem!important}.is-size-2{font-size:2.5rem!important}.is-size-3{font-size:2rem!important}.is-size-4{font-size:1.5rem!important}.is-size-5{font-size:1.25rem!important}.is-size-6{font-size:1rem!important}.is-size-7{font-size:.75rem!important}@media screen and (max-width:768px){.is-size-1-mobile{font-size:3rem!important}.is-size-2-mobile{font-size:2.5rem!important}.is-size-3-mobile{font-size:2rem!important}.is-size-4-mobile{font-size:1.5rem!important}.is-size-5-mobile{font-size:1.25rem!important}.is-size-6-mobile{font-size:1rem!important}.is-size-7-mobile{font-size:.75rem!important}}@media screen and (min-width:769px),print{.is-size-1-tablet{font-size:3rem!important}.is-size-2-tablet{font-size:2.5rem!important}.is-size-3-tablet{font-size:2rem!important}.is-size-4-tablet{font-size:1.5rem!important}.is-size-5-tablet{font-size:1.25rem!important}.is-size-6-tablet{font-size:1rem!important}.is-size-7-tablet{font-size:.75rem!important}}@media screen and (max-width:1023px){.is-size-1-touch{font-size:3rem!important}.is-size-2-touch{font-size:2.5rem!important}.is-size-3-touch{font-size:2rem!important}.is-size-4-touch{font-size:1.5rem!important}.is-size-5-touch{font-size:1.25rem!important}.is-size-6-touch{font-size:1rem!important}.is-size-7-touch{font-size:.75rem!important}}@media screen and (min-width:1024px){.is-size-1-desktop{font-size:3rem!important}.is-size-2-desktop{font-size:2.5rem!important}.is-size-3-desktop{font-size:2rem!important}.is-size-4-desktop{font-size:1.5rem!important}.is-size-5-desktop{font-size:1.25rem!important}.is-size-6-desktop{font-size:1rem!important}.is-size-7-desktop{font-size:.75rem!important}}@media screen and (min-width:1216px){.is-size-1-widescreen{font-size:3rem!important}.is-size-2-widescreen{font-size:2.5rem!important}.is-size-3-widescreen{font-size:2rem!important}.is-size-4-widescreen{font-size:1.5rem!important}.is-size-5-widescreen{font-size:1.25rem!important}.is-size-6-widescreen{font-size:1rem!important}.is-size-7-widescreen{font-size:.75rem!important}}@media screen and (min-width:1408px){.is-size-1-fullhd{font-size:3rem!important}.is-size-2-fullhd{font-size:2.5rem!important}.is-size-3-fullhd{font-size:2rem!important}.is-size-4-fullhd{font-size:1.5rem!important}.is-size-5-fullhd{font-size:1.25rem!important}.is-size-6-fullhd{font-size:1rem!important}.is-size-7-fullhd{font-size:.75rem!important}}.has-text-centered{text-align:center!important}.has-text-justified{text-align:justify!important}.has-text-left{text-align:left!important}.has-text-right{text-align:right!important}@media screen and (max-width:768px){.has-text-centered-mobile{text-align:center!important}}@media screen and (min-width:769px),print{.has-text-centered-tablet{text-align:center!important}}@media screen and (min-width:769px) and (max-width:1023px){.has-text-centered-tablet-only{text-align:center!important}}@media screen and (max-width:1023px){.has-text-centered-touch{text-align:center!important}}@media screen and (min-width:1024px){.has-text-centered-desktop{text-align:center!important}}@media screen and (min-width:1024px) and (max-width:1215px){.has-text-centered-desktop-only{text-align:center!important}}@media screen and (min-width:1216px){.has-text-centered-widescreen{text-align:center!important}}@media screen and (min-width:1216px) and (max-width:1407px){.has-text-centered-widescreen-only{text-align:center!important}}@media screen and (min-width:1408px){.has-text-centered-fullhd{text-align:center!important}}@media screen and (max-width:768px){.has-text-justified-mobile{text-align:justify!important}}@media screen and (min-width:769px),print{.has-text-justified-tablet{text-align:justify!important}}@media screen and (min-width:769px) and (max-width:1023px){.has-text-justified-tablet-only{text-align:justify!important}}@media screen and (max-width:1023px){.has-text-justified-touch{text-align:justify!important}}@media screen and (min-width:1024px){.has-text-justified-desktop{text-align:justify!important}}@media screen and (min-width:1024px) and (max-width:1215px){.has-text-justified-desktop-only{text-align:justify!important}}@media screen and (min-width:1216px){.has-text-justified-widescreen{text-align:justify!important}}@media screen and (min-width:1216px) and (max-width:1407px){.has-text-justified-widescreen-only{text-align:justify!important}}@media screen and (min-width:1408px){.has-text-justified-fullhd{text-align:justify!important}}@media screen and (max-width:768px){.has-text-left-mobile{text-align:left!important}}@media screen and (min-width:769px),print{.has-text-left-tablet{text-align:left!important}}@media screen and (min-width:769px) and (max-width:1023px){.has-text-left-tablet-only{text-align:left!important}}@media screen and (max-width:1023px){.has-text-left-touch{text-align:left!important}}@media screen and (min-width:1024px){.has-text-left-desktop{text-align:left!important}}@media screen and (min-width:1024px) and (max-width:1215px){.has-text-left-desktop-only{text-align:left!important}}@media screen and (min-width:1216px){.has-text-left-widescreen{text-align:left!important}}@media screen and (min-width:1216px) and (max-width:1407px){.has-text-left-widescreen-only{text-align:left!important}}@media screen and (min-width:1408px){.has-text-left-fullhd{text-align:left!important}}@media screen and (max-width:768px){.has-text-right-mobile{text-align:right!important}}@media screen and (min-width:769px),print{.has-text-right-tablet{text-align:right!important}}@media screen and (min-width:769px) and (max-width:1023px){.has-text-right-tablet-only{text-align:right!important}}@media screen and (max-width:1023px){.has-text-right-touch{text-align:right!important}}@media screen and (min-width:1024px){.has-text-right-desktop{text-align:right!important}}@media screen and (min-width:1024px) and (max-width:1215px){.has-text-right-desktop-only{text-align:right!important}}@media screen and (min-width:1216px){.has-text-right-widescreen{text-align:right!important}}@media screen and (min-width:1216px) and (max-width:1407px){.has-text-right-widescreen-only{text-align:right!important}}@media screen and (min-width:1408px){.has-text-right-fullhd{text-align:right!important}}.is-capitalized{text-transform:capitalize!important}.is-lowercase{text-transform:lowercase!important}.is-uppercase{text-transform:uppercase!important}.is-italic{font-style:italic!important}.is-underlined{text-decoration:underline!important}.has-text-weight-light{font-weight:300!important}.has-text-weight-normal{font-weight:400!important}.has-text-weight-medium{font-weight:500!important}.has-text-weight-semibold{font-weight:600!important}.has-text-weight-bold{font-weight:700!important}.is-family-primary{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif!important}.is-family-secondary{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif!important}.is-family-sans-serif{font-family:BlinkMacSystemFont,-apple-system,"Segoe UI",Roboto,Oxygen,Ubuntu,Cantarell,"Fira Sans","Droid Sans","Helvetica Neue",Helvetica,Arial,sans-serif!important}.is-family-monospace{font-family:monospace!important}.is-family-code{font-family:monospace!important}.is-block{display:block!important}@media screen and (max-width:768px){.is-block-mobile{display:block!important}}@media screen and (min-width:769px),print{.is-block-tablet{display:block!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-block-tablet-only{display:block!important}}@media screen and (max-width:1023px){.is-block-touch{display:block!important}}@media screen and (min-width:1024px){.is-block-desktop{display:block!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-block-desktop-only{display:block!important}}@media screen and (min-width:1216px){.is-block-widescreen{display:block!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-block-widescreen-only{display:block!important}}@media screen and (min-width:1408px){.is-block-fullhd{display:block!important}}.is-flex{display:flex!important}@media screen and (max-width:768px){.is-flex-mobile{display:flex!important}}@media screen and (min-width:769px),print{.is-flex-tablet{display:flex!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-flex-tablet-only{display:flex!important}}@media screen and (max-width:1023px){.is-flex-touch{display:flex!important}}@media screen and (min-width:1024px){.is-flex-desktop{display:flex!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-flex-desktop-only{display:flex!important}}@media screen and (min-width:1216px){.is-flex-widescreen{display:flex!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-flex-widescreen-only{display:flex!important}}@media screen and (min-width:1408px){.is-flex-fullhd{display:flex!important}}.is-inline{display:inline!important}@media screen and (max-width:768px){.is-inline-mobile{display:inline!important}}@media screen and (min-width:769px),print{.is-inline-tablet{display:inline!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-inline-tablet-only{display:inline!important}}@media screen and (max-width:1023px){.is-inline-touch{display:inline!important}}@media screen and (min-width:1024px){.is-inline-desktop{display:inline!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-inline-desktop-only{display:inline!important}}@media screen and (min-width:1216px){.is-inline-widescreen{display:inline!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-inline-widescreen-only{display:inline!important}}@media screen and (min-width:1408px){.is-inline-fullhd{display:inline!important}}.is-inline-block{display:inline-block!important}@media screen and (max-width:768px){.is-inline-block-mobile{display:inline-block!important}}@media screen and (min-width:769px),print{.is-inline-block-tablet{display:inline-block!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-inline-block-tablet-only{display:inline-block!important}}@media screen and (max-width:1023px){.is-inline-block-touch{display:inline-block!important}}@media screen and (min-width:1024px){.is-inline-block-desktop{display:inline-block!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-inline-block-desktop-only{display:inline-block!important}}@media screen and (min-width:1216px){.is-inline-block-widescreen{display:inline-block!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-inline-block-widescreen-only{display:inline-block!important}}@media screen and (min-width:1408px){.is-inline-block-fullhd{display:inline-block!important}}.is-inline-flex{display:inline-flex!important}@media screen and (max-width:768px){.is-inline-flex-mobile{display:inline-flex!important}}@media screen and (min-width:769px),print{.is-inline-flex-tablet{display:inline-flex!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-inline-flex-tablet-only{display:inline-flex!important}}@media screen and (max-width:1023px){.is-inline-flex-touch{display:inline-flex!important}}@media screen and (min-width:1024px){.is-inline-flex-desktop{display:inline-flex!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-inline-flex-desktop-only{display:inline-flex!important}}@media screen and (min-width:1216px){.is-inline-flex-widescreen{display:inline-flex!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-inline-flex-widescreen-only{display:inline-flex!important}}@media screen and (min-width:1408px){.is-inline-flex-fullhd{display:inline-flex!important}}.is-hidden{display:none!important}.is-sr-only{border:none!important;clip:rect(0,0,0,0)!important;height:.01em!important;overflow:hidden!important;padding:0!important;position:absolute!important;white-space:nowrap!important;width:.01em!important}@media screen and (max-width:768px){.is-hidden-mobile{display:none!important}}@media screen and (min-width:769px),print{.is-hidden-tablet{display:none!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-hidden-tablet-only{display:none!important}}@media screen and (max-width:1023px){.is-hidden-touch{display:none!important}}@media screen and (min-width:1024px){.is-hidden-desktop{display:none!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-hidden-desktop-only{display:none!important}}@media screen and (min-width:1216px){.is-hidden-widescreen{display:none!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-hidden-widescreen-only{display:none!important}}@media screen and (min-width:1408px){.is-hidden-fullhd{display:none!important}}.is-invisible{visibility:hidden!important}@media screen and (max-width:768px){.is-invisible-mobile{visibility:hidden!important}}@media screen and (min-width:769px),print{.is-invisible-tablet{visibility:hidden!important}}@media screen and (min-width:769px) and (max-width:1023px){.is-invisible-tablet-only{visibility:hidden!important}}@media screen and (max-width:1023px){.is-invisible-touch{visibility:hidden!important}}@media screen and (min-width:1024px){.is-invisible-desktop{visibility:hidden!important}}@media screen and (min-width:1024px) and (max-width:1215px){.is-invisible-desktop-only{visibility:hidden!important}}@media screen and (min-width:1216px){.is-invisible-widescreen{visibility:hidden!important}}@media screen and (min-width:1216px) and (max-width:1407px){.is-invisible-widescreen-only{visibility:hidden!important}}@media screen and (min-width:1408px){.is-invisible-fullhd{visibility:hidden!important}}.hero{align-items:stretch;display:flex;flex-direction:column;justify-content:space-between}.hero .navbar{background:0 0}.hero .tabs ul{border-bottom:none}.hero.is-white{background-color:#fff;color:#0a0a0a}.hero.is-white a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-white strong{color:inherit}.hero.is-white .title{color:#0a0a0a}.hero.is-white .subtitle{color:rgba(10,10,10,.9)}.hero.is-white .subtitle a:not(.button),.hero.is-white .subtitle strong{color:#0a0a0a}@media screen and (max-width:1023px){.hero.is-white .navbar-menu{background-color:#fff}}.hero.is-white .navbar-item,.hero.is-white .navbar-link{color:rgba(10,10,10,.7)}.hero.is-white .navbar-link.is-active,.hero.is-white .navbar-link:hover,.hero.is-white a.navbar-item.is-active,.hero.is-white a.navbar-item:hover{background-color:#f2f2f2;color:#0a0a0a}.hero.is-white .tabs a{color:#0a0a0a;opacity:.9}.hero.is-white .tabs a:hover{opacity:1}.hero.is-white .tabs li.is-active a{color:#fff!important;opacity:1}.hero.is-white .tabs.is-boxed a,.hero.is-white .tabs.is-toggle a{color:#0a0a0a}.hero.is-white .tabs.is-boxed a:hover,.hero.is-white .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-white .tabs.is-boxed li.is-active a,.hero.is-white .tabs.is-boxed li.is-active a:hover,.hero.is-white .tabs.is-toggle li.is-active a,.hero.is-white .tabs.is-toggle li.is-active a:hover{background-color:#0a0a0a;border-color:#0a0a0a;color:#fff}.hero.is-white.is-bold{background-image:linear-gradient(141deg,#e6e6e6 0,#fff 71%,#fff 100%)}@media screen and (max-width:768px){.hero.is-white.is-bold .navbar-menu{background-image:linear-gradient(141deg,#e6e6e6 0,#fff 71%,#fff 100%)}}.hero.is-black{background-color:#0a0a0a;color:#fff}.hero.is-black a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-black strong{color:inherit}.hero.is-black .title{color:#fff}.hero.is-black .subtitle{color:rgba(255,255,255,.9)}.hero.is-black .subtitle a:not(.button),.hero.is-black .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-black .navbar-menu{background-color:#0a0a0a}}.hero.is-black .navbar-item,.hero.is-black .navbar-link{color:rgba(255,255,255,.7)}.hero.is-black .navbar-link.is-active,.hero.is-black .navbar-link:hover,.hero.is-black a.navbar-item.is-active,.hero.is-black a.navbar-item:hover{background-color:#000;color:#fff}.hero.is-black .tabs a{color:#fff;opacity:.9}.hero.is-black .tabs a:hover{opacity:1}.hero.is-black .tabs li.is-active a{color:#0a0a0a!important;opacity:1}.hero.is-black .tabs.is-boxed a,.hero.is-black .tabs.is-toggle a{color:#fff}.hero.is-black .tabs.is-boxed a:hover,.hero.is-black .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-black .tabs.is-boxed li.is-active a,.hero.is-black .tabs.is-boxed li.is-active a:hover,.hero.is-black .tabs.is-toggle li.is-active a,.hero.is-black .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#0a0a0a}.hero.is-black.is-bold{background-image:linear-gradient(141deg,#000 0,#0a0a0a 71%,#181616 100%)}@media screen and (max-width:768px){.hero.is-black.is-bold .navbar-menu{background-image:linear-gradient(141deg,#000 0,#0a0a0a 71%,#181616 100%)}}.hero.is-light{background-color:#f5f5f5;color:rgba(0,0,0,.7)}.hero.is-light a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-light strong{color:inherit}.hero.is-light .title{color:rgba(0,0,0,.7)}.hero.is-light .subtitle{color:rgba(0,0,0,.9)}.hero.is-light .subtitle a:not(.button),.hero.is-light .subtitle strong{color:rgba(0,0,0,.7)}@media screen and (max-width:1023px){.hero.is-light .navbar-menu{background-color:#f5f5f5}}.hero.is-light .navbar-item,.hero.is-light .navbar-link{color:rgba(0,0,0,.7)}.hero.is-light .navbar-link.is-active,.hero.is-light .navbar-link:hover,.hero.is-light a.navbar-item.is-active,.hero.is-light a.navbar-item:hover{background-color:#e8e8e8;color:rgba(0,0,0,.7)}.hero.is-light .tabs a{color:rgba(0,0,0,.7);opacity:.9}.hero.is-light .tabs a:hover{opacity:1}.hero.is-light .tabs li.is-active a{color:#f5f5f5!important;opacity:1}.hero.is-light .tabs.is-boxed a,.hero.is-light .tabs.is-toggle a{color:rgba(0,0,0,.7)}.hero.is-light .tabs.is-boxed a:hover,.hero.is-light .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-light .tabs.is-boxed li.is-active a,.hero.is-light .tabs.is-boxed li.is-active a:hover,.hero.is-light .tabs.is-toggle li.is-active a,.hero.is-light .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,.7);border-color:rgba(0,0,0,.7);color:#f5f5f5}.hero.is-light.is-bold{background-image:linear-gradient(141deg,#dfd8d9 0,#f5f5f5 71%,#fff 100%)}@media screen and (max-width:768px){.hero.is-light.is-bold .navbar-menu{background-image:linear-gradient(141deg,#dfd8d9 0,#f5f5f5 71%,#fff 100%)}}.hero.is-dark{background-color:#363636;color:#fff}.hero.is-dark a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-dark strong{color:inherit}.hero.is-dark .title{color:#fff}.hero.is-dark .subtitle{color:rgba(255,255,255,.9)}.hero.is-dark .subtitle a:not(.button),.hero.is-dark .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-dark .navbar-menu{background-color:#363636}}.hero.is-dark .navbar-item,.hero.is-dark .navbar-link{color:rgba(255,255,255,.7)}.hero.is-dark .navbar-link.is-active,.hero.is-dark .navbar-link:hover,.hero.is-dark a.navbar-item.is-active,.hero.is-dark a.navbar-item:hover{background-color:#292929;color:#fff}.hero.is-dark .tabs a{color:#fff;opacity:.9}.hero.is-dark .tabs a:hover{opacity:1}.hero.is-dark .tabs li.is-active a{color:#363636!important;opacity:1}.hero.is-dark .tabs.is-boxed a,.hero.is-dark .tabs.is-toggle a{color:#fff}.hero.is-dark .tabs.is-boxed a:hover,.hero.is-dark .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-dark .tabs.is-boxed li.is-active a,.hero.is-dark .tabs.is-boxed li.is-active a:hover,.hero.is-dark .tabs.is-toggle li.is-active a,.hero.is-dark .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#363636}.hero.is-dark.is-bold{background-image:linear-gradient(141deg,#1f191a 0,#363636 71%,#46403f 100%)}@media screen and (max-width:768px){.hero.is-dark.is-bold .navbar-menu{background-image:linear-gradient(141deg,#1f191a 0,#363636 71%,#46403f 100%)}}.hero.is-primary{background-color:#00d1b2;color:#fff}.hero.is-primary a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-primary strong{color:inherit}.hero.is-primary .title{color:#fff}.hero.is-primary .subtitle{color:rgba(255,255,255,.9)}.hero.is-primary .subtitle a:not(.button),.hero.is-primary .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-primary .navbar-menu{background-color:#00d1b2}}.hero.is-primary .navbar-item,.hero.is-primary .navbar-link{color:rgba(255,255,255,.7)}.hero.is-primary .navbar-link.is-active,.hero.is-primary .navbar-link:hover,.hero.is-primary a.navbar-item.is-active,.hero.is-primary a.navbar-item:hover{background-color:#00b89c;color:#fff}.hero.is-primary .tabs a{color:#fff;opacity:.9}.hero.is-primary .tabs a:hover{opacity:1}.hero.is-primary .tabs li.is-active a{color:#00d1b2!important;opacity:1}.hero.is-primary .tabs.is-boxed a,.hero.is-primary .tabs.is-toggle a{color:#fff}.hero.is-primary .tabs.is-boxed a:hover,.hero.is-primary .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-primary .tabs.is-boxed li.is-active a,.hero.is-primary .tabs.is-boxed li.is-active a:hover,.hero.is-primary .tabs.is-toggle li.is-active a,.hero.is-primary .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#00d1b2}.hero.is-primary.is-bold{background-image:linear-gradient(141deg,#009e6c 0,#00d1b2 71%,#00e7eb 100%)}@media screen and (max-width:768px){.hero.is-primary.is-bold .navbar-menu{background-image:linear-gradient(141deg,#009e6c 0,#00d1b2 71%,#00e7eb 100%)}}.hero.is-link{background-color:#485fc7;color:#fff}.hero.is-link a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-link strong{color:inherit}.hero.is-link .title{color:#fff}.hero.is-link .subtitle{color:rgba(255,255,255,.9)}.hero.is-link .subtitle a:not(.button),.hero.is-link .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-link .navbar-menu{background-color:#485fc7}}.hero.is-link .navbar-item,.hero.is-link .navbar-link{color:rgba(255,255,255,.7)}.hero.is-link .navbar-link.is-active,.hero.is-link .navbar-link:hover,.hero.is-link a.navbar-item.is-active,.hero.is-link a.navbar-item:hover{background-color:#3a51bb;color:#fff}.hero.is-link .tabs a{color:#fff;opacity:.9}.hero.is-link .tabs a:hover{opacity:1}.hero.is-link .tabs li.is-active a{color:#485fc7!important;opacity:1}.hero.is-link .tabs.is-boxed a,.hero.is-link .tabs.is-toggle a{color:#fff}.hero.is-link .tabs.is-boxed a:hover,.hero.is-link .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-link .tabs.is-boxed li.is-active a,.hero.is-link .tabs.is-boxed li.is-active a:hover,.hero.is-link .tabs.is-toggle li.is-active a,.hero.is-link .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#485fc7}.hero.is-link.is-bold{background-image:linear-gradient(141deg,#2959b3 0,#485fc7 71%,#5658d2 100%)}@media screen and (max-width:768px){.hero.is-link.is-bold .navbar-menu{background-image:linear-gradient(141deg,#2959b3 0,#485fc7 71%,#5658d2 100%)}}.hero.is-info{background-color:#3e8ed0;color:#fff}.hero.is-info a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-info strong{color:inherit}.hero.is-info .title{color:#fff}.hero.is-info .subtitle{color:rgba(255,255,255,.9)}.hero.is-info .subtitle a:not(.button),.hero.is-info .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-info .navbar-menu{background-color:#3e8ed0}}.hero.is-info .navbar-item,.hero.is-info .navbar-link{color:rgba(255,255,255,.7)}.hero.is-info .navbar-link.is-active,.hero.is-info .navbar-link:hover,.hero.is-info a.navbar-item.is-active,.hero.is-info a.navbar-item:hover{background-color:#3082c5;color:#fff}.hero.is-info .tabs a{color:#fff;opacity:.9}.hero.is-info .tabs a:hover{opacity:1}.hero.is-info .tabs li.is-active a{color:#3e8ed0!important;opacity:1}.hero.is-info .tabs.is-boxed a,.hero.is-info .tabs.is-toggle a{color:#fff}.hero.is-info .tabs.is-boxed a:hover,.hero.is-info .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-info .tabs.is-boxed li.is-active a,.hero.is-info .tabs.is-boxed li.is-active a:hover,.hero.is-info .tabs.is-toggle li.is-active a,.hero.is-info .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#3e8ed0}.hero.is-info.is-bold{background-image:linear-gradient(141deg,#208fbc 0,#3e8ed0 71%,#4d83db 100%)}@media screen and (max-width:768px){.hero.is-info.is-bold .navbar-menu{background-image:linear-gradient(141deg,#208fbc 0,#3e8ed0 71%,#4d83db 100%)}}.hero.is-success{background-color:#48c78e;color:#fff}.hero.is-success a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-success strong{color:inherit}.hero.is-success .title{color:#fff}.hero.is-success .subtitle{color:rgba(255,255,255,.9)}.hero.is-success .subtitle a:not(.button),.hero.is-success .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-success .navbar-menu{background-color:#48c78e}}.hero.is-success .navbar-item,.hero.is-success .navbar-link{color:rgba(255,255,255,.7)}.hero.is-success .navbar-link.is-active,.hero.is-success .navbar-link:hover,.hero.is-success a.navbar-item.is-active,.hero.is-success a.navbar-item:hover{background-color:#3abb81;color:#fff}.hero.is-success .tabs a{color:#fff;opacity:.9}.hero.is-success .tabs a:hover{opacity:1}.hero.is-success .tabs li.is-active a{color:#48c78e!important;opacity:1}.hero.is-success .tabs.is-boxed a,.hero.is-success .tabs.is-toggle a{color:#fff}.hero.is-success .tabs.is-boxed a:hover,.hero.is-success .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-success .tabs.is-boxed li.is-active a,.hero.is-success .tabs.is-boxed li.is-active a:hover,.hero.is-success .tabs.is-toggle li.is-active a,.hero.is-success .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#48c78e}.hero.is-success.is-bold{background-image:linear-gradient(141deg,#29b35e 0,#48c78e 71%,#56d2af 100%)}@media screen and (max-width:768px){.hero.is-success.is-bold .navbar-menu{background-image:linear-gradient(141deg,#29b35e 0,#48c78e 71%,#56d2af 100%)}}.hero.is-warning{background-color:#ffe08a;color:rgba(0,0,0,.7)}.hero.is-warning a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-warning strong{color:inherit}.hero.is-warning .title{color:rgba(0,0,0,.7)}.hero.is-warning .subtitle{color:rgba(0,0,0,.9)}.hero.is-warning .subtitle a:not(.button),.hero.is-warning .subtitle strong{color:rgba(0,0,0,.7)}@media screen and (max-width:1023px){.hero.is-warning .navbar-menu{background-color:#ffe08a}}.hero.is-warning .navbar-item,.hero.is-warning .navbar-link{color:rgba(0,0,0,.7)}.hero.is-warning .navbar-link.is-active,.hero.is-warning .navbar-link:hover,.hero.is-warning a.navbar-item.is-active,.hero.is-warning a.navbar-item:hover{background-color:#ffd970;color:rgba(0,0,0,.7)}.hero.is-warning .tabs a{color:rgba(0,0,0,.7);opacity:.9}.hero.is-warning .tabs a:hover{opacity:1}.hero.is-warning .tabs li.is-active a{color:#ffe08a!important;opacity:1}.hero.is-warning .tabs.is-boxed a,.hero.is-warning .tabs.is-toggle a{color:rgba(0,0,0,.7)}.hero.is-warning .tabs.is-boxed a:hover,.hero.is-warning .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-warning .tabs.is-boxed li.is-active a,.hero.is-warning .tabs.is-boxed li.is-active a:hover,.hero.is-warning .tabs.is-toggle li.is-active a,.hero.is-warning .tabs.is-toggle li.is-active a:hover{background-color:rgba(0,0,0,.7);border-color:rgba(0,0,0,.7);color:#ffe08a}.hero.is-warning.is-bold{background-image:linear-gradient(141deg,#ffb657 0,#ffe08a 71%,#fff6a3 100%)}@media screen and (max-width:768px){.hero.is-warning.is-bold .navbar-menu{background-image:linear-gradient(141deg,#ffb657 0,#ffe08a 71%,#fff6a3 100%)}}.hero.is-danger{background-color:#f14668;color:#fff}.hero.is-danger a:not(.button):not(.dropdown-item):not(.tag):not(.pagination-link.is-current),.hero.is-danger strong{color:inherit}.hero.is-danger .title{color:#fff}.hero.is-danger .subtitle{color:rgba(255,255,255,.9)}.hero.is-danger .subtitle a:not(.button),.hero.is-danger .subtitle strong{color:#fff}@media screen and (max-width:1023px){.hero.is-danger .navbar-menu{background-color:#f14668}}.hero.is-danger .navbar-item,.hero.is-danger .navbar-link{color:rgba(255,255,255,.7)}.hero.is-danger .navbar-link.is-active,.hero.is-danger .navbar-link:hover,.hero.is-danger a.navbar-item.is-active,.hero.is-danger a.navbar-item:hover{background-color:#ef2e55;color:#fff}.hero.is-danger .tabs a{color:#fff;opacity:.9}.hero.is-danger .tabs a:hover{opacity:1}.hero.is-danger .tabs li.is-active a{color:#f14668!important;opacity:1}.hero.is-danger .tabs.is-boxed a,.hero.is-danger .tabs.is-toggle a{color:#fff}.hero.is-danger .tabs.is-boxed a:hover,.hero.is-danger .tabs.is-toggle a:hover{background-color:rgba(10,10,10,.1)}.hero.is-danger .tabs.is-boxed li.is-active a,.hero.is-danger .tabs.is-boxed li.is-active a:hover,.hero.is-danger .tabs.is-toggle li.is-active a,.hero.is-danger .tabs.is-toggle li.is-active a:hover{background-color:#fff;border-color:#fff;color:#f14668}.hero.is-danger.is-bold{background-image:linear-gradient(141deg,#fa0a62 0,#f14668 71%,#f7595f 100%)}@media screen and (max-width:768px){.hero.is-danger.is-bold .navbar-menu{background-image:linear-gradient(141deg,#fa0a62 0,#f14668 71%,#f7595f 100%)}}.hero.is-small .hero-body{padding:1.5rem}@media screen and (min-width:769px),print{.hero.is-medium .hero-body{padding:9rem 4.5rem}}@media screen and (min-width:769px),print{.hero.is-large .hero-body{padding:18rem 6rem}}.hero.is-fullheight .hero-body,.hero.is-fullheight-with-navbar .hero-body,.hero.is-halfheight .hero-body{align-items:center;display:flex}.hero.is-fullheight .hero-body>.container,.hero.is-fullheight-with-navbar .hero-body>.container,.hero.is-halfheight .hero-body>.container{flex-grow:1;flex-shrink:1}.hero.is-halfheight{min-height:50vh}.hero.is-fullheight{min-height:100vh}.hero-video{overflow:hidden}.hero-video video{left:50%;min-height:100%;min-width:100%;position:absolute;top:50%;transform:translate3d(-50%,-50%,0)}.hero-video.is-transparent{opacity:.3}@media screen and (max-width:768px){.hero-video{display:none}}.hero-buttons{margin-top:1.5rem}@media screen and (max-width:768px){.hero-buttons .button{display:flex}.hero-buttons .button:not(:last-child){margin-bottom:.75rem}}@media screen and (min-width:769px),print{.hero-buttons{display:flex;justify-content:center}.hero-buttons .button:not(:last-child){margin-right:1.5rem}}.hero-foot,.hero-head{flex-grow:0;flex-shrink:0}.hero-body{flex-grow:1;flex-shrink:0;padding:3rem 1.5rem}@media screen and (min-width:769px),print{.hero-body{padding:3rem 3rem}}.section{padding:3rem 1.5rem}@media screen and (min-width:1024px){.section{padding:3rem 3rem}.section.is-medium{padding:9rem 4.5rem}.section.is-large{padding:18rem 6rem}}.footer{background-color:#fafafa;padding:3rem 1.5rem 6rem} +|] + +-- | To avoid run-time dependency from the static content, embed Bulma Tooltip extension in the page's header. +bulmaTooltipCSS :: String +bulmaTooltipCSS = [s| +/*! @creativebulma/bulma-tooltip v1.2.0 | (c) 2020 Gaetan | MIT License | https://github.com/CreativeBulma/bulma-tooltip */ +[data-tooltip]:not(.is-disabled),[data-tooltip]:not(.is-loading),[data-tooltip]:not([disabled]){cursor:pointer;overflow:visible;position:relative}[data-tooltip]:not(.is-disabled):before,[data-tooltip]:not(.is-loading):before,[data-tooltip]:not([disabled]):before{background:rgba(74,74,74,.9);border-radius:2px;content:attr(data-tooltip);padding:.5rem 1rem;text-overflow:ellipsis;white-space:pre-line;right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}[data-tooltip]:not(.is-disabled).has-tooltip-arrow:after,[data-tooltip]:not(.is-disabled):before,[data-tooltip]:not(.is-loading).has-tooltip-arrow:after,[data-tooltip]:not(.is-loading):before,[data-tooltip]:not([disabled]).has-tooltip-arrow:after,[data-tooltip]:not([disabled]):before{box-sizing:border-box;color:#fff;display:inline-block;font-family:BlinkMacSystemFont,-apple-system,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,Helvetica,Arial,sans-serif;font-size:.75rem;-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;opacity:0;overflow:hidden;pointer-events:none;position:absolute;visibility:hidden;z-index:1}[data-tooltip]:not(.is-disabled).has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-arrow:after{content:"";border-style:solid;border-width:6px;border-color:rgba(74,74,74,.9) transparent transparent;margin-bottom:-5px}[data-tooltip]:not(.is-disabled).has-tooltip-arrow.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-arrow.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-arrow.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-bottom.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom:before,[data-tooltip]:not([disabled]).has-tooltip-bottom:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}[data-tooltip]:not(.is-disabled).has-tooltip-left.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left:before,[data-tooltip]:not(.is-loading).has-tooltip-left:before,[data-tooltip]:not([disabled]).has-tooltip-left:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}[data-tooltip]:not(.is-disabled).has-tooltip-right.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right:before,[data-tooltip]:not(.is-loading).has-tooltip-right:before,[data-tooltip]:not([disabled]).has-tooltip-right:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}[data-tooltip]:not(.is-disabled).has-tooltip-multiline:before,[data-tooltip]:not(.is-loading).has-tooltip-multiline:before,[data-tooltip]:not([disabled]).has-tooltip-multiline:before{height:auto;width:15rem;max-width:15rem;text-overflow:clip;white-space:normal;word-break:keep-all}[data-tooltip]:not(.is-disabled).has-tooltip-text-left:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left:before,[data-tooltip]:not([disabled]).has-tooltip-text-left:before{text-align:left}[data-tooltip]:not(.is-disabled).has-tooltip-text-centered:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered:before{text-align:center}[data-tooltip]:not(.is-disabled).has-tooltip-text-right:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right:before,[data-tooltip]:not([disabled]).has-tooltip-text-right:before{text-align:right}[data-tooltip]:not(.is-disabled).has-tooltip-white:after,[data-tooltip]:not(.is-loading).has-tooltip-white:after,[data-tooltip]:not([disabled]).has-tooltip-white:after{border-color:hsla(0,0%,100%,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-white.has-tooltip-bottom:after,[data-tooltip]:not(.is-loading).has-tooltip-white.has-tooltip-bottom:after,[data-tooltip]:not([disabled]).has-tooltip-white.has-tooltip-bottom:after{border-color:transparent transparent hsla(0,0%,100%,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-white.has-tooltip-left:after,[data-tooltip]:not(.is-loading).has-tooltip-white.has-tooltip-left:after,[data-tooltip]:not([disabled]).has-tooltip-white.has-tooltip-left:after{border-color:transparent transparent transparent hsla(0,0%,100%,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-white.has-tooltip-right:after,[data-tooltip]:not(.is-loading).has-tooltip-white.has-tooltip-right:after,[data-tooltip]:not([disabled]).has-tooltip-white.has-tooltip-right:after{border-color:transparent hsla(0,0%,100%,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-white:before,[data-tooltip]:not(.is-loading).has-tooltip-white:before,[data-tooltip]:not([disabled]).has-tooltip-white:before{background-color:hsla(0,0%,100%,.9);color:#0a0a0a}[data-tooltip]:not(.is-disabled).has-tooltip-black:after,[data-tooltip]:not(.is-loading).has-tooltip-black:after,[data-tooltip]:not([disabled]).has-tooltip-black:after{border-color:hsla(0,0%,4%,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-black.has-tooltip-bottom:after,[data-tooltip]:not(.is-loading).has-tooltip-black.has-tooltip-bottom:after,[data-tooltip]:not([disabled]).has-tooltip-black.has-tooltip-bottom:after{border-color:transparent transparent hsla(0,0%,4%,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-black.has-tooltip-left:after,[data-tooltip]:not(.is-loading).has-tooltip-black.has-tooltip-left:after,[data-tooltip]:not([disabled]).has-tooltip-black.has-tooltip-left:after{border-color:transparent transparent transparent hsla(0,0%,4%,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-black.has-tooltip-right:after,[data-tooltip]:not(.is-loading).has-tooltip-black.has-tooltip-right:after,[data-tooltip]:not([disabled]).has-tooltip-black.has-tooltip-right:after{border-color:transparent hsla(0,0%,4%,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-black:before,[data-tooltip]:not(.is-loading).has-tooltip-black:before,[data-tooltip]:not([disabled]).has-tooltip-black:before{background-color:hsla(0,0%,4%,.9);color:#fff}[data-tooltip]:not(.is-disabled).has-tooltip-light:after,[data-tooltip]:not(.is-loading).has-tooltip-light:after,[data-tooltip]:not([disabled]).has-tooltip-light:after{border-color:hsla(0,0%,96%,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-light.has-tooltip-bottom:after,[data-tooltip]:not(.is-loading).has-tooltip-light.has-tooltip-bottom:after,[data-tooltip]:not([disabled]).has-tooltip-light.has-tooltip-bottom:after{border-color:transparent transparent hsla(0,0%,96%,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-light.has-tooltip-left:after,[data-tooltip]:not(.is-loading).has-tooltip-light.has-tooltip-left:after,[data-tooltip]:not([disabled]).has-tooltip-light.has-tooltip-left:after{border-color:transparent transparent transparent hsla(0,0%,96%,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-light.has-tooltip-right:after,[data-tooltip]:not(.is-loading).has-tooltip-light.has-tooltip-right:after,[data-tooltip]:not([disabled]).has-tooltip-light.has-tooltip-right:after{border-color:transparent hsla(0,0%,96%,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-light:before,[data-tooltip]:not(.is-loading).has-tooltip-light:before,[data-tooltip]:not([disabled]).has-tooltip-light:before{background-color:hsla(0,0%,96%,.9);color:rgba(0,0,0,.7)}[data-tooltip]:not(.is-disabled).has-tooltip-dark:after,[data-tooltip]:not(.is-loading).has-tooltip-dark:after,[data-tooltip]:not([disabled]).has-tooltip-dark:after{border-color:rgba(54,54,54,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-dark.has-tooltip-bottom:after,[data-tooltip]:not(.is-loading).has-tooltip-dark.has-tooltip-bottom:after,[data-tooltip]:not([disabled]).has-tooltip-dark.has-tooltip-bottom:after{border-color:transparent transparent rgba(54,54,54,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-dark.has-tooltip-left:after,[data-tooltip]:not(.is-loading).has-tooltip-dark.has-tooltip-left:after,[data-tooltip]:not([disabled]).has-tooltip-dark.has-tooltip-left:after{border-color:transparent transparent transparent rgba(54,54,54,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-dark.has-tooltip-right:after,[data-tooltip]:not(.is-loading).has-tooltip-dark.has-tooltip-right:after,[data-tooltip]:not([disabled]).has-tooltip-dark.has-tooltip-right:after{border-color:transparent rgba(54,54,54,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-dark:before,[data-tooltip]:not(.is-loading).has-tooltip-dark:before,[data-tooltip]:not([disabled]).has-tooltip-dark:before{background-color:rgba(54,54,54,.9);color:#fff}[data-tooltip]:not(.is-disabled).has-tooltip-primary:after,[data-tooltip]:not(.is-loading).has-tooltip-primary:after,[data-tooltip]:not([disabled]).has-tooltip-primary:after{border-color:rgba(0,209,178,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-primary.has-tooltip-bottom:after,[data-tooltip]:not(.is-loading).has-tooltip-primary.has-tooltip-bottom:after,[data-tooltip]:not([disabled]).has-tooltip-primary.has-tooltip-bottom:after{border-color:transparent transparent rgba(0,209,178,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-primary.has-tooltip-left:after,[data-tooltip]:not(.is-loading).has-tooltip-primary.has-tooltip-left:after,[data-tooltip]:not([disabled]).has-tooltip-primary.has-tooltip-left:after{border-color:transparent transparent transparent rgba(0,209,178,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-primary.has-tooltip-right:after,[data-tooltip]:not(.is-loading).has-tooltip-primary.has-tooltip-right:after,[data-tooltip]:not([disabled]).has-tooltip-primary.has-tooltip-right:after{border-color:transparent rgba(0,209,178,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-primary:before,[data-tooltip]:not(.is-loading).has-tooltip-primary:before,[data-tooltip]:not([disabled]).has-tooltip-primary:before{background-color:rgba(0,209,178,.9);color:#fff}[data-tooltip]:not(.is-disabled).has-tooltip-link:after,[data-tooltip]:not(.is-loading).has-tooltip-link:after,[data-tooltip]:not([disabled]).has-tooltip-link:after{border-color:rgba(50,115,220,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-link.has-tooltip-bottom:after,[data-tooltip]:not(.is-loading).has-tooltip-link.has-tooltip-bottom:after,[data-tooltip]:not([disabled]).has-tooltip-link.has-tooltip-bottom:after{border-color:transparent transparent rgba(50,115,220,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-link.has-tooltip-left:after,[data-tooltip]:not(.is-loading).has-tooltip-link.has-tooltip-left:after,[data-tooltip]:not([disabled]).has-tooltip-link.has-tooltip-left:after{border-color:transparent transparent transparent rgba(50,115,220,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-link.has-tooltip-right:after,[data-tooltip]:not(.is-loading).has-tooltip-link.has-tooltip-right:after,[data-tooltip]:not([disabled]).has-tooltip-link.has-tooltip-right:after{border-color:transparent rgba(50,115,220,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-link:before,[data-tooltip]:not(.is-loading).has-tooltip-link:before,[data-tooltip]:not([disabled]).has-tooltip-link:before{background-color:rgba(50,115,220,.9);color:#fff}[data-tooltip]:not(.is-disabled).has-tooltip-info:after,[data-tooltip]:not(.is-loading).has-tooltip-info:after,[data-tooltip]:not([disabled]).has-tooltip-info:after{border-color:rgba(50,152,220,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-info.has-tooltip-bottom:after,[data-tooltip]:not(.is-loading).has-tooltip-info.has-tooltip-bottom:after,[data-tooltip]:not([disabled]).has-tooltip-info.has-tooltip-bottom:after{border-color:transparent transparent rgba(50,152,220,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-info.has-tooltip-left:after,[data-tooltip]:not(.is-loading).has-tooltip-info.has-tooltip-left:after,[data-tooltip]:not([disabled]).has-tooltip-info.has-tooltip-left:after{border-color:transparent transparent transparent rgba(50,152,220,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-info.has-tooltip-right:after,[data-tooltip]:not(.is-loading).has-tooltip-info.has-tooltip-right:after,[data-tooltip]:not([disabled]).has-tooltip-info.has-tooltip-right:after{border-color:transparent rgba(50,152,220,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-info:before,[data-tooltip]:not(.is-loading).has-tooltip-info:before,[data-tooltip]:not([disabled]).has-tooltip-info:before{background-color:rgba(50,152,220,.9);color:#fff}[data-tooltip]:not(.is-disabled).has-tooltip-success:after,[data-tooltip]:not(.is-loading).has-tooltip-success:after,[data-tooltip]:not([disabled]).has-tooltip-success:after{border-color:rgba(72,199,116,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-success.has-tooltip-bottom:after,[data-tooltip]:not(.is-loading).has-tooltip-success.has-tooltip-bottom:after,[data-tooltip]:not([disabled]).has-tooltip-success.has-tooltip-bottom:after{border-color:transparent transparent rgba(72,199,116,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-success.has-tooltip-left:after,[data-tooltip]:not(.is-loading).has-tooltip-success.has-tooltip-left:after,[data-tooltip]:not([disabled]).has-tooltip-success.has-tooltip-left:after{border-color:transparent transparent transparent rgba(72,199,116,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-success.has-tooltip-right:after,[data-tooltip]:not(.is-loading).has-tooltip-success.has-tooltip-right:after,[data-tooltip]:not([disabled]).has-tooltip-success.has-tooltip-right:after{border-color:transparent rgba(72,199,116,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-success:before,[data-tooltip]:not(.is-loading).has-tooltip-success:before,[data-tooltip]:not([disabled]).has-tooltip-success:before{background-color:rgba(72,199,116,.9);color:#fff}[data-tooltip]:not(.is-disabled).has-tooltip-warning:after,[data-tooltip]:not(.is-loading).has-tooltip-warning:after,[data-tooltip]:not([disabled]).has-tooltip-warning:after{border-color:rgba(255,221,87,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-warning.has-tooltip-bottom:after,[data-tooltip]:not(.is-loading).has-tooltip-warning.has-tooltip-bottom:after,[data-tooltip]:not([disabled]).has-tooltip-warning.has-tooltip-bottom:after{border-color:transparent transparent rgba(255,221,87,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-warning.has-tooltip-left:after,[data-tooltip]:not(.is-loading).has-tooltip-warning.has-tooltip-left:after,[data-tooltip]:not([disabled]).has-tooltip-warning.has-tooltip-left:after{border-color:transparent transparent transparent rgba(255,221,87,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-warning.has-tooltip-right:after,[data-tooltip]:not(.is-loading).has-tooltip-warning.has-tooltip-right:after,[data-tooltip]:not([disabled]).has-tooltip-warning.has-tooltip-right:after{border-color:transparent rgba(255,221,87,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-warning:before,[data-tooltip]:not(.is-loading).has-tooltip-warning:before,[data-tooltip]:not([disabled]).has-tooltip-warning:before{background-color:rgba(255,221,87,.9);color:rgba(0,0,0,.7)}[data-tooltip]:not(.is-disabled).has-tooltip-danger:after,[data-tooltip]:not(.is-loading).has-tooltip-danger:after,[data-tooltip]:not([disabled]).has-tooltip-danger:after{border-color:rgba(241,70,104,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-danger.has-tooltip-bottom:after,[data-tooltip]:not(.is-loading).has-tooltip-danger.has-tooltip-bottom:after,[data-tooltip]:not([disabled]).has-tooltip-danger.has-tooltip-bottom:after{border-color:transparent transparent rgba(241,70,104,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-danger.has-tooltip-left:after,[data-tooltip]:not(.is-loading).has-tooltip-danger.has-tooltip-left:after,[data-tooltip]:not([disabled]).has-tooltip-danger.has-tooltip-left:after{border-color:transparent transparent transparent rgba(241,70,104,.9)!important}[data-tooltip]:not(.is-disabled).has-tooltip-danger.has-tooltip-right:after,[data-tooltip]:not(.is-loading).has-tooltip-danger.has-tooltip-right:after,[data-tooltip]:not([disabled]).has-tooltip-danger.has-tooltip-right:after{border-color:transparent rgba(241,70,104,.9) transparent transparent!important}[data-tooltip]:not(.is-disabled).has-tooltip-danger:before,[data-tooltip]:not(.is-loading).has-tooltip-danger:before,[data-tooltip]:not([disabled]).has-tooltip-danger:before{background-color:rgba(241,70,104,.9);color:#fff}[data-tooltip]:not(.is-disabled).has-tooltip-active:after,[data-tooltip]:not(.is-disabled).has-tooltip-active:before,[data-tooltip]:not(.is-disabled):hover:after,[data-tooltip]:not(.is-disabled):hover:before,[data-tooltip]:not(.is-loading).has-tooltip-active:after,[data-tooltip]:not(.is-loading).has-tooltip-active:before,[data-tooltip]:not(.is-loading):hover:after,[data-tooltip]:not(.is-loading):hover:before,[data-tooltip]:not([disabled]).has-tooltip-active:after,[data-tooltip]:not([disabled]).has-tooltip-active:before,[data-tooltip]:not([disabled]):hover:after,[data-tooltip]:not([disabled]):hover:before{opacity:1;visibility:visible}[data-tooltip]:not(.is-disabled).has-tooltip-fade:after,[data-tooltip]:not(.is-disabled).has-tooltip-fade:before,[data-tooltip]:not(.is-loading).has-tooltip-fade:after,[data-tooltip]:not(.is-loading).has-tooltip-fade:before,[data-tooltip]:not([disabled]).has-tooltip-fade:after,[data-tooltip]:not([disabled]).has-tooltip-fade:before{transition:opacity .3s linear,visibility .3s linear}@media screen and (max-width:768px){[data-tooltip]:not(.is-disabled).has-tooltip-top-mobile.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-mobile.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-mobile.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-mobile:before,[data-tooltip]:not(.is-loading).has-tooltip-top-mobile:before,[data-tooltip]:not([disabled]).has-tooltip-top-mobile:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media print,screen and (min-width:769px){[data-tooltip]:not(.is-disabled).has-tooltip-top-tablet.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-tablet.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-tablet.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-tablet:before,[data-tooltip]:not(.is-loading).has-tooltip-top-tablet:before,[data-tooltip]:not([disabled]).has-tooltip-top-tablet:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media screen and (min-width:769px) and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-top-tablet-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-tablet-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-tablet-only.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-tablet-only:before,[data-tooltip]:not(.is-loading).has-tooltip-top-tablet-only:before,[data-tooltip]:not([disabled]).has-tooltip-top-tablet-only:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media screen and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-top-touch.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-touch.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-touch.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-touch:before,[data-tooltip]:not(.is-loading).has-tooltip-top-touch:before,[data-tooltip]:not([disabled]).has-tooltip-top-touch:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media screen and (min-width:1024px){[data-tooltip]:not(.is-disabled).has-tooltip-top-desktop.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-desktop.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-desktop.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-desktop:before,[data-tooltip]:not(.is-loading).has-tooltip-top-desktop:before,[data-tooltip]:not([disabled]).has-tooltip-top-desktop:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media screen and (min-width:1024px) and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-top-desktop-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-desktop-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-desktop-only.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-desktop-only:before,[data-tooltip]:not(.is-loading).has-tooltip-top-desktop-only:before,[data-tooltip]:not([disabled]).has-tooltip-top-desktop-only:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media screen and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-top-until-widescreen.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-until-widescreen.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-until-widescreen.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-until-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-top-until-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-top-until-widescreen:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media screen and (min-width:1216px){[data-tooltip]:not(.is-disabled).has-tooltip-top-widescreen.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-widescreen.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-widescreen.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-top-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-top-widescreen:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media screen and (min-width:1216px) and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-top-widescreen-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-widescreen-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-widescreen-only.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-widescreen-only:before,[data-tooltip]:not(.is-loading).has-tooltip-top-widescreen-only:before,[data-tooltip]:not([disabled]).has-tooltip-top-widescreen-only:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media screen and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-top-until-fullhd.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-until-fullhd.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-until-fullhd.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-until-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-top-until-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-top-until-fullhd:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media screen and (min-width:1408px){[data-tooltip]:not(.is-disabled).has-tooltip-top-fullhd.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-top-fullhd.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-top-fullhd.has-tooltip-arrow:after{top:0;right:auto;bottom:auto;left:50%;margin:-5px auto auto -5px;border-color:rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-top-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-top-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-top-fullhd:before{right:auto;bottom:auto;left:50%;top:0;margin-top:-5px;margin-bottom:auto;transform:translate(-50%,-100%)}}@media screen and (max-width:768px){[data-tooltip]:not(.is-disabled).has-tooltip-right-mobile.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-mobile.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-mobile.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-mobile:before,[data-tooltip]:not(.is-loading).has-tooltip-right-mobile:before,[data-tooltip]:not([disabled]).has-tooltip-right-mobile:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media print,screen and (min-width:769px){[data-tooltip]:not(.is-disabled).has-tooltip-right-tablet.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-tablet.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-tablet.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-tablet:before,[data-tooltip]:not(.is-loading).has-tooltip-right-tablet:before,[data-tooltip]:not([disabled]).has-tooltip-right-tablet:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media screen and (min-width:769px) and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-right-tablet-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-tablet-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-tablet-only.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-tablet-only:before,[data-tooltip]:not(.is-loading).has-tooltip-right-tablet-only:before,[data-tooltip]:not([disabled]).has-tooltip-right-tablet-only:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media screen and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-right-touch.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-touch.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-touch.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-touch:before,[data-tooltip]:not(.is-loading).has-tooltip-right-touch:before,[data-tooltip]:not([disabled]).has-tooltip-right-touch:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media screen and (min-width:1024px){[data-tooltip]:not(.is-disabled).has-tooltip-right-desktop.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-desktop.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-desktop.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-desktop:before,[data-tooltip]:not(.is-loading).has-tooltip-right-desktop:before,[data-tooltip]:not([disabled]).has-tooltip-right-desktop:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media screen and (min-width:1024px) and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-right-desktop-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-desktop-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-desktop-only.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-desktop-only:before,[data-tooltip]:not(.is-loading).has-tooltip-right-desktop-only:before,[data-tooltip]:not([disabled]).has-tooltip-right-desktop-only:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media screen and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-right-until-widescreen.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-until-widescreen.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-until-widescreen.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-until-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-right-until-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-right-until-widescreen:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media screen and (min-width:1216px){[data-tooltip]:not(.is-disabled).has-tooltip-right-widescreen.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-widescreen.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-widescreen.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-right-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-right-widescreen:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media screen and (min-width:1216px) and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-right-widescreen-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-widescreen-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-widescreen-only.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-widescreen-only:before,[data-tooltip]:not(.is-loading).has-tooltip-right-widescreen-only:before,[data-tooltip]:not([disabled]).has-tooltip-right-widescreen-only:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media screen and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-right-until-fullhd.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-until-fullhd.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-until-fullhd.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-until-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-right-until-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-right-until-fullhd:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media screen and (min-width:1408px){[data-tooltip]:not(.is-disabled).has-tooltip-right-fullhd.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-right-fullhd.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-right-fullhd.has-tooltip-arrow:after{top:auto;right:0;bottom:50%;left:auto;margin:auto -6px -6px auto;border-color:transparent rgba(74,74,74,.9) transparent transparent}[data-tooltip]:not(.is-disabled).has-tooltip-right-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-right-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-right-fullhd:before{top:auto;right:-5px;bottom:50%;left:auto;margin-top:auto;transform:translate(100%,50%)}}@media screen and (max-width:768px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-mobile.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-mobile.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-mobile.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-mobile:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-mobile:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-mobile:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media print,screen and (min-width:769px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-tablet.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-tablet.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-tablet.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-tablet:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-tablet:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-tablet:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media screen and (min-width:769px) and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-tablet-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-tablet-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-tablet-only.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-tablet-only:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-tablet-only:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-tablet-only:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media screen and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-touch.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-touch.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-touch.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-touch:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-touch:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-touch:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media screen and (min-width:1024px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-desktop.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-desktop.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-desktop.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-desktop:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-desktop:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-desktop:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media screen and (min-width:1024px) and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-desktop-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-desktop-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-desktop-only.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-desktop-only:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-desktop-only:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-desktop-only:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media screen and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-until-widescreen.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-until-widescreen.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-until-widescreen.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-until-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-until-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-until-widescreen:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media screen and (min-width:1216px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-widescreen.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-widescreen.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-widescreen.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-widescreen:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media screen and (min-width:1216px) and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-widescreen-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-widescreen-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-widescreen-only.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-widescreen-only:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-widescreen-only:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-widescreen-only:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media screen and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-until-fullhd.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-until-fullhd.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-until-fullhd.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-until-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-until-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-until-fullhd:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media screen and (min-width:1408px){[data-tooltip]:not(.is-disabled).has-tooltip-bottom-fullhd.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-bottom-fullhd.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-bottom-fullhd.has-tooltip-arrow:after{top:auto;right:auto;bottom:-1px;left:50%;margin:auto auto -5px -5px;border-color:transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-bottom-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-bottom-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-bottom-fullhd:before{top:auto;right:auto;bottom:0;left:50%;margin-top:auto;margin-bottom:-5px;transform:translate(-50%,100%)}}@media screen and (max-width:768px){[data-tooltip]:not(.is-disabled).has-tooltip-left-mobile.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-mobile.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-mobile.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-mobile:before,[data-tooltip]:not(.is-loading).has-tooltip-left-mobile:before,[data-tooltip]:not([disabled]).has-tooltip-left-mobile:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media print,screen and (min-width:769px){[data-tooltip]:not(.is-disabled).has-tooltip-left-tablet.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-tablet.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-tablet.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-tablet:before,[data-tooltip]:not(.is-loading).has-tooltip-left-tablet:before,[data-tooltip]:not([disabled]).has-tooltip-left-tablet:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media screen and (min-width:769px) and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-left-tablet-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-tablet-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-tablet-only.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-tablet-only:before,[data-tooltip]:not(.is-loading).has-tooltip-left-tablet-only:before,[data-tooltip]:not([disabled]).has-tooltip-left-tablet-only:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media screen and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-left-touch.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-touch.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-touch.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-touch:before,[data-tooltip]:not(.is-loading).has-tooltip-left-touch:before,[data-tooltip]:not([disabled]).has-tooltip-left-touch:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media screen and (min-width:1024px){[data-tooltip]:not(.is-disabled).has-tooltip-left-desktop.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-desktop.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-desktop.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-desktop:before,[data-tooltip]:not(.is-loading).has-tooltip-left-desktop:before,[data-tooltip]:not([disabled]).has-tooltip-left-desktop:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media screen and (min-width:1024px) and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-left-desktop-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-desktop-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-desktop-only.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-desktop-only:before,[data-tooltip]:not(.is-loading).has-tooltip-left-desktop-only:before,[data-tooltip]:not([disabled]).has-tooltip-left-desktop-only:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media screen and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-left-until-widescreen.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-until-widescreen.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-until-widescreen.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-until-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-left-until-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-left-until-widescreen:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media screen and (min-width:1216px){[data-tooltip]:not(.is-disabled).has-tooltip-left-widescreen.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-widescreen.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-widescreen.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-left-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-left-widescreen:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media screen and (min-width:1216px) and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-left-widescreen-only.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-widescreen-only.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-widescreen-only.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-widescreen-only:before,[data-tooltip]:not(.is-loading).has-tooltip-left-widescreen-only:before,[data-tooltip]:not([disabled]).has-tooltip-left-widescreen-only:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media screen and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-left-until-fullhd.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-until-fullhd.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-until-fullhd.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-until-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-left-until-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-left-until-fullhd:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media screen and (min-width:1408px){[data-tooltip]:not(.is-disabled).has-tooltip-left-fullhd.has-tooltip-arrow:after,[data-tooltip]:not(.is-loading).has-tooltip-left-fullhd.has-tooltip-arrow:after,[data-tooltip]:not([disabled]).has-tooltip-left-fullhd.has-tooltip-arrow:after{top:auto;right:auto;bottom:50%;left:0;margin:auto auto -6px -5px;border-color:transparent transparent transparent rgba(74,74,74,.9)}[data-tooltip]:not(.is-disabled).has-tooltip-left-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-left-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-left-fullhd:before{top:auto;right:auto;bottom:50%;left:-5px;transform:translate(-100%,50%)}}@media screen and (max-width:768px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-mobile:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-mobile:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-mobile:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-mobile:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-mobile:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-mobile:before{opacity:0!important;display:none!important}}@media print,screen and (min-width:769px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-tablet:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-tablet:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-tablet:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-tablet:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-tablet:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-tablet:before{opacity:0!important;display:none!important}}@media screen and (min-width:769px) and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-tablet-only:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-tablet-only:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-tablet-only:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-tablet-only:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-tablet-only:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-tablet-only:before{opacity:0!important;display:none!important}}@media screen and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-touch:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-touch:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-touch:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-touch:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-touch:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-touch:before{opacity:0!important;display:none!important}}@media screen and (min-width:1024px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-desktop:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-desktop:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-desktop:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-desktop:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-desktop:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-desktop:before{opacity:0!important;display:none!important}}@media screen and (min-width:1024px) and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-desktop-only:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-desktop-only:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-desktop-only:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-desktop-only:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-desktop-only:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-desktop-only:before{opacity:0!important;display:none!important}}@media screen and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-until-widescreen:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-until-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-until-widescreen:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-until-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-until-widescreen:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-until-widescreen:before{opacity:0!important;display:none!important}}@media screen and (min-width:1216px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-widescreen:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-widescreen:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-widescreen:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-widescreen:before{opacity:0!important;display:none!important}}@media screen and (min-width:1216px) and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-widescreen-only:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-widescreen-only:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-widescreen-only:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-widescreen-only:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-widescreen-only:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-widescreen-only:before{opacity:0!important;display:none!important}}@media screen and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-until-fullhd:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-until-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-until-fullhd:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-until-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-until-fullhd:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-until-fullhd:before{opacity:0!important;display:none!important}}@media screen and (min-width:1408px){[data-tooltip]:not(.is-disabled).has-tooltip-hidden-fullhd:after,[data-tooltip]:not(.is-disabled).has-tooltip-hidden-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-hidden-fullhd:after,[data-tooltip]:not(.is-loading).has-tooltip-hidden-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-hidden-fullhd:after,[data-tooltip]:not([disabled]).has-tooltip-hidden-fullhd:before{opacity:0!important;display:none!important}}@media screen and (max-width:768px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-mobile:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-mobile:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-mobile:before{text-align:left}}@media print,screen and (min-width:769px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-tablet:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-tablet:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-tablet:before{text-align:left}}@media screen and (min-width:769px) and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-tablet-only:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-tablet-only:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-tablet-only:before{text-align:left}}@media screen and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-touch:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-touch:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-touch:before{text-align:left}}@media screen and (min-width:1024px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-desktop:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-desktop:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-desktop:before{text-align:left}}@media screen and (min-width:1024px) and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-desktop-only:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-desktop-only:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-desktop-only:before{text-align:left}}@media screen and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-until-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-until-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-until-widescreen:before{text-align:left}}@media screen and (min-width:1216px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-widescreen:before{text-align:left}}@media screen and (min-width:1216px) and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-widescreen-only:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-widescreen-only:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-widescreen-only:before{text-align:left}}@media screen and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-until-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-until-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-until-fullhd:before{text-align:left}}@media screen and (min-width:1408px){[data-tooltip]:not(.is-disabled).has-tooltip-text-left-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-text-left-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-text-left-fullhd:before{text-align:left}}@media screen and (max-width:768px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-mobile:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-mobile:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-mobile:before{text-align:center}}@media print,screen and (min-width:769px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-tablet:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-tablet:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-tablet:before{text-align:center}}@media screen and (min-width:769px) and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-tablet-only:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-tablet-only:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-tablet-only:before{text-align:center}}@media screen and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-touch:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-touch:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-touch:before{text-align:center}}@media screen and (min-width:1024px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-desktop:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-desktop:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-desktop:before{text-align:center}}@media screen and (min-width:1024px) and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-desktop-only:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-desktop-only:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-desktop-only:before{text-align:center}}@media screen and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-until-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-until-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-until-widescreen:before{text-align:center}}@media screen and (min-width:1216px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-widescreen:before{text-align:center}}@media screen and (min-width:1216px) and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-widescreen-only:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-widescreen-only:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-widescreen-only:before{text-align:center}}@media screen and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-until-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-until-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-until-fullhd:before{text-align:center}}@media screen and (min-width:1408px){[data-tooltip]:not(.is-disabled).has-tooltip-text-centered-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-text-centered-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-text-centered-fullhd:before{text-align:center}}@media screen and (max-width:768px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-mobile:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-mobile:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-mobile:before{text-align:right}}@media print,screen and (min-width:769px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-tablet:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-tablet:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-tablet:before{text-align:right}}@media screen and (min-width:769px) and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-tablet-only:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-tablet-only:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-tablet-only:before{text-align:right}}@media screen and (max-width:1023px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-touch:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-touch:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-touch:before{text-align:right}}@media screen and (min-width:1024px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-desktop:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-desktop:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-desktop:before{text-align:right}}@media screen and (min-width:1024px) and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-desktop-only:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-desktop-only:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-desktop-only:before{text-align:right}}@media screen and (max-width:1215px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-until-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-until-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-until-widescreen:before{text-align:right}}@media screen and (min-width:1216px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-widescreen:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-widescreen:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-widescreen:before{text-align:right}}@media screen and (min-width:1216px) and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-widescreen-only:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-widescreen-only:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-widescreen-only:before{text-align:right}}@media screen and (max-width:1407px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-until-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-until-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-until-fullhd:before{text-align:right}}@media screen and (min-width:1408px){[data-tooltip]:not(.is-disabled).has-tooltip-text-right-fullhd:before,[data-tooltip]:not(.is-loading).has-tooltip-text-right-fullhd:before,[data-tooltip]:not([disabled]).has-tooltip-text-right-fullhd:before{text-align:right}}span[data-tooltip]{border-bottom:1px dashed #dbdbdb}span[data-tooltip].has-tooltip-white{border-bottom-color:#fff}span[data-tooltip].has-tooltip-black{border-bottom-color:#171717}span[data-tooltip].has-tooltip-light{border-bottom-color:#fff}span[data-tooltip].has-tooltip-dark{border-bottom-color:#424242}span[data-tooltip].has-tooltip-primary{border-bottom-color:#00ebc7}span[data-tooltip].has-tooltip-link{border-bottom-color:#4882e0}span[data-tooltip].has-tooltip-info{border-bottom-color:#48a3e0}span[data-tooltip].has-tooltip-success{border-bottom-color:#5bcd83}span[data-tooltip].has-tooltip-warning{border-bottom-color:#ffe270}span[data-tooltip].has-tooltip-danger{border-bottom-color:#f35e7c}.control span[data-tooltip]{border-bottom:none} +|] + +bulmaPageloaderCSS :: String +bulmaPageloaderCSS = [s| +@-webkit-keyframes spinAround{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes spinAround{from{-webkit-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.pageloader{bottom:0;left:0;position:absolute;right:0;top:0}.pageloader{position:fixed;padding-top:2em;background:#00d1b2;background:#00d1b2;z-index:999998;transition:transform .35s ease-out,-webkit-transform .35s ease-out;will-change:transform}.pageloader.is-white{background-color:#fff;background:#fff}.pageloader.is-white::after{border-color:#0a0a0a;-webkit-animation:loader-figure-white 1.15s infinite cubic-bezier(.215,.61,.355,1);animation:loader-figure-white 1.15s infinite cubic-bezier(.215,.61,.355,1)}.pageloader.is-white .title{color:#0a0a0a}.pageloader.is-black{background-color:#0a0a0a;background:#0a0a0a}.pageloader.is-black::after{border-color:#fff;-webkit-animation:loader-figure-black 1.15s infinite cubic-bezier(.215,.61,.355,1);animation:loader-figure-black 1.15s infinite cubic-bezier(.215,.61,.355,1)}.pageloader.is-black .title{color:#fff}.pageloader.is-light{background-color:#f5f5f5;background:#f5f5f5}.pageloader.is-light::after{border-color:#363636;-webkit-animation:loader-figure-light 1.15s infinite cubic-bezier(.215,.61,.355,1);animation:loader-figure-light 1.15s infinite cubic-bezier(.215,.61,.355,1)}.pageloader.is-light .title{color:#363636}.pageloader.is-dark{background-color:#363636;background:#363636}.pageloader.is-dark::after{border-color:#f5f5f5;-webkit-animation:loader-figure-dark 1.15s infinite cubic-bezier(.215,.61,.355,1);animation:loader-figure-dark 1.15s infinite cubic-bezier(.215,.61,.355,1)}.pageloader.is-dark .title{color:#f5f5f5}.pageloader.is-primary{background-color:#00d1b2;background:#00d1b2}.pageloader.is-primary::after{border-color:#fff;-webkit-animation:loader-figure-primary 1.15s infinite cubic-bezier(.215,.61,.355,1);animation:loader-figure-primary 1.15s infinite cubic-bezier(.215,.61,.355,1)}.pageloader.is-primary .title{color:#fff}.pageloader.is-link{background-color:#3273dc;background:#3273dc}.pageloader.is-link::after{border-color:#fff;-webkit-animation:loader-figure-link 1.15s infinite cubic-bezier(.215,.61,.355,1);animation:loader-figure-link 1.15s infinite cubic-bezier(.215,.61,.355,1)}.pageloader.is-link .title{color:#fff}.pageloader.is-info{background-color:#209cee;background:#209cee}.pageloader.is-info::after{border-color:#fff;-webkit-animation:loader-figure-info 1.15s infinite cubic-bezier(.215,.61,.355,1);animation:loader-figure-info 1.15s infinite cubic-bezier(.215,.61,.355,1)}.pageloader.is-info .title{color:#fff}.pageloader.is-success{background-color:#23d160;background:#23d160}.pageloader.is-success::after{border-color:#fff;-webkit-animation:loader-figure-success 1.15s infinite cubic-bezier(.215,.61,.355,1);animation:loader-figure-success 1.15s infinite cubic-bezier(.215,.61,.355,1)}.pageloader.is-success .title{color:#fff}.pageloader.is-warning{background-color:#ffdd57;background:#ffdd57}.pageloader.is-warning::after{border-color:rgba(0,0,0,.7);-webkit-animation:loader-figure-warning 1.15s infinite cubic-bezier(.215,.61,.355,1);animation:loader-figure-warning 1.15s infinite cubic-bezier(.215,.61,.355,1)}.pageloader.is-warning .title{color:rgba(0,0,0,.7)}.pageloader.is-danger{background-color:#ff3860;background:#ff3860}.pageloader.is-danger::after{border-color:#fff;-webkit-animation:loader-figure-danger 1.15s infinite cubic-bezier(.215,.61,.355,1);animation:loader-figure-danger 1.15s infinite cubic-bezier(.215,.61,.355,1)}.pageloader.is-danger .title{color:#fff}.pageloader:not(.is-bottom-to-top){-webkit-transform:translateY(-100%);transform:translateY(-100%)}.pageloader.is-bottom-to-top{-webkit-transform:translateY(100%);transform:translateY(100%)}.pageloader.is-left-to-right{-webkit-transform:translateX(-100%);transform:translateX(-100%)}.pageloader.is-right-to-left{-webkit-transform:translateX(100%);transform:translateX(100%)}.pageloader.is-active:not(.is-left-to-right),.pageloader.is-active:not(.is-right-to-left){-webkit-transform:translateY(0);transform:translateY(0)}.pageloader.is-active.is-left-to-right,.pageloader.is-active.is-right-to-left{-webkit-transform:translateX(0);transform:translateX(0)}.pageloader::after{position:absolute;top:50%;left:50%;display:block;border-radius:100%;content:'';z-index:9999;-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);width:0;height:0;box-sizing:border-box;border:0 solid #fff;-webkit-animation:loader-figure 1.15s infinite cubic-bezier(.215,.61,.355,1);animation:loader-figure 1.15s infinite cubic-bezier(.215,.61,.355,1)}.pageloader .title{position:absolute;top:50%;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%);margin:2em 0 0 0;font-size:.875em;letter-spacing:.1em;line-height:1.5em;color:#fff;white-space:nowrap}@-webkit-keyframes loader-figure{0%{height:0;width:0;background-color:#fff}29%{background-color:#fff}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@keyframes loader-figure{0%{height:0;width:0;background-color:#fff}29%{background-color:#fff}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@-webkit-keyframes loader-figure-white{0%{height:0;width:0;background-color:#0a0a0a}29%{background-color:#0a0a0a}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@keyframes loader-figure-white{0%{height:0;width:0;background-color:#0a0a0a}29%{background-color:#0a0a0a}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@-webkit-keyframes loader-figure-black{0%{height:0;width:0;background-color:#fff}29%{background-color:#fff}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@keyframes loader-figure-black{0%{height:0;width:0;background-color:#fff}29%{background-color:#fff}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@-webkit-keyframes loader-figure-light{0%{height:0;width:0;background-color:#363636}29%{background-color:#363636}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@keyframes loader-figure-light{0%{height:0;width:0;background-color:#363636}29%{background-color:#363636}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@-webkit-keyframes loader-figure-dark{0%{height:0;width:0;background-color:#f5f5f5}29%{background-color:#f5f5f5}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@keyframes loader-figure-dark{0%{height:0;width:0;background-color:#f5f5f5}29%{background-color:#f5f5f5}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@-webkit-keyframes loader-figure-primary{0%{height:0;width:0;background-color:#fff}29%{background-color:#fff}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@keyframes loader-figure-primary{0%{height:0;width:0;background-color:#fff}29%{background-color:#fff}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@-webkit-keyframes loader-figure-link{0%{height:0;width:0;background-color:#fff}29%{background-color:#fff}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@keyframes loader-figure-link{0%{height:0;width:0;background-color:#fff}29%{background-color:#fff}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@-webkit-keyframes loader-figure-info{0%{height:0;width:0;background-color:#fff}29%{background-color:#fff}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@keyframes loader-figure-info{0%{height:0;width:0;background-color:#fff}29%{background-color:#fff}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@-webkit-keyframes loader-figure-success{0%{height:0;width:0;background-color:#fff}29%{background-color:#fff}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@keyframes loader-figure-success{0%{height:0;width:0;background-color:#fff}29%{background-color:#fff}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@-webkit-keyframes loader-figure-warning{0%{height:0;width:0;background-color:rgba(0,0,0,.7)}29%{background-color:rgba(0,0,0,.7)}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@keyframes loader-figure-warning{0%{height:0;width:0;background-color:rgba(0,0,0,.7)}29%{background-color:rgba(0,0,0,.7)}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@-webkit-keyframes loader-figure-danger{0%{height:0;width:0;background-color:#fff}29%{background-color:#fff}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}}@keyframes loader-figure-danger{0%{height:0;width:0;background-color:#fff}29%{background-color:#fff}30%{height:2em;width:2em;background-color:transparent;border-width:1em;opacity:1}100%{height:2em;width:2em;border-width:0;opacity:0;background-color:transparent}} +|] + +bulmaSwitchCSS :: String +bulmaSwitchCSS = [s| +.switch[type=checkbox]{outline:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;display:inline-block;position:absolute;opacity:0}.switch[type=checkbox]:focus+label::after,.switch[type=checkbox]:focus+label::before,.switch[type=checkbox]:focus+label:after,.switch[type=checkbox]:focus+label:before{outline:1px dotted #b5b5b5}.switch[type=checkbox][disabled]{cursor:not-allowed}.switch[type=checkbox][disabled]+label{opacity:.5}.switch[type=checkbox][disabled]+label::before,.switch[type=checkbox][disabled]+label:before{opacity:.5}.switch[type=checkbox][disabled]+label::after,.switch[type=checkbox][disabled]+label:after{opacity:.5}.switch[type=checkbox][disabled]+label:hover{cursor:not-allowed}.switch[type=checkbox]+label{position:relative;display:inline-flex;align-items:center;justify-content:flex-start;font-size:1rem;height:2.5em;line-height:1.5;padding-left:3.5rem;padding-top:.2rem;cursor:pointer}.switch[type=checkbox]+label::before,.switch[type=checkbox]+label:before{position:absolute;display:block;top:calc(50% - 1.5rem * .5);left:0;width:3rem;height:1.5rem;border:.1rem solid transparent;border-radius:4px;background:#b5b5b5;content:""}.switch[type=checkbox]+label::after,.switch[type=checkbox]+label:after{display:block;position:absolute;top:calc(50% - 1rem * .5);left:.25rem;width:1rem;height:1rem;transform:translate3d(0,0,0);border-radius:4px;background:#fff;transition:all .25s ease-out;content:""}.switch[type=checkbox]+label .switch-active,.switch[type=checkbox]+label .switch-inactive{font-size:.9rem;z-index:1;margin-top:-4px}.switch[type=checkbox]+label.has-text-inside .switch-inactive{margin-left:-1.925rem}.switch[type=checkbox]+label.has-text-inside .switch-active{margin-left:-3.25rem}.switch[type=checkbox].is-rtl+label{padding-left:0;padding-right:3.5rem}.switch[type=checkbox].is-rtl+label::before,.switch[type=checkbox].is-rtl+label:before{left:auto;right:0}.switch[type=checkbox].is-rtl+label::after,.switch[type=checkbox].is-rtl+label:after{left:auto;right:1.625rem}.switch[type=checkbox]:checked+label::before,.switch[type=checkbox]:checked+label:before{background:#00d1b2}.switch[type=checkbox]:checked+label::after{left:1.625rem}.switch[type=checkbox]:checked.is-rtl+label::after,.switch[type=checkbox]:checked.is-rtl+label:after{left:auto;right:.25rem}.switch[type=checkbox].is-outlined+label::before,.switch[type=checkbox].is-outlined+label:before{background-color:transparent;border-color:#b5b5b5}.switch[type=checkbox].is-outlined+label::after,.switch[type=checkbox].is-outlined+label:after{background:#b5b5b5}.switch[type=checkbox].is-outlined:checked+label::before,.switch[type=checkbox].is-outlined:checked+label:before{background-color:transparent;border-color:#00d1b2}.switch[type=checkbox].is-outlined:checked+label::after,.switch[type=checkbox].is-outlined:checked+label:after{background:#00d1b2}.switch[type=checkbox].is-thin+label::before,.switch[type=checkbox].is-thin+label:before{top:.5454545456rem;height:.375rem}.switch[type=checkbox].is-thin+label::after,.switch[type=checkbox].is-thin+label:after{box-shadow:0 0 3px #7a7a7a}.switch[type=checkbox].is-rounded+label::before,.switch[type=checkbox].is-rounded+label:before{border-radius:24px}.switch[type=checkbox].is-rounded+label::after,.switch[type=checkbox].is-rounded+label:after{border-radius:50%}.switch[type=checkbox].is-small+label{position:relative;display:inline-flex;align-items:center;justify-content:flex-start;font-size:.75rem;height:2.5em;line-height:1.5;padding-left:2.75rem;padding-top:.2rem;cursor:pointer}.switch[type=checkbox].is-small+label::before,.switch[type=checkbox].is-small+label:before{position:absolute;display:block;top:calc(50% - 1.125rem * .5);left:0;width:2.25rem;height:1.125rem;border:.1rem solid transparent;border-radius:4px;background:#b5b5b5;content:""}.switch[type=checkbox].is-small+label::after,.switch[type=checkbox].is-small+label:after{display:block;position:absolute;top:calc(50% - .625rem * .5);left:.25rem;width:.625rem;height:.625rem;transform:translate3d(0,0,0);border-radius:4px;background:#fff;transition:all .25s ease-out;content:""}.switch[type=checkbox].is-small+label .switch-active,.switch[type=checkbox].is-small+label .switch-inactive{font-size:.65rem;z-index:1;margin-top:-4px}.switch[type=checkbox].is-small+label.has-text-inside .switch-inactive{margin-left:-1.55rem}.switch[type=checkbox].is-small+label.has-text-inside .switch-active{margin-left:-2.5rem}.switch[type=checkbox].is-small.is-rtl+label{padding-left:0;padding-right:2.75rem}.switch[type=checkbox].is-small.is-rtl+label::before,.switch[type=checkbox].is-small.is-rtl+label:before{left:auto;right:0}.switch[type=checkbox].is-small.is-rtl+label::after,.switch[type=checkbox].is-small.is-rtl+label:after{left:auto;right:1.25rem}.switch[type=checkbox].is-small:checked+label::before,.switch[type=checkbox].is-small:checked+label:before{background:#00d1b2}.switch[type=checkbox].is-small:checked+label::after{left:1.25rem}.switch[type=checkbox].is-small:checked.is-rtl+label::after,.switch[type=checkbox].is-small:checked.is-rtl+label:after{left:auto;right:.25rem}.switch[type=checkbox].is-small.is-outlined+label::before,.switch[type=checkbox].is-small.is-outlined+label:before{background-color:transparent;border-color:#b5b5b5}.switch[type=checkbox].is-small.is-outlined+label::after,.switch[type=checkbox].is-small.is-outlined+label:after{background:#b5b5b5}.switch[type=checkbox].is-small.is-outlined:checked+label::before,.switch[type=checkbox].is-small.is-outlined:checked+label:before{background-color:transparent;border-color:#00d1b2}.switch[type=checkbox].is-small.is-outlined:checked+label::after,.switch[type=checkbox].is-small.is-outlined:checked+label:after{background:#00d1b2}.switch[type=checkbox].is-small.is-thin+label::before,.switch[type=checkbox].is-small.is-thin+label:before{top:.4090909093rem;height:.28125rem}.switch[type=checkbox].is-small.is-thin+label::after,.switch[type=checkbox].is-small.is-thin+label:after{box-shadow:0 0 3px #7a7a7a}.switch[type=checkbox].is-small.is-rounded+label::before,.switch[type=checkbox].is-small.is-rounded+label:before{border-radius:24px}.switch[type=checkbox].is-small.is-rounded+label::after,.switch[type=checkbox].is-small.is-rounded+label:after{border-radius:50%}.switch[type=checkbox].is-medium+label{position:relative;display:inline-flex;align-items:center;justify-content:flex-start;font-size:1.25rem;height:2.5em;line-height:1.5;padding-left:4.25rem;padding-top:.2rem;cursor:pointer}.switch[type=checkbox].is-medium+label::before,.switch[type=checkbox].is-medium+label:before{position:absolute;display:block;top:calc(50% - 1.875rem * .5);left:0;width:3.75rem;height:1.875rem;border:.1rem solid transparent;border-radius:4px;background:#b5b5b5;content:""}.switch[type=checkbox].is-medium+label::after,.switch[type=checkbox].is-medium+label:after{display:block;position:absolute;top:calc(50% - 1.375rem * .5);left:.25rem;width:1.375rem;height:1.375rem;transform:translate3d(0,0,0);border-radius:4px;background:#fff;transition:all .25s ease-out;content:""}.switch[type=checkbox].is-medium+label .switch-active,.switch[type=checkbox].is-medium+label .switch-inactive{font-size:1.15rem;z-index:1;margin-top:-4px}.switch[type=checkbox].is-medium+label.has-text-inside .switch-inactive{margin-left:-2.3rem}.switch[type=checkbox].is-medium+label.has-text-inside .switch-active{margin-left:-4rem}.switch[type=checkbox].is-medium.is-rtl+label{padding-left:0;padding-right:4.25rem}.switch[type=checkbox].is-medium.is-rtl+label::before,.switch[type=checkbox].is-medium.is-rtl+label:before{left:auto;right:0}.switch[type=checkbox].is-medium.is-rtl+label::after,.switch[type=checkbox].is-medium.is-rtl+label:after{left:auto;right:2rem}.switch[type=checkbox].is-medium:checked+label::before,.switch[type=checkbox].is-medium:checked+label:before{background:#00d1b2}.switch[type=checkbox].is-medium:checked+label::after{left:2rem}.switch[type=checkbox].is-medium:checked.is-rtl+label::after,.switch[type=checkbox].is-medium:checked.is-rtl+label:after{left:auto;right:.25rem}.switch[type=checkbox].is-medium.is-outlined+label::before,.switch[type=checkbox].is-medium.is-outlined+label:before{background-color:transparent;border-color:#b5b5b5}.switch[type=checkbox].is-medium.is-outlined+label::after,.switch[type=checkbox].is-medium.is-outlined+label:after{background:#b5b5b5}.switch[type=checkbox].is-medium.is-outlined:checked+label::before,.switch[type=checkbox].is-medium.is-outlined:checked+label:before{background-color:transparent;border-color:#00d1b2}.switch[type=checkbox].is-medium.is-outlined:checked+label::after,.switch[type=checkbox].is-medium.is-outlined:checked+label:after{background:#00d1b2}.switch[type=checkbox].is-medium.is-thin+label::before,.switch[type=checkbox].is-medium.is-thin+label:before{top:.6818181819rem;height:.46875rem}.switch[type=checkbox].is-medium.is-thin+label::after,.switch[type=checkbox].is-medium.is-thin+label:after{box-shadow:0 0 3px #7a7a7a}.switch[type=checkbox].is-medium.is-rounded+label::before,.switch[type=checkbox].is-medium.is-rounded+label:before{border-radius:24px}.switch[type=checkbox].is-medium.is-rounded+label::after,.switch[type=checkbox].is-medium.is-rounded+label:after{border-radius:50%}.switch[type=checkbox].is-large+label{position:relative;display:inline-flex;align-items:center;justify-content:flex-start;font-size:1.5rem;height:2.5em;line-height:1.5;padding-left:5rem;padding-top:.2rem;cursor:pointer}.switch[type=checkbox].is-large+label::before,.switch[type=checkbox].is-large+label:before{position:absolute;display:block;top:calc(50% - 2.25rem * .5);left:0;width:4.5rem;height:2.25rem;border:.1rem solid transparent;border-radius:4px;background:#b5b5b5;content:""}.switch[type=checkbox].is-large+label::after,.switch[type=checkbox].is-large+label:after{display:block;position:absolute;top:calc(50% - 1.75rem * .5);left:.25rem;width:1.75rem;height:1.75rem;transform:translate3d(0,0,0);border-radius:4px;background:#fff;transition:all .25s ease-out;content:""}.switch[type=checkbox].is-large+label .switch-active,.switch[type=checkbox].is-large+label .switch-inactive{font-size:1.4rem;z-index:1;margin-top:-4px}.switch[type=checkbox].is-large+label.has-text-inside .switch-inactive{margin-left:-2.675rem}.switch[type=checkbox].is-large+label.has-text-inside .switch-active{margin-left:-4.75rem}.switch[type=checkbox].is-large.is-rtl+label{padding-left:0;padding-right:5rem}.switch[type=checkbox].is-large.is-rtl+label::before,.switch[type=checkbox].is-large.is-rtl+label:before{left:auto;right:0}.switch[type=checkbox].is-large.is-rtl+label::after,.switch[type=checkbox].is-large.is-rtl+label:after{left:auto;right:2.375rem}.switch[type=checkbox].is-large:checked+label::before,.switch[type=checkbox].is-large:checked+label:before{background:#00d1b2}.switch[type=checkbox].is-large:checked+label::after{left:2.375rem}.switch[type=checkbox].is-large:checked.is-rtl+label::after,.switch[type=checkbox].is-large:checked.is-rtl+label:after{left:auto;right:.25rem}.switch[type=checkbox].is-large.is-outlined+label::before,.switch[type=checkbox].is-large.is-outlined+label:before{background-color:transparent;border-color:#b5b5b5}.switch[type=checkbox].is-large.is-outlined+label::after,.switch[type=checkbox].is-large.is-outlined+label:after{background:#b5b5b5}.switch[type=checkbox].is-large.is-outlined:checked+label::before,.switch[type=checkbox].is-large.is-outlined:checked+label:before{background-color:transparent;border-color:#00d1b2}.switch[type=checkbox].is-large.is-outlined:checked+label::after,.switch[type=checkbox].is-large.is-outlined:checked+label:after{background:#00d1b2}.switch[type=checkbox].is-large.is-thin+label::before,.switch[type=checkbox].is-large.is-thin+label:before{top:.8181818183rem;height:.5625rem}.switch[type=checkbox].is-large.is-thin+label::after,.switch[type=checkbox].is-large.is-thin+label:after{box-shadow:0 0 3px #7a7a7a}.switch[type=checkbox].is-large.is-rounded+label::before,.switch[type=checkbox].is-large.is-rounded+label:before{border-radius:24px}.switch[type=checkbox].is-large.is-rounded+label::after,.switch[type=checkbox].is-large.is-rounded+label:after{border-radius:50%}.switch[type=checkbox].is-white+label .switch-active{display:none}.switch[type=checkbox].is-white+label .switch-inactive{display:inline-block}.switch[type=checkbox].is-white:checked+label::before,.switch[type=checkbox].is-white:checked+label:before{background:#fff}.switch[type=checkbox].is-white:checked+label .switch-active{display:inline-block}.switch[type=checkbox].is-white:checked+label .switch-inactive{display:none}.switch[type=checkbox].is-white.is-outlined:checked+label::before,.switch[type=checkbox].is-white.is-outlined:checked+label:before{background-color:transparent;border-color:#fff!important}.switch[type=checkbox].is-white.is-outlined:checked+label::after,.switch[type=checkbox].is-white.is-outlined:checked+label:after{background:#fff}.switch[type=checkbox].is-white.is-thin.is-outlined+label::after,.switch[type=checkbox].is-white.is-thin.is-outlined+label:after{box-shadow:none}.switch[type=checkbox].is-unchecked-white+label::before,.switch[type=checkbox].is-unchecked-white+label:before{background:#fff}.switch[type=checkbox].is-unchecked-white.is-outlined+label::before,.switch[type=checkbox].is-unchecked-white.is-outlined+label:before{background-color:transparent;border-color:#fff!important}.switch[type=checkbox].is-unchecked-white.is-outlined+label::after,.switch[type=checkbox].is-unchecked-white.is-outlined+label:after{background:#fff}.switch[type=checkbox].is-black+label .switch-active{display:none}.switch[type=checkbox].is-black+label .switch-inactive{display:inline-block}.switch[type=checkbox].is-black:checked+label::before,.switch[type=checkbox].is-black:checked+label:before{background:#0a0a0a}.switch[type=checkbox].is-black:checked+label .switch-active{display:inline-block}.switch[type=checkbox].is-black:checked+label .switch-inactive{display:none}.switch[type=checkbox].is-black.is-outlined:checked+label::before,.switch[type=checkbox].is-black.is-outlined:checked+label:before{background-color:transparent;border-color:#0a0a0a!important}.switch[type=checkbox].is-black.is-outlined:checked+label::after,.switch[type=checkbox].is-black.is-outlined:checked+label:after{background:#0a0a0a}.switch[type=checkbox].is-black.is-thin.is-outlined+label::after,.switch[type=checkbox].is-black.is-thin.is-outlined+label:after{box-shadow:none}.switch[type=checkbox].is-unchecked-black+label::before,.switch[type=checkbox].is-unchecked-black+label:before{background:#0a0a0a}.switch[type=checkbox].is-unchecked-black.is-outlined+label::before,.switch[type=checkbox].is-unchecked-black.is-outlined+label:before{background-color:transparent;border-color:#0a0a0a!important}.switch[type=checkbox].is-unchecked-black.is-outlined+label::after,.switch[type=checkbox].is-unchecked-black.is-outlined+label:after{background:#0a0a0a}.switch[type=checkbox].is-light+label .switch-active{display:none}.switch[type=checkbox].is-light+label .switch-inactive{display:inline-block}.switch[type=checkbox].is-light:checked+label::before,.switch[type=checkbox].is-light:checked+label:before{background:#f5f5f5}.switch[type=checkbox].is-light:checked+label .switch-active{display:inline-block}.switch[type=checkbox].is-light:checked+label .switch-inactive{display:none}.switch[type=checkbox].is-light.is-outlined:checked+label::before,.switch[type=checkbox].is-light.is-outlined:checked+label:before{background-color:transparent;border-color:#f5f5f5!important}.switch[type=checkbox].is-light.is-outlined:checked+label::after,.switch[type=checkbox].is-light.is-outlined:checked+label:after{background:#f5f5f5}.switch[type=checkbox].is-light.is-thin.is-outlined+label::after,.switch[type=checkbox].is-light.is-thin.is-outlined+label:after{box-shadow:none}.switch[type=checkbox].is-unchecked-light+label::before,.switch[type=checkbox].is-unchecked-light+label:before{background:#f5f5f5}.switch[type=checkbox].is-unchecked-light.is-outlined+label::before,.switch[type=checkbox].is-unchecked-light.is-outlined+label:before{background-color:transparent;border-color:#f5f5f5!important}.switch[type=checkbox].is-unchecked-light.is-outlined+label::after,.switch[type=checkbox].is-unchecked-light.is-outlined+label:after{background:#f5f5f5}.switch[type=checkbox].is-dark+label .switch-active{display:none}.switch[type=checkbox].is-dark+label .switch-inactive{display:inline-block}.switch[type=checkbox].is-dark:checked+label::before,.switch[type=checkbox].is-dark:checked+label:before{background:#363636}.switch[type=checkbox].is-dark:checked+label .switch-active{display:inline-block}.switch[type=checkbox].is-dark:checked+label .switch-inactive{display:none}.switch[type=checkbox].is-dark.is-outlined:checked+label::before,.switch[type=checkbox].is-dark.is-outlined:checked+label:before{background-color:transparent;border-color:#363636!important}.switch[type=checkbox].is-dark.is-outlined:checked+label::after,.switch[type=checkbox].is-dark.is-outlined:checked+label:after{background:#363636}.switch[type=checkbox].is-dark.is-thin.is-outlined+label::after,.switch[type=checkbox].is-dark.is-thin.is-outlined+label:after{box-shadow:none}.switch[type=checkbox].is-unchecked-dark+label::before,.switch[type=checkbox].is-unchecked-dark+label:before{background:#363636}.switch[type=checkbox].is-unchecked-dark.is-outlined+label::before,.switch[type=checkbox].is-unchecked-dark.is-outlined+label:before{background-color:transparent;border-color:#363636!important}.switch[type=checkbox].is-unchecked-dark.is-outlined+label::after,.switch[type=checkbox].is-unchecked-dark.is-outlined+label:after{background:#363636}.switch[type=checkbox].is-primary+label .switch-active{display:none}.switch[type=checkbox].is-primary+label .switch-inactive{display:inline-block}.switch[type=checkbox].is-primary:checked+label::before,.switch[type=checkbox].is-primary:checked+label:before{background:#00d1b2}.switch[type=checkbox].is-primary:checked+label .switch-active{display:inline-block}.switch[type=checkbox].is-primary:checked+label .switch-inactive{display:none}.switch[type=checkbox].is-primary.is-outlined:checked+label::before,.switch[type=checkbox].is-primary.is-outlined:checked+label:before{background-color:transparent;border-color:#00d1b2!important}.switch[type=checkbox].is-primary.is-outlined:checked+label::after,.switch[type=checkbox].is-primary.is-outlined:checked+label:after{background:#00d1b2}.switch[type=checkbox].is-primary.is-thin.is-outlined+label::after,.switch[type=checkbox].is-primary.is-thin.is-outlined+label:after{box-shadow:none}.switch[type=checkbox].is-unchecked-primary+label::before,.switch[type=checkbox].is-unchecked-primary+label:before{background:#00d1b2}.switch[type=checkbox].is-unchecked-primary.is-outlined+label::before,.switch[type=checkbox].is-unchecked-primary.is-outlined+label:before{background-color:transparent;border-color:#00d1b2!important}.switch[type=checkbox].is-unchecked-primary.is-outlined+label::after,.switch[type=checkbox].is-unchecked-primary.is-outlined+label:after{background:#00d1b2}.switch[type=checkbox].is-link+label .switch-active{display:none}.switch[type=checkbox].is-link+label .switch-inactive{display:inline-block}.switch[type=checkbox].is-link:checked+label::before,.switch[type=checkbox].is-link:checked+label:before{background:#485fc7}.switch[type=checkbox].is-link:checked+label .switch-active{display:inline-block}.switch[type=checkbox].is-link:checked+label .switch-inactive{display:none}.switch[type=checkbox].is-link.is-outlined:checked+label::before,.switch[type=checkbox].is-link.is-outlined:checked+label:before{background-color:transparent;border-color:#485fc7!important}.switch[type=checkbox].is-link.is-outlined:checked+label::after,.switch[type=checkbox].is-link.is-outlined:checked+label:after{background:#485fc7}.switch[type=checkbox].is-link.is-thin.is-outlined+label::after,.switch[type=checkbox].is-link.is-thin.is-outlined+label:after{box-shadow:none}.switch[type=checkbox].is-unchecked-link+label::before,.switch[type=checkbox].is-unchecked-link+label:before{background:#485fc7}.switch[type=checkbox].is-unchecked-link.is-outlined+label::before,.switch[type=checkbox].is-unchecked-link.is-outlined+label:before{background-color:transparent;border-color:#485fc7!important}.switch[type=checkbox].is-unchecked-link.is-outlined+label::after,.switch[type=checkbox].is-unchecked-link.is-outlined+label:after{background:#485fc7}.switch[type=checkbox].is-info+label .switch-active{display:none}.switch[type=checkbox].is-info+label .switch-inactive{display:inline-block}.switch[type=checkbox].is-info:checked+label::before,.switch[type=checkbox].is-info:checked+label:before{background:#3e8ed0}.switch[type=checkbox].is-info:checked+label .switch-active{display:inline-block}.switch[type=checkbox].is-info:checked+label .switch-inactive{display:none}.switch[type=checkbox].is-info.is-outlined:checked+label::before,.switch[type=checkbox].is-info.is-outlined:checked+label:before{background-color:transparent;border-color:#3e8ed0!important}.switch[type=checkbox].is-info.is-outlined:checked+label::after,.switch[type=checkbox].is-info.is-outlined:checked+label:after{background:#3e8ed0}.switch[type=checkbox].is-info.is-thin.is-outlined+label::after,.switch[type=checkbox].is-info.is-thin.is-outlined+label:after{box-shadow:none}.switch[type=checkbox].is-unchecked-info+label::before,.switch[type=checkbox].is-unchecked-info+label:before{background:#3e8ed0}.switch[type=checkbox].is-unchecked-info.is-outlined+label::before,.switch[type=checkbox].is-unchecked-info.is-outlined+label:before{background-color:transparent;border-color:#3e8ed0!important}.switch[type=checkbox].is-unchecked-info.is-outlined+label::after,.switch[type=checkbox].is-unchecked-info.is-outlined+label:after{background:#3e8ed0}.switch[type=checkbox].is-success+label .switch-active{display:none}.switch[type=checkbox].is-success+label .switch-inactive{display:inline-block}.switch[type=checkbox].is-success:checked+label::before,.switch[type=checkbox].is-success:checked+label:before{background:#48c78e}.switch[type=checkbox].is-success:checked+label .switch-active{display:inline-block}.switch[type=checkbox].is-success:checked+label .switch-inactive{display:none}.switch[type=checkbox].is-success.is-outlined:checked+label::before,.switch[type=checkbox].is-success.is-outlined:checked+label:before{background-color:transparent;border-color:#48c78e!important}.switch[type=checkbox].is-success.is-outlined:checked+label::after,.switch[type=checkbox].is-success.is-outlined:checked+label:after{background:#48c78e}.switch[type=checkbox].is-success.is-thin.is-outlined+label::after,.switch[type=checkbox].is-success.is-thin.is-outlined+label:after{box-shadow:none}.switch[type=checkbox].is-unchecked-success+label::before,.switch[type=checkbox].is-unchecked-success+label:before{background:#48c78e}.switch[type=checkbox].is-unchecked-success.is-outlined+label::before,.switch[type=checkbox].is-unchecked-success.is-outlined+label:before{background-color:transparent;border-color:#48c78e!important}.switch[type=checkbox].is-unchecked-success.is-outlined+label::after,.switch[type=checkbox].is-unchecked-success.is-outlined+label:after{background:#48c78e}.switch[type=checkbox].is-warning+label .switch-active{display:none}.switch[type=checkbox].is-warning+label .switch-inactive{display:inline-block}.switch[type=checkbox].is-warning:checked+label::before,.switch[type=checkbox].is-warning:checked+label:before{background:#ffe08a}.switch[type=checkbox].is-warning:checked+label .switch-active{display:inline-block}.switch[type=checkbox].is-warning:checked+label .switch-inactive{display:none}.switch[type=checkbox].is-warning.is-outlined:checked+label::before,.switch[type=checkbox].is-warning.is-outlined:checked+label:before{background-color:transparent;border-color:#ffe08a!important}.switch[type=checkbox].is-warning.is-outlined:checked+label::after,.switch[type=checkbox].is-warning.is-outlined:checked+label:after{background:#ffe08a}.switch[type=checkbox].is-warning.is-thin.is-outlined+label::after,.switch[type=checkbox].is-warning.is-thin.is-outlined+label:after{box-shadow:none}.switch[type=checkbox].is-unchecked-warning+label::before,.switch[type=checkbox].is-unchecked-warning+label:before{background:#ffe08a}.switch[type=checkbox].is-unchecked-warning.is-outlined+label::before,.switch[type=checkbox].is-unchecked-warning.is-outlined+label:before{background-color:transparent;border-color:#ffe08a!important}.switch[type=checkbox].is-unchecked-warning.is-outlined+label::after,.switch[type=checkbox].is-unchecked-warning.is-outlined+label:after{background:#ffe08a}.switch[type=checkbox].is-danger+label .switch-active{display:none}.switch[type=checkbox].is-danger+label .switch-inactive{display:inline-block}.switch[type=checkbox].is-danger:checked+label::before,.switch[type=checkbox].is-danger:checked+label:before{background:#f14668}.switch[type=checkbox].is-danger:checked+label .switch-active{display:inline-block}.switch[type=checkbox].is-danger:checked+label .switch-inactive{display:none}.switch[type=checkbox].is-danger.is-outlined:checked+label::before,.switch[type=checkbox].is-danger.is-outlined:checked+label:before{background-color:transparent;border-color:#f14668!important}.switch[type=checkbox].is-danger.is-outlined:checked+label::after,.switch[type=checkbox].is-danger.is-outlined:checked+label:after{background:#f14668}.switch[type=checkbox].is-danger.is-thin.is-outlined+label::after,.switch[type=checkbox].is-danger.is-thin.is-outlined+label:after{box-shadow:none}.switch[type=checkbox].is-unchecked-danger+label::before,.switch[type=checkbox].is-unchecked-danger+label:before{background:#f14668}.switch[type=checkbox].is-unchecked-danger.is-outlined+label::before,.switch[type=checkbox].is-unchecked-danger.is-outlined+label:before{background-color:transparent;border-color:#f14668!important}.switch[type=checkbox].is-unchecked-danger.is-outlined+label::after,.switch[type=checkbox].is-unchecked-danger.is-outlined+label:after{background:#f14668}.field-body .switch[type=checkbox]+label{margin-top:.375em} +|] + +bulmaDividerCSS :: String +bulmaDividerCSS = [s| +/*! @creativebulma/bulma-divider v1.1.0 | (c) 2020 Gaetan | MIT License | https://github.com/CreativeBulma/bulma-divider */ +.divider{position:relative;display:flex;align-items:center;text-transform:uppercase;color:#7a7a7a;font-size:.75rem;font-weight:600;letter-spacing:.5px;margin:25px 0}.divider:after,.divider:before{content:"";display:block;flex:1;height:1px;background-color:#dbdbdb}.divider:not(.is-right):after{margin-left:10px}.divider:not(.is-left):before{margin-right:10px}.divider.is-left:before,.divider.is-right:after{display:none}.divider.is-vertical{flex-direction:column;margin:0 25px}.divider.is-vertical:after,.divider.is-vertical:before{height:auto;width:1px}.divider.is-vertical:after{margin-left:0;margin-top:10px}.divider.is-vertical:before{margin-right:0;margin-bottom:10px}.divider.is-white:after,.divider.is-white:before{background-color:#fff}.divider.is-black:after,.divider.is-black:before{background-color:#0a0a0a}.divider.is-light:after,.divider.is-light:before{background-color:#f5f5f5}.divider.is-dark:after,.divider.is-dark:before{background-color:#363636}.divider.is-primary:after,.divider.is-primary:before{background-color:#00d1b2}.divider.is-primary.is-light:after,.divider.is-primary.is-light:before{background-color:#ebfffc}.divider.is-link:after,.divider.is-link:before{background-color:#3273dc}.divider.is-link.is-light:after,.divider.is-link.is-light:before{background-color:#eef3fc}.divider.is-info:after,.divider.is-info:before{background-color:#3298dc}.divider.is-info.is-light:after,.divider.is-info.is-light:before{background-color:#eef6fc}.divider.is-success:after,.divider.is-success:before{background-color:#48c774}.divider.is-success.is-light:after,.divider.is-success.is-light:before{background-color:#effaf3}.divider.is-warning:after,.divider.is-warning:before{background-color:#ffdd57}.divider.is-warning.is-light:after,.divider.is-warning.is-light:before{background-color:#fffbeb}.divider.is-danger:after,.divider.is-danger:before{background-color:#f14668}.divider.is-danger.is-light:after,.divider.is-danger.is-light:before{background-color:#feecf0} +|] diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/CSS/Own.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/CSS/Own.hs new file mode 100644 index 00000000000..ba4e50b526c --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/CSS/Own.hs @@ -0,0 +1,875 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Cardano.Tracer.Handlers.RTView.UI.CSS.Own + ( ownCSS + , chartGridDark + , chartGridLight + , chartTextDark + , chartTextLight + ) where + +import Data.String.QQ + +-- | To avoid run-time dependency from the static content, embed own CSS in the page's header. +ownCSS :: String +ownCSS = [s| +html { + height: 100%; +} + +body { + height: 100%; +} + +.wrapper { + min-height: 100%; + width: 100%; + position: relative; + padding-bottom: 62px; + box-sizing: border-box; +} + +.footer { + height: 62px; + bottom: 0; + position: absolute; + width: 100%; +} + +code { + color: #1d359f; + padding: 0.11em 0.2em 0.11em; + border-radius: 3px; +} + +pre { + color: #1d359f; + font-size: .875em; + overflow-x: auto; + padding: 8px 8px 8px 8px; + margin-top: 12px; + margin-bottom: 12px; + white-space: pre; + word-wrap: normal; + border-radius: 3px; +} + +.message.is-link .message-body { + border-color: #485fc7; + color: #1834ae; +} + +span[data-tooltip] { + border-bottom: none !important; +} + +.pageloader { + opacity: 0.98 !important; +} + +.field.is-grouped { + display: inline-flex !important; +} + +.divider { + font-size: 16px; + letter-spacing: .5px; + margin: 30px 0; +} + +.rt-view-show-hide-chart-group { + margin-top: 5px; +} + +.rt-view-no-nodes-icon svg { + width: 70px; + margin-top: 60px; + margin-bottom: 40px; +} + +.rt-view-no-nodes-info { + max-width: 800px !important; + margin-top: 50px; + font-size: 97%; +} + +.rt-view-chart-area { + width: 100% !important; +} + +.rt-view-main-table-description { + min-width: 380px; +} + +.rt-view-node-chart-label svg { + width: 42px; + height: 15px; + margin-right: 3px; +} + +.rt-view-peer-modal { + width: 45%; +} + +.rt-view-errors-modal { + width: 65%; + min-height: 65%; +} + +.rt-view-ekg-metrics-modal { + width: 50%; +} + +@media only screen and (max-width: 1216px) { + .rt-view-peer-modal { + width: 60%; + } + + .rt-view-errors-modal { + width: 70%; + } + + .rt-view-ekg-metrics-modal { + width: 60%; + } +} + +@media only screen and (max-width: 1024px) { + .rt-view-peer-modal { + width: 70%; + } + + .rt-view-errors-modal { + width: 75%; + } + + .rt-view-ekg-metrics-modal { + width: 70%; + } +} + +@media only screen and (max-width: 769px) { + .rt-view-peer-modal { + width: 80%; + } + + .rt-view-errors-modal { + width: 85%; + } + + .rt-view-ekg-metrics-modal { + width: 80%; + } +} + +.rt-view-logs-input { + max-width: 150px; +} + +.rt-view-error-msg-input { + max-width: 380px; +} + +.rt-view-errors-timestamp { + width: 30%; +} + +.rt-view-errors-severity { + width: 16%; +} + +.rt-view-copy-icon { + margin-top: 6px; +} + +.rt-view-search-errors-icon { + margin-top: 6px; +} + +/* Dark Theme */ + +.dark { + font-family: sans-serif; + font-size: 22px; + background-color: #131325; + min-height: 100%; +} + +.dark .wrapper { + background-color: #131325; +} + +.dark .rt-view-href { + color: #607bf7; +} + +.dark .rt-view-href:hover { + color: #889cf5 !important; + border-bottom: 1px solid #889cf5; +} + +.dark .navbar-link, a.navbar-item { + font-size: 18px; + cursor: pointer; +} + +.dark .rt-view-href-icon svg { + width: 12px; + margin-left: 5px; + margin-bottom: 4px; + color: #607bf7; +} + +.dark .rt-view-top-bar { + background-color: #282841; + color: whitesmoke; + padding-top: 8px; + padding-bottom: 2px; + border-bottom: 1px solid #555; +} + +.dark .rt-view-cardano-logo svg { + width: 48px; + color: whitesmoke; + margin-left: 5px; +} + +.dark .rt-view-name { + color: whitesmoke; + margin-left: 17px; + margin-right: 6px; + margin-bottom: 6px; +} + +.dark .rt-view-info-icon svg { + width: 25px; + padding-top: 2px; + color: whitesmoke; + cursor: pointer; +} + +.dark .rt-view-theme-icon svg { + width: 23px; + padding-top: 2px; + color: whitesmoke; + cursor: pointer; +} + +.dark .rt-view-copy-icon svg { + width: 20px; + color: whitesmoke; + cursor: pointer; +} + +.dark .rt-view-sort-icon svg { + width: 11px; + margin-left: 9px; + color: whitesmoke; + cursor: pointer; +} + +.dark .rt-view-delete-icon svg { + width: 20px; + color: red; + cursor: pointer; +} + +.dark .rt-view-delete-errors-icon svg { + width: 22px; + color: red; + cursor: pointer; +} + +.dark .rt-view-search-errors-icon svg { + width: 18px; + color: whitesmoke; + cursor: pointer; +} + +.dark .rt-view-logs-icon svg { + width: 23px; + padding-top: 2px; + color: whitesmoke; +} + +.dark .rt-view-overview-icon svg { + width: 18px; + margin-right: 12px; + color: #0cc9cb; +} + +.dark .rt-view-no-nodes-icon svg { + color: #677deb; +} + +.dark .rt-view-notify-menu-icon svg { + width: 15px; + padding-top: 6px; + margin-right: 9px; + color: whitesmoke; +} + +.dark .rt-view-no-nodes-message { + font-size: 23px; + color: whitesmoke; +} + +.dark .rt-view-charts-container { + padding-bottom: 0px; +} + +.dark .rt-view-chart-container { + background-color: #2c2b3b; + padding-top: 10px; + padding-bottom: 20px; + padding-left: 20px; + padding-right: 20px; + margin-bottom: 24px; + border: 1px solid #444; + border-radius: 6px; +} + +.dark .rt-view-chart-name { + color: whitesmoke; +} + +.dark .rt-view-about-title { + color: whitesmoke; +} + +.dark .rt-view-about-head { + color: whitesmoke; + background-color: #282841; + border-bottom: 1px solid #555; +} + +.dark .rt-view-about-body { + color: whitesmoke; + background-color: #131325; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; +} + +.dark .rt-view-peer-title { + color: whitesmoke; +} + +.dark .rt-view-peer-head { + color: whitesmoke; + background-color: #282841; + border-bottom: 1px solid #555; +} + +.dark .rt-view-peer-body { + color: whitesmoke; + background-color: #131325; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; +} + +.dark .rt-view-errors-title { + color: whitesmoke; +} + +.dark .rt-view-errors-head { + color: whitesmoke; + background-color: #282841; + border-bottom: 1px solid #555; +} + +.dark .rt-view-errors-body { + color: whitesmoke; + background-color: #131325; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; +} + +.dark .rt-view-errors-foot { + color: whitesmoke; + background-color: #282841; + border-top: 1px solid #555; +} + +.dark .rt-view-notifications-title { + color: whitesmoke; +} + +.dark .rt-view-notifications-head { + color: whitesmoke; + background-color: #282841; + border-bottom: 1px solid #555; +} + +.dark .rt-view-notifications-body { + color: whitesmoke; + background-color: #131325; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; +} + +.dark .rt-view-ekg-metrics-title { + color: whitesmoke; +} + +.dark .rt-view-ekg-metrics-head { + color: whitesmoke; + background-color: #282841; + border-bottom: 1px solid #555; +} + +.dark .rt-view-ekg-metrics-body { + color: whitesmoke; + background-color: #131325; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; +} + +.dark .rt-view-main-table { + background-color: #131325; + color: whitesmoke; +} + +.dark .rt-view-main-table td { + padding-top: 17px; + padding-bottom: 17px; + border-bottom: 0px solid #444; +} + +.dark .rt-view-main-table th { + color: whitesmoke; + border-bottom: 2px solid #888; + vertical-align: middle; +} + +.dark .rt-view-peer-table { + background-color: #131325; + color: whitesmoke; +} + +.dark .rt-view-peer-table td { + padding-top: 10px; + padding-bottom: 10px; + border-bottom: 0px solid #444; +} + +.dark .rt-view-peer-table th { + color: whitesmoke; + border-bottom: 2px solid #888; + vertical-align: middle; +} + +.dark .rt-view-errors-table { + background-color: #131325; + color: whitesmoke; +} + +.dark .rt-view-errors-table td { + padding-top: 10px; + padding-bottom: 10px; + border-bottom: 0px solid #444; +} + +.dark .rt-view-errors-table th { + color: whitesmoke; + border-bottom: 2px solid #888; + vertical-align: middle; +} + +.dark .rt-view-chart-group-title { + color: whitesmoke; + font-weight: bold; + margin-left: 13px; + font-size: 115%; +} + +.dark .rt-view-show-hide-chart-group svg { + width: 21px; + color: #0cc9cb; +} + +.dark .rt-view-chart-icon svg { + width: 19px; + margin-right: 11px; + color: #0cc9cb; +} + +.dark .rt-view-footer { + background-color: #282841; + color: #999; + padding-top: 16px; + padding-bottom: 12px; + border-top: 1px solid #555; + font-size: 80%; +} + +.dark .rt-view-footer-github svg { + width: 23px; + margin-top: 1px; + color: #0cc9cb; +} + +.dark .rt-view-percent-done { + color: #07e949; +} + +.dark .rt-view-what-icon svg { + width: 18px; + margin-left: 12px; + color: #999; +} + +.dark .rt-view-epoch-end svg { + width: 16px; + margin-left: 20px; + margin-right: 5px; + color: #0cc9cb; +} + +/**** Light Theme ****/ + +.light { + font-family: sans-serif; + font-size: 22px; + background-color: #f5f5f5; + min-height: 100%; +} + +.light .wrapper { + background-color: #f5f5f5; +} + +.light .rt-view-href { + color: #264af0; +} + +.light .rt-view-href:hover { + color: #889cf5 !important; + border-bottom: 1px solid #889cf5; +} + +.light .rt-view-href-icon svg { + width: 12px; + margin-left: 5px; + margin-bottom: 4px; + color: #264af0; +} + +.light .navbar-link, a.navbar-item { + font-size: 18px; + cursor: pointer; +} + +.light .rt-view-top-bar { + background-color: #efefef; + color: #131325; + padding-top: 8px; + padding-bottom: 2px; + border-bottom: 1px solid #dbdbdb; +} + +.light .rt-view-cardano-logo svg { + width: 48px; + color: #0033ad; + margin-left: 5px; +} + +.light .rt-view-name { + color: #0033ad; + margin-left: 17px; + margin-right: 6px; + margin-bottom: 6px; +} + +.light .rt-view-info-icon svg { + width: 25px; + padding-top: 2px; + color: #0033ad; + cursor: pointer; +} + +.light .rt-view-theme-icon svg { + width: 23px; + padding-top: 2px; + color: #0033ad; + cursor: pointer; +} + +.light .rt-view-copy-icon svg { + width: 20px; + color: #444; + cursor: pointer; +} + +.light .rt-view-sort-icon svg { + width: 11px; + margin-left: 9px; + color: #444; + cursor: pointer; +} + +.light .rt-view-delete-icon svg { + width: 20px; + color: red; + cursor: pointer; +} + +.light .rt-view-delete-errors-icon svg { + width: 22px; + color: red; + cursor: pointer; +} + +.light .rt-view-search-errors-icon svg { + width: 18px; + color: #444; + cursor: pointer; +} + +.light .rt-view-logs-icon svg { + width: 23px; + padding-top: 2px; + color: #0033ad; +} + +.light .rt-view-overview-icon svg { + width: 18px; + margin-right: 12px; + color: #038b8c; +} + +.light .rt-view-no-nodes-icon svg { + color: #0033ad; +} + +.light .rt-view-no-nodes-message { + font-size: 23px; + color: #0033ad; +} + +.light .rt-view-charts-container { + padding-bottom: 0px; +} + +.light .rt-view-chart-container { + background-color: #eeeeee; + padding-top: 10px; + padding-bottom: 20px; + padding-left: 20px; + padding-right: 20px; + margin-bottom: 24px; + border: 1px solid #dddddd; + border-radius: 6px; +} + +.light .rt-view-chart-name { + color: #444; +} + +.light .rt-view-about-title { + color: #444; +} + +.light .rt-view-about-head { + color: whitesmoke; + background-color: whitesmoke; + border-bottom: 1px solid #bebebe; +} + +.light .rt-view-about-body { + color: #555; + background-color: #eaeaea; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; +} + +.light .rt-view-peer-title { + color: #444; +} + +.light .rt-view-peer-head { + color: whitesmoke; + background-color: whitesmoke; + border-bottom: 1px solid #bebebe; +} + +.light .rt-view-peer-body { + color: #555; + background-color: #eaeaea; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; +} + +.light .rt-view-errors-title { + color: #444; +} + +.light .rt-view-errors-head { + color: #555; + background-color: whitesmoke; + border-bottom: 1px solid #bebebe; +} + +.light .rt-view-errors-body { + color: #555; + background-color: #eaeaea; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; +} + +.light .rt-view-errors-foot { + color: #555; + background-color: whitesmoke; + border-top: 1px solid #bebebe; +} + +.light .rt-view-notifications-title { + color: #444; +} + +.light .rt-view-notifications-head { + color: whitesmoke; + background-color: whitesmoke; + border-bottom: 1px solid #bebebe; +} + +.light .rt-view-notifications-body { + color: #555; + background-color: #eaeaea; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; +} + +.light .rt-view-ekg-metrics-title { + color: #444; +} + +.light .rt-view-ekg-metrics-head { + color: whitesmoke; + background-color: whitesmoke; + border-bottom: 1px solid #bebebe; +} + +.light .rt-view-ekg-metrics-body { + color: #555; + background-color: #eaeaea; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; +} + +.light .rt-view-main-table { + background-color: #f5f5f5; + color: #444; +} + +.light .rt-view-main-table td { + padding-top: 17px; + padding-bottom: 17px; + border-bottom: 0px solid #444; +} + +.light .rt-view-main-table th { + color: #444; + border-bottom: 2px solid #cfcfcf; + vertical-align: middle; +} + +.light .rt-view-peer-table { + background-color: #eaeaea; + color: #444; +} + +.light .rt-view-peer-table td { + padding-top: 10px; + padding-bottom: 10px; + border-bottom: 0px solid #444; +} + +.light .rt-view-peer-table th { + color: #444; + border-bottom: 2px solid #cfcfcf; + vertical-align: middle; +} + +.light .rt-view-errors-table { + background-color: #eaeaea; + color: #444; +} + +.light .rt-view-errors-table td { + padding-top: 10px; + padding-bottom: 10px; + border-bottom: 0px solid #444; +} + +.light .rt-view-errors-table th { + color: #444; + border-bottom: 2px solid #cfcfcf; + vertical-align: middle; +} + +.light .rt-view-chart-group-title { + color: #444; + font-weight: bold; + margin-left: 13px; + font-size: 110%; +} + +.light .rt-view-show-hide-chart-group svg { + width: 21px; + color: #038b8c; +} + +.light .rt-view-chart-icon svg { + width: 19px; + margin-right: 11px; + color: #038b8c; +} + +.light .rt-view-footer { + background-color: #efefef; + color: #777; + padding-top: 16px; + padding-bottom: 12px; + border-top: 1px solid #dbdbdb; + font-size: 80%; +} + +.light .rt-view-footer-github svg { + width: 23px; + margin-top: 1px; + color: #038b8c; +} + +.light .rt-view-percent-done { + color: #048b04; +} + +.light .rt-view-what-icon svg { + width: 18px; + margin-left: 12px; + color: #9a9a9a; +} + +.light .rt-view-epoch-end svg { + width: 16px; + margin-left: 20px; + margin-right: 5px; + color: #038b8c; +} + +.light .rt-view-notify-menu-icon svg { + width: 15px; + padding-top: 6px; + margin-right: 9px; + color: #0033ad; +} +|] + +chartTextLight + , chartTextDark + , chartGridDark + , chartGridLight :: String +chartGridDark = "#ccc" +chartGridLight = "#555" +chartTextDark = "#555" +chartTextLight = "#ddd" diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/Charts.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/Charts.hs new file mode 100644 index 00000000000..7f80b6d0106 --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/Charts.hs @@ -0,0 +1,298 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} + +module Cardano.Tracer.Handlers.RTView.UI.Charts + ( initColors + , initDatasetsIndices + , initDatasetsTimestamps + , getDatasetIx + , addNodeDatasetsToCharts + , addPointsToChart + , addAllPointsToChart + , getLatestDisplayedTS + , saveLatestDisplayedTS + , restoreChartsSettings + , saveChartsSettings + , changeChartsToLightTheme + , changeChartsToDarkTheme + ) where + +-- | The module 'Cardano.Tracer.Handlers.RTView.UI.JS.Charts' contains the tools +-- for rendering/updating charts using Chart.JS library, via JS FFI. +-- +-- This module contains common tools for charts' state. We need it to be able +-- to re-render their values after web-page reloading. + +import Control.Concurrent.STM (atomically) +import Control.Concurrent.STM.TBQueue +import Control.Concurrent.STM.TVar +import Control.Exception.Extra (ignore, try_) +import Control.Monad (forM, forM_, when) +import Data.Aeson +import Data.List.Extra (chunksOf) +import qualified Data.Map.Strict as M +import Data.Maybe (catMaybes, fromMaybe) +import qualified Data.Set as S +import Data.Text (pack) +import Graphics.UI.Threepenny.Core +import Text.Read (readMaybe) + +import Cardano.Tracer.Types +import Cardano.Tracer.Handlers.RTView.State.Displayed +import Cardano.Tracer.Handlers.RTView.State.Historical +import Cardano.Tracer.Handlers.RTView.System +import Cardano.Tracer.Handlers.RTView.UI.CSS.Own +import qualified Cardano.Tracer.Handlers.RTView.UI.JS.Charts as Chart +import qualified Cardano.Tracer.Handlers.RTView.UI.JS.Utils as JS +import Cardano.Tracer.Handlers.RTView.UI.Types +import Cardano.Tracer.Handlers.RTView.UI.Utils + +chartsIds :: [ChartId] +chartsIds = [minBound .. maxBound] + +initColors :: UI Colors +initColors = liftIO $ do + q <- newTBQueueIO . fromIntegral $ length colors + mapM_ (atomically . writeTBQueue q . Color) colors + return q + where + -- | There are unique colors for each chart line corresponding to each connected node. + -- To make chart lines visually distinct, the colors in this list are contrast enough. + -- It is assumed that the number of colors in this list is enough. + colors = + [ "#ff0000", "#66ff33", "#0066ff", "#ff00ff", "#cc0066", "#00ccff", "#ff9933", "#cc9900" + , "#33cc33", "#0099ff" + ] + +getNewColor :: Colors -> UI Color +getNewColor q = + (liftIO . atomically $ tryReadTBQueue q) >>= \case + Just color -> return color + Nothing -> return defaultColor + where + defaultColor = Color "#cccc00" + +initDatasetsIndices :: UI DatasetsIndices +initDatasetsIndices = liftIO . newTVarIO $ M.empty + +saveDatasetIx + :: DatasetsIndices + -> NodeId + -> Index + -> UI () +saveDatasetIx indices nodeId ix = liftIO . atomically $ + modifyTVar' indices $ \currentIndices -> + case M.lookup nodeId currentIndices of + Nothing -> M.insert nodeId ix currentIndices + Just _ -> M.adjust (const ix) nodeId currentIndices + +getDatasetIx + :: DatasetsIndices + -> NodeId + -> UI (Maybe Index) +getDatasetIx indices nodeId = liftIO $ + M.lookup nodeId <$> readTVarIO indices + +addNodeDatasetsToCharts + :: Window + -> NodeId + -> Colors + -> DatasetsIndices + -> DisplayedElements + -> UI () +addNodeDatasetsToCharts window nodeId@(NodeId anId) colors datasetIndices displayedElements = do + colorForNode@(Color code) <- getNewColor colors + forM_ chartsIds $ \chartId -> do + newIx <- Chart.getDatasetsLengthChartJS chartId + nodeName <- liftIO $ getDisplayedValue displayedElements nodeId (anId <> "__node-name") + Chart.addDatasetChartJS chartId (fromMaybe anId nodeName) colorForNode + saveDatasetIx datasetIndices nodeId (Index newIx) + -- Change color label for node name as well. + findAndSet (set style [("color", code)]) window (anId <> "__node-chart-label") + +initDatasetsTimestamps :: UI DatasetsTimestamps +initDatasetsTimestamps = liftIO . newTVarIO $ M.empty + +saveLatestDisplayedTS + :: DatasetsTimestamps + -> NodeId + -> DataName + -> POSIXTime + -> UI () +saveLatestDisplayedTS tss nodeId dataName ts = liftIO . atomically $ + modifyTVar' tss $ \currentTimestamps -> + case M.lookup nodeId currentTimestamps of + Nothing -> + -- There is no latest timestamps for charts for this node yet. + let newTSForNode = M.singleton dataName ts + in M.insert nodeId newTSForNode currentTimestamps + Just tssForNode -> + let newTSForNode = + case M.lookup dataName tssForNode of + Nothing -> + -- There is no latest timestamps for this dataName yet. + M.insert dataName ts tssForNode + Just _ -> + M.adjust (const ts) dataName tssForNode + in M.adjust (const newTSForNode) nodeId currentTimestamps + +getLatestDisplayedTS + :: DatasetsTimestamps + -> NodeId + -> DataName + -> UI (Maybe POSIXTime) +getLatestDisplayedTS tss nodeId dataName = liftIO $ do + tss' <- readTVarIO tss + case M.lookup nodeId tss' of + Nothing -> return Nothing + Just tssForNode -> return $ M.lookup dataName tssForNode + +-- Each chart updates independently from others. Because of this, the user +-- can specify "auto-update period" for each chart. Some of data (by its nature) +-- shoudn't be updated too frequently. +-- +-- All points will be added to all datasets (corresponding to the number of connected nodes) +-- using one single FFI-call, for better performance. +-- +-- 'addAllPointsToChart' doesn not do average calculation, it pushes all the points as they are. +addPointsToChart, addAllPointsToChart + :: ConnectedNodes + -> History + -> DatasetsIndices + -> DatasetsTimestamps + -> DataName + -> ChartId + -> UI () +addPointsToChart = doAddPointsToChart replacePointsByAvgPoints +addAllPointsToChart = doAddPointsToChart id + +doAddPointsToChart + :: ([HistoricalPoint] -> [HistoricalPoint]) + -> ConnectedNodes + -> History + -> DatasetsIndices + -> DatasetsTimestamps + -> DataName + -> ChartId + -> UI () +doAddPointsToChart replaceByAvg connectedNodes hist datasetIndices datasetTimestamps dataName chartId = do + connected <- liftIO $ S.toList <$> readTVarIO connectedNodes + dataForPush <- + forM connected $ \nodeId -> + liftIO (getHistoricalData hist nodeId dataName) >>= \case + [] -> return Nothing + points -> do + let (latestTS, _) = last points + getLatestDisplayedTS datasetTimestamps nodeId dataName >>= \case + Nothing -> + -- There is no saved latestTS for this node and chart yet, + -- so display all the history and remember the latestTS. + getDatasetIx datasetIndices nodeId >>= \case + Nothing -> return Nothing + Just ix -> + return . Just $ ( (nodeId, latestTS) + , (ix, replaceByAvg points) + ) + Just storedTS -> + -- Some of the history for this node and chart is already displayed, + -- so cut displayed points first. The only points we should add now + -- are the points with 'ts' that is bigger than 'storedTS'. + getDatasetIx datasetIndices nodeId >>= \case + Nothing -> return Nothing + Just ix -> + return . Just $ ( (nodeId, latestTS) + , (ix, replaceByAvg $! cutOldPoints storedTS points) + ) + let (nodeIdsWithLatestTss, datasetIxsWithPoints) = unzip $ catMaybes dataForPush + Chart.addAllPointsChartJS chartId datasetIxsWithPoints + forM_ nodeIdsWithLatestTss $ \(nodeId, latestTS) -> + saveLatestDisplayedTS datasetTimestamps nodeId dataName latestTS + +replacePointsByAvgPoints :: [HistoricalPoint] -> [HistoricalPoint] +replacePointsByAvgPoints [] = [] +replacePointsByAvgPoints points = + map calculateAvgPoint $ chunksOf numberOfPointsToAverage points + where + calculateAvgPoint :: [HistoricalPoint] -> HistoricalPoint + calculateAvgPoint pointsForAvg = + let !valuesSum = sum [v | (_, v) <- pointsForAvg] + avgValue = + case valuesSum of + ValueI i -> ValueD $ fromIntegral i / fromIntegral (length pointsForAvg) + ValueD d -> ValueD $ d / fromIntegral (length pointsForAvg) + (latestTS, _) = last pointsForAvg + in (latestTS, avgValue) + -- TODO: calculate it, remove hardcoded value! + -- P1 - Period of calling 'addAllPointsToChart' + -- P2 - Period of asking of EKG.Metrics + -- Number of new points received since last call of 'addAllPointsToChart' + -- is P1/P2 + -- Maximum number of points to calculate avg = 15 s. + numberOfPointsToAverage = 15 + +cutOldPoints + :: POSIXTime + -> [HistoricalPoint] + -> [HistoricalPoint] +cutOldPoints _ [] = [] +cutOldPoints oldTS (point@(ts, _):newerPoints) = + if ts > oldTS + then + -- This point is newer than 'oldTS', take it and all the following + -- as well, because they are definitely newer (points are sorted by ts). + point : newerPoints + else + -- This point are older than 'oldTS', it means that it already was displayed. + cutOldPoints oldTS newerPoints + +restoreChartsSettings :: UI () +restoreChartsSettings = readSavedChartsSettings >>= setCharts + where + setCharts settings = + forM_ settings $ \(chartId, ChartSettings tr up) -> do + JS.selectOption (show chartId <> show TimeRangeSelect) tr + JS.selectOption (show chartId <> show UpdatePeriodSelect) up + Chart.setTimeRange chartId tr + when (tr == 0) $ Chart.resetZoomChartJS chartId + +saveChartsSettings :: Window -> UI () +saveChartsSettings window = do + settings <- + forM chartsIds $ \chartId -> do + selectedTR <- getOptionValue $ show chartId <> show TimeRangeSelect + selectedUP <- getOptionValue $ show chartId <> show UpdatePeriodSelect + return (chartId, ChartSettings selectedTR selectedUP) + liftIO . ignore $ do + pathToChartsConfig <- getPathToChartsConfig + encodeFile pathToChartsConfig settings + where + getOptionValue selectId = do + v <- findAndGetValue window (pack selectId) + case readMaybe v of + Just (valueInS :: Int) -> return valueInS + Nothing -> return 0 + +readSavedChartsSettings :: UI ChartsSettings +readSavedChartsSettings = liftIO $ + try_ (decodeFileStrict' =<< getPathToChartsConfig) >>= \case + Right (Just (settings :: ChartsSettings)) -> return settings + _ -> return defaultSettings + where + defaultSettings = + [ (chartId, ChartSettings defaultTimeRangeInS defaultUpdatePeriodInS) + | chartId <- chartsIds + ] + defaultTimeRangeInS = 0 -- All time + defaultUpdatePeriodInS = 15 + +changeChartsToLightTheme :: UI () +changeChartsToLightTheme = + forM_ chartsIds $ \chartId -> + Chart.changeColorsChartJS chartId (Color chartTextDark) (Color chartGridDark) + +changeChartsToDarkTheme :: UI () +changeChartsToDarkTheme = + forM_ chartsIds $ \chartId -> + Chart.changeColorsChartJS chartId (Color chartTextLight) (Color chartGridLight) diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/About.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/About.hs new file mode 100644 index 00000000000..3c9d46fb9ad --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/About.hs @@ -0,0 +1,103 @@ +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MultiWayIf #-} + +module Cardano.Tracer.Handlers.RTView.UI.HTML.About + ( mkAboutInfo + ) where + +import qualified Data.Text as T +import Data.Version (showVersion) +import qualified Graphics.UI.Threepenny as UI +import Graphics.UI.Threepenny.Core +import System.Directory (makeAbsolute) +import System.Environment (getArgs) +import System.Info.Extra (isWindows, isMac) + +import Cardano.Git.Rev (gitRev) + +import Cardano.Tracer.Handlers.RTView.System +import Cardano.Tracer.Handlers.RTView.UI.JS.Utils +import Cardano.Tracer.Handlers.RTView.UI.Img.Icons +import Cardano.Tracer.Handlers.RTView.UI.Utils +import Paths_cardano_tracer (version) + +mkAboutInfo :: UI Element +mkAboutInfo = do + pathToConfig <- liftIO $ getArgs >>= \case + ["-c", path] -> makeAbsolute path + ["--config", path] -> makeAbsolute path + _ -> return "" + copyPath <- image "has-tooltip-multiline has-tooltip-top rt-view-copy-icon" copySVG + # set dataTooltip "Click to copy the path" + on UI.click copyPath . const $ + copyTextToClipboard pathToConfig + closeIt <- UI.button #. "delete" + pid <- getProcessId + info <- + UI.div #. "modal" #+ + [ UI.div #. "modal-background" #+ [] + , UI.div #. "modal-card" #+ + [ UI.header #. "modal-card-head rt-view-about-head" #+ + [ UI.p #. "modal-card-title rt-view-about-title" # set text "About" + , element closeIt + ] + , UI.mkElement "section" #. "modal-card-body rt-view-about-body" #+ + [ UI.div #. "columns" #+ + [ UI.div #. "column ml-1 is-one-third" #+ + [ UI.p #. "mb-3" #+ + [ image "rt-view-overview-icon" versionSVG + , string "Version" + ] + , UI.p #. "mb-3" #+ + [ image "rt-view-overview-icon" commitSVG + , string "Commit" + ] + , UI.p #. "mb-3" #+ + [ image "rt-view-overview-icon" platformSVG + , string "Platform" + ] + , UI.p #. "mb-3" #+ + [ image "rt-view-overview-icon" configSVG + , string "Configuration" + ] + , UI.p #. "mb-1" #+ + [ image "rt-view-overview-icon" serverSVG + , string "Process ID" + ] + ] + , UI.div #. "column has-text-weight-semibold" #+ + [ UI.p #. "mb-3" #+ [string $ showVersion version] + , UI.p #. "mb-3" #+ + [ UI.anchor + #. ("rt-view-href is-family-monospace has-text-weight-normal" + <> " has-tooltip-multiline has-tooltip-top") + # set UI.href ("https://github.com/input-output-hk/cardano-node/commit/" <> commit) + # set UI.target "_blank" + # set dataTooltip "Browse repository on this commit" + # set text commit + , image "rt-view-href-icon" externalLinkSVG + ] + , UI.p #. "mb-3" #+ + [ string $ if | isWindows -> "Windows" + | isMac -> "macOS" + | otherwise -> "Linux" + ] + , UI.p #. "mb-3" #+ + [ UI.span #. ("tag is-info is-light is-rounded is-medium mr-3" + <> " has-tooltip-multiline has-tooltip-top rt-view-logs-path") + # set dataTooltip "The path to configuration file" + # set text (shortenPath pathToConfig) + , element copyPath + ] + , UI.p #. "mb-1" #+ + [ string $ show pid + ] + ] + ] + ] + ] + ] + on UI.click closeIt . const $ element info #. "modal" + return info + where + commit = T.unpack . T.take 7 $ gitRev diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Body.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Body.hs new file mode 100644 index 00000000000..8b408f5e5d8 --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Body.hs @@ -0,0 +1,682 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} + +module Cardano.Tracer.Handlers.RTView.UI.HTML.Body + ( mkPageBody + ) where + +import Control.Monad (void, unless, when) +import Control.Monad.Extra (whenM, whenJustM) +import Data.Text (Text) +import qualified Graphics.UI.Threepenny as UI +import Graphics.UI.Threepenny.Core +import Text.Read (readMaybe) + +import Cardano.Tracer.Configuration +import Cardano.Tracer.Handlers.RTView.State.Historical +import Cardano.Tracer.Handlers.RTView.UI.Img.Icons +import Cardano.Tracer.Handlers.RTView.UI.HTML.About +import Cardano.Tracer.Handlers.RTView.UI.HTML.NoNodes +import Cardano.Tracer.Handlers.RTView.UI.HTML.Notifications +import qualified Cardano.Tracer.Handlers.RTView.UI.JS.Charts as Chart +import Cardano.Tracer.Handlers.RTView.UI.JS.ChartJS +import Cardano.Tracer.Handlers.RTView.UI.JS.Utils +import Cardano.Tracer.Handlers.RTView.UI.Charts +import Cardano.Tracer.Handlers.RTView.UI.Theme +import Cardano.Tracer.Handlers.RTView.UI.Types +import Cardano.Tracer.Handlers.RTView.UI.Utils +import Cardano.Tracer.Types + +mkPageBody + :: UI.Window + -> Network + -> ConnectedNodes + -> ResourcesHistory + -> BlockchainHistory + -> TransactionsHistory + -> DatasetsIndices + -> DatasetsTimestamps + -> UI Element +mkPageBody window networkConfig connected + (ResHistory rHistory) (ChainHistory cHistory) (TXHistory tHistory) + dsIxs dsTss = do + txsProcessedNumTimer <- mkChartTimer connected tHistory dsIxs dsTss TxsProcessedNumData TxsProcessedNumChart + mempoolBytesTimer <- mkChartTimer connected tHistory dsIxs dsTss MempoolBytesData MempoolBytesChart + txsInMempoolTimer <- mkChartTimer connected tHistory dsIxs dsTss TxsInMempoolData TxsInMempoolChart + + txsProcessedNumChart <- mkChart window txsProcessedNumTimer TxsProcessedNumChart "Processed txs" + mempoolBytesChart <- mkChart window mempoolBytesTimer MempoolBytesChart "Mempool size" + txsInMempoolChart <- mkChart window txsInMempoolTimer TxsInMempoolChart "Txs in mempool" + + -- Resources charts. + cpuTimer <- mkChartTimer connected rHistory dsIxs dsTss CPUData CPUChart + memoryTimer <- mkChartTimer connected rHistory dsIxs dsTss MemoryData MemoryChart + gcMajorNumTimer <- mkChartTimer connected rHistory dsIxs dsTss GCMajorNumData GCMajorNumChart + gcMinorNumTimer <- mkChartTimer connected rHistory dsIxs dsTss GCMinorNumData GCMinorNumChart + gcLiveMemoryTimer <- mkChartTimer connected rHistory dsIxs dsTss GCLiveMemoryData GCLiveMemoryChart + cpuTimeGCTimer <- mkChartTimer connected rHistory dsIxs dsTss CPUTimeGCData CPUTimeGCChart + cpuTimeAppTimer <- mkChartTimer connected rHistory dsIxs dsTss CPUTimeAppData CPUTimeAppChart + threadsNumTimer <- mkChartTimer connected rHistory dsIxs dsTss ThreadsNumData ThreadsNumChart + + cpuChart <- mkChart window cpuTimer CPUChart "CPU usage" + memoryChart <- mkChart window memoryTimer MemoryChart "Memory usage" + gcMajorNumChart <- mkChart window gcMajorNumTimer GCMajorNumChart "Number of major GCs" + gcMinorNumChart <- mkChart window gcMinorNumTimer GCMinorNumChart "Number of minor GCs" + gcLiveMemoryChart <- mkChart window gcLiveMemoryTimer GCLiveMemoryChart "GC, live data in heap" + cpuTimeGCChart <- mkChart window cpuTimeGCTimer CPUTimeGCChart "CPU time used by GC" + cpuTimeAppChart <- mkChart window cpuTimeAppTimer CPUTimeAppChart "CPU time used by app" + threadsNumChart <- mkChart window threadsNumTimer ThreadsNumChart "Number of threads" + + -- Blockchain charts. + chainDensityTimer <- mkChartTimer connected cHistory dsIxs dsTss ChainDensityData ChainDensityChart + slotNumTimer <- mkChartTimer connected cHistory dsIxs dsTss SlotNumData SlotNumChart + blockNumTimer <- mkChartTimer connected cHistory dsIxs dsTss BlockNumData BlockNumChart + slotInEpochTimer <- mkChartTimer connected cHistory dsIxs dsTss SlotInEpochData SlotInEpochChart + epochTimer <- mkChartTimer connected cHistory dsIxs dsTss EpochData EpochChart + + chainDensityChart <- mkChart window chainDensityTimer ChainDensityChart "Chain density" + slotNumChart <- mkChart window slotNumTimer SlotNumChart "Slot height" + blockNumChart <- mkChart window blockNumTimer BlockNumChart "Block height" + slotInEpochChart <- mkChart window slotInEpochTimer SlotInEpochChart "Slot in epoch" + epochChart <- mkChart window epochTimer EpochChart "Epoch" + + -- Leadership charts. + cannotForgeTimer <- mkChartTimer' connected cHistory dsIxs dsTss NodeCannotForgeData NodeCannotForgeChart + forgedSlotTimer <- mkChartTimer' connected cHistory dsIxs dsTss ForgedSlotLastData ForgedSlotLastChart + nodeIsLeaderTimer <- mkChartTimer' connected cHistory dsIxs dsTss NodeIsLeaderData NodeIsLeaderChart + nodeIsNotLeaderTimer <- mkChartTimer' connected cHistory dsIxs dsTss NodeIsNotLeaderData NodeIsNotLeaderChart + forgedInvalidTimer <- mkChartTimer' connected cHistory dsIxs dsTss ForgedInvalidSlotLastData ForgedInvalidSlotLastChart + adoptedTimer <- mkChartTimer' connected cHistory dsIxs dsTss AdoptedSlotLastData AdoptedSlotLastChart + notAdoptedTimer <- mkChartTimer' connected cHistory dsIxs dsTss NotAdoptedSlotLastData NotAdoptedSlotLastChart + aboutToLeadTimer <- mkChartTimer' connected cHistory dsIxs dsTss AboutToLeadSlotLastData AboutToLeadSlotLastChart + couldNotForgeTimer <- mkChartTimer' connected cHistory dsIxs dsTss CouldNotForgeSlotLastData CouldNotForgeSlotLastChart + + cannotForgeChart <- mkChart window cannotForgeTimer NodeCannotForgeChart "Cannot forge" + forgedSlotChart <- mkChart window forgedSlotTimer ForgedSlotLastChart "Forged" + nodeIsLeaderChart <- mkChart window nodeIsLeaderTimer NodeIsLeaderChart "Is leader" + nodeIsNotLeaderChart <- mkChart window nodeIsNotLeaderTimer NodeIsNotLeaderChart "Is not leader" + forgedInvalidChart <- mkChart window forgedInvalidTimer ForgedInvalidSlotLastChart "Forged invalid" + adoptedChart <- mkChart window adoptedTimer AdoptedSlotLastChart "Is adopted" + notAdoptedChart <- mkChart window notAdoptedTimer NotAdoptedSlotLastChart "Is not adopted" + aboutToLeadChart <- mkChart window aboutToLeadTimer AboutToLeadSlotLastChart "About to lead" + couldNotForgeChart <- mkChart window couldNotForgeTimer CouldNotForgeSlotLastChart "Could not forge" + + -- Visibility of charts groups. + showHideTxs <- image "has-tooltip-multiline has-tooltip-top rt-view-show-hide-chart-group" showSVG + # set dataTooltip "Click to hide Transactions" + # set dataState shownState + showHideChain <- image "has-tooltip-multiline has-tooltip-top rt-view-show-hide-chart-group" showSVG + # set dataTooltip "Click to hide Blockchain" + # set dataState shownState + showHideLeadership <- image "has-tooltip-multiline has-tooltip-top rt-view-show-hide-chart-group" showSVG + # set dataTooltip "Click to hide Leadership" + # set dataState shownState + showHideResources <- image "has-tooltip-multiline has-tooltip-top rt-view-show-hide-chart-group" showSVG + # set dataTooltip "Click to hide Resources" + # set dataState shownState + + on UI.click showHideTxs . const $ + changeVisibilityForCharts window showHideTxs "transactions-charts" "Transactions" + on UI.click showHideChain . const $ + changeVisibilityForCharts window showHideChain "chain-charts" "Blockchain" + on UI.click showHideLeadership . const $ + changeVisibilityForCharts window showHideLeadership "leadership-charts" "Leadership" + on UI.click showHideResources . const $ + changeVisibilityForCharts window showHideResources "resources-charts" "Resources" + + -- Body. + body <- + UI.getBody window #+ + [ UI.div #. "wrapper" #+ + [ UI.div ## "preloader" #. "pageloader is-active" #+ + [ UI.span #. "title" # set text "Just a second..." + ] + , topNavigation window + , UI.div ## "no-nodes" #. "container is-max-widescreen has-text-centered" #+ + [ image "rt-view-no-nodes-icon" noNodesSVG ## "no-nodes-icon" + , UI.p ## "no-nodes-message" #. "rt-view-no-nodes-message" #+ + [ string "There are no connected nodes. Yet." + ] + ] + , mkNoNodesInfo networkConfig + , UI.mkElement "section" #. "section" #+ + [ UI.div ## "main-table-container" + #. "table-container" + # hideIt #+ + [ UI.table ## "main-table" #. "table rt-view-main-table" #+ + [ UI.mkElement "thead" #+ + [ UI.tr ## "node-name-row" #+ + [ UI.th #. "rt-view-main-table-description" + #+ [UI.span # set html " "] + ] + ] + , UI.mkElement "tbody" #+ + [ UI.tr ## "node-version-row" #+ + [ UI.td #+ [ image "rt-view-overview-icon" versionSVG + , string "Version" + ] + ] + , UI.tr ## "node-commit-row" #+ + [ UI.td #+ [ image "rt-view-overview-icon" commitSVG + , string "Commit" + ] + ] + , UI.tr ## "node-protocol-row" #+ + [ UI.td #+ [ image "rt-view-overview-icon" protocolSVG + , string "Protocol" + ] + ] + , UI.tr ## "node-era-row" #+ + [ UI.td #+ [ image "rt-view-overview-icon" eraSVG + , string "Era" + ] + ] + , UI.tr ## "node-epoch-row" #+ + [ UI.td #+ [ image "rt-view-overview-icon" lengthSVG + , string "Epoch" + , image "has-tooltip-multiline has-tooltip-right rt-view-what-icon" whatSVG + # set dataTooltip ("Current epoch from this node." + <> " It can be outdated because of node's out of sync!") + ] + ] + , UI.tr ## "node-sync-row" #+ + [ UI.td #+ [ image "rt-view-overview-icon" refreshSVG + , string "Sync" + ] + ] + , UI.tr ## "node-system-start-time-row" #+ + [ UI.td #+ [ image "rt-view-overview-icon" systemStartSVG + , string "Blockchain start" + ] + ] + , UI.tr ## "node-start-time-row" #+ + [ UI.td #+ [ image "rt-view-overview-icon" startSVG + , string "Node start" + ] + ] + , UI.tr ## "node-uptime-row" #+ + [ UI.td #+ [ image "rt-view-overview-icon" uptimeSVG + , string "Uptime" + ] + ] + , UI.tr ## "node-logs-row" #+ + [ UI.td #+ [ image "rt-view-overview-icon" logsSVG + , string "Logs" + ] + ] + , UI.tr ## "node-block-replay-row" #+ + [ UI.td #+ [ image "rt-view-overview-icon" blocksSVG + , string "Block replay" + ] + ] + , UI.tr ## "node-chunk-validation-row" #+ + [ UI.td #+ [ image "rt-view-overview-icon" dbSVG + , string "Chunk validation" + ] + ] + , UI.tr ## "node-update-ledger-db-row" #+ + [ UI.td #+ [ image "rt-view-overview-icon" dbSVG + , string "Ledger DB" + ] + ] + , UI.tr ## "node-peers-row" #+ + [ UI.td #+ [ image "rt-view-overview-icon" peersSVG + , string "Peers" + ] + ] + , UI.tr ## "node-errors-row" #+ + [ UI.td #+ [ image "rt-view-overview-icon" errorsSVG + , string "Errors" + ] + ] + , UI.tr ## "node-leadership-row" #+ + [ UI.td #+ [ image "rt-view-overview-icon" leaderSVG + , string "Leadership" + , image "has-tooltip-multiline has-tooltip-right rt-view-what-icon" whatSVG + # set dataTooltip "How many times this node was leader" + ] + ] + , UI.tr ## "node-forged-blocks-row" #+ + [ UI.td #+ [ image "rt-view-overview-icon" forgeSVG + , string "Forged blocks" + , image "has-tooltip-multiline has-tooltip-right rt-view-what-icon" whatSVG + # set dataTooltip "How many blocks did forge by this node" + ] + ] + , UI.tr ## "node-cannot-forge-row" #+ + [ UI.td #+ [ image "rt-view-overview-icon" notForgeSVG + , string "Cannot forge" + , image "has-tooltip-multiline has-tooltip-right rt-view-what-icon" whatSVG + # set dataTooltip "How many times this node could not forge" + ] + ] + , UI.tr ## "node-missed-slots-row" #+ + [ UI.td #+ [ image "rt-view-overview-icon" missedSVG + , string "Missed slots" + , image "has-tooltip-multiline has-tooltip-right rt-view-what-icon" whatSVG + # set dataTooltip "How many slots were missed by this node" + ] + ] + , UI.tr ## "node-current-kes-period-row" #+ + [ UI.td #+ [ image "rt-view-overview-icon" certificateSVG + , string "KES current" + ] + ] + , UI.tr ## "node-op-cert-expiry-kes-period-row" #+ + [ UI.td #+ [ image "rt-view-overview-icon" certificateSVG + , string "KES Expiry" + ] + ] + , UI.tr ## "node-remaining-kes-periods-row" #+ + [ UI.td #+ [ image "rt-view-overview-icon" certificateSVG + , string "Remainig KES" + ] + ] + , UI.tr ## "node-op-cert-start-kes-period-row" #+ + [ UI.td #+ [ image "rt-view-overview-icon" certificateSVG + , string "Op Cert Start KES" + ] + ] + , UI.tr ## "node-days-until-op-cert-renew-row" #+ + [ UI.td #+ [ image "rt-view-overview-icon" endSVG + , string "Days until Op Cert renew" + ] + ] + , UI.tr ## "node-ekg-metrics-row" #+ + [ UI.td #+ [ image "rt-view-overview-icon" ekgMetricsSVG + , string "EKG metrics" + , image "has-tooltip-multiline has-tooltip-right rt-view-what-icon" whatSVG + # set dataTooltip ("All EKG metrics forwarded by this node, " + <> "as they are (no preparing)") + ] + ] + ] + ] + ] + ] + , UI.mkElement "section" #. "section" #+ + [ UI.div ## "main-charts-container" + #. "is-fluid rt-view-charts-container" + # hideIt #+ + [ UI.p #. "mb-5" #+ + [ UI.div #. "divider" #+ + [ element showHideChain + , UI.span #. "rt-view-chart-group-title" # set text "Blockchain" + ] + ] + , UI.div ## "chain-charts" #. "columns" #+ + [ UI.div #. "column" #+ + [ element chainDensityChart + , element epochChart + , element blockNumChart + ] + , UI.div #. "column" #+ + [ element slotInEpochChart + , element slotNumChart + ] + ] + -- Leadership charts. + , UI.p #. "mb-5" #+ + [ UI.div #. "divider" #+ + [ element showHideLeadership + , UI.span #. "rt-view-chart-group-title" # set text "Leadership" + ] + ] + , UI.div ## "leadership-charts" #. "columns" #+ + [ UI.div #. "column" #+ + [ element forgedSlotChart + , element nodeIsLeaderChart + , element forgedInvalidChart + , element adoptedChart + , element aboutToLeadChart + ] + , UI.div #. "column" #+ + [ element cannotForgeChart + , element nodeIsNotLeaderChart + , element couldNotForgeChart + , element notAdoptedChart + ] + ] + -- Transactions charts. + , UI.p #. "mb-5" #+ + [ UI.div #. "divider" #+ + [ element showHideTxs + , UI.span #. "rt-view-chart-group-title" # set text "Transactions" + ] + ] + , UI.div ## "transactions-charts" #. "columns" #+ + [ UI.div #. "column" #+ + [ element mempoolBytesChart + , element txsProcessedNumChart + ] + , UI.div #. "column" #+ + [ element txsInMempoolChart + ] + ] + -- Resources charts. + , UI.p #. "mb-5" #+ + [ UI.div #. "divider" #+ + [ element showHideResources + , UI.span #. "rt-view-chart-group-title" # set text "Resources" + ] + ] + , UI.div ## "resources-charts" #. "columns" #+ + [ UI.div #. "column" #+ + [ element cpuChart + , element gcMajorNumChart + , element gcLiveMemoryChart + , element cpuTimeGCChart + ] + , UI.div #. "column" #+ + [ element memoryChart + , element gcMinorNumChart + , element threadsNumChart + , element cpuTimeAppChart + ] + ] + ] + ] + , footer + ] + -- JS + , UI.mkElement "script" # set UI.html chartJS + , UI.mkElement "script" # set UI.html chartJSLuxon + , UI.mkElement "script" # set UI.html chartJSAdapter + , UI.mkElement "script" # set UI.html chartJSPluginZoom + ] + + closeModalsByEscapeButton + + Chart.prepareChartsJS + + Chart.newTimeChartJS TxsProcessedNumChart "" + Chart.newTimeChartJS MempoolBytesChart "MB" + Chart.newTimeChartJS TxsInMempoolChart "" + + Chart.newTimeChartJS CPUChart "Percent" + Chart.newTimeChartJS MemoryChart "MB" + Chart.newTimeChartJS GCMajorNumChart "" + Chart.newTimeChartJS GCMinorNumChart "" + Chart.newTimeChartJS GCLiveMemoryChart "MB" + Chart.newTimeChartJS CPUTimeGCChart "Milliseconds" + Chart.newTimeChartJS CPUTimeAppChart "Milliseconds" + Chart.newTimeChartJS ThreadsNumChart "" + + Chart.newTimeChartJS ChainDensityChart "Percent" + Chart.newTimeChartJS SlotNumChart "" + Chart.newTimeChartJS BlockNumChart "" + Chart.newTimeChartJS SlotInEpochChart "" + Chart.newTimeChartJS EpochChart "" + + Chart.newTimeChartJS NodeCannotForgeChart "Slots" + Chart.newTimeChartJS ForgedSlotLastChart "Slots" + Chart.newTimeChartJS NodeIsLeaderChart "Slots" + Chart.newTimeChartJS NodeIsNotLeaderChart "Slots" + Chart.newTimeChartJS ForgedInvalidSlotLastChart "Slots" + Chart.newTimeChartJS AdoptedSlotLastChart "Slots" + Chart.newTimeChartJS NotAdoptedSlotLastChart "Slots" + Chart.newTimeChartJS AboutToLeadSlotLastChart "Slots" + Chart.newTimeChartJS CouldNotForgeSlotLastChart "Slots" + + -- Start all timer. + + UI.start txsProcessedNumTimer + UI.start mempoolBytesTimer + UI.start txsInMempoolTimer + + UI.start cpuTimer + UI.start memoryTimer + UI.start gcMajorNumTimer + UI.start gcMinorNumTimer + UI.start gcLiveMemoryTimer + UI.start cpuTimeGCTimer + UI.start cpuTimeAppTimer + UI.start threadsNumTimer + + UI.start chainDensityTimer + UI.start slotNumTimer + UI.start blockNumTimer + UI.start slotInEpochTimer + UI.start epochTimer + + UI.start cannotForgeTimer + UI.start forgedSlotTimer + UI.start nodeIsLeaderTimer + UI.start nodeIsNotLeaderTimer + UI.start forgedInvalidTimer + UI.start adoptedTimer + UI.start notAdoptedTimer + UI.start aboutToLeadTimer + UI.start couldNotForgeTimer + + on UI.disconnect window . const $ do + UI.stop txsProcessedNumTimer + UI.stop mempoolBytesTimer + UI.stop txsInMempoolTimer + + UI.stop cpuTimer + UI.stop memoryTimer + UI.stop gcMajorNumTimer + UI.stop gcMinorNumTimer + UI.stop gcLiveMemoryTimer + UI.stop cpuTimeGCTimer + UI.stop cpuTimeAppTimer + UI.stop threadsNumTimer + + UI.stop chainDensityTimer + UI.stop slotNumTimer + UI.stop blockNumTimer + UI.stop slotInEpochTimer + UI.stop epochTimer + + UI.stop cannotForgeTimer + UI.stop forgedSlotTimer + UI.stop nodeIsLeaderTimer + UI.stop nodeIsNotLeaderTimer + UI.stop forgedInvalidTimer + UI.stop adoptedTimer + UI.stop notAdoptedTimer + UI.stop aboutToLeadTimer + UI.stop couldNotForgeTimer + + return body + +topNavigation :: UI.Window -> UI Element +topNavigation window = do + info <- mkAboutInfo + infoIcon <- image "has-tooltip-multiline has-tooltip-bottom rt-view-info-icon mr-3" rtViewInfoSVG + ## "info-icon" + # set dataTooltip "RTView info" + on UI.click infoIcon . const $ element info #. "modal is-active" + + notificationsEvents <- mkNotificationsEvents + notificationsSettings <- mkNotificationsSettings + + notificationsEventsItem <- UI.anchor #. "navbar-item" #+ + [ image "rt-view-notify-menu-icon" eventsSVG + , string "Events" + ] + notificationsSettingsItem <- UI.anchor #. "navbar-item" #+ + [ image "rt-view-notify-menu-icon" settingsSVG + , string "Settings" + ] + on UI.click notificationsEventsItem . const $ element notificationsEvents #. "modal is-active" + on UI.click notificationsSettingsItem . const $ element notificationsSettings #. "modal is-active" + + _notificationsIcon <- image "rt-view-info-icon mr-2" rtViewNotifySVG + ## "notifications-icon" + + themeIcon <- image "has-tooltip-multiline has-tooltip-bottom rt-view-theme-icon" rtViewThemeToLightSVG + ## "theme-icon" + # set dataTooltip "Switch to light theme" + on UI.click themeIcon . const $ switchTheme window + + UI.div ## "top-bar" #. "navbar rt-view-top-bar" #+ + [ element info + --, element notificationsEvents + --, element notificationsSettings + , UI.div #. "navbar-brand" #+ + [ UI.div #. "navbar-item" #+ + [ image "rt-view-cardano-logo" cardanoLogoSVG ## "cardano-logo" + , UI.span ## "name" #. "rt-view-name" # set text "Node Real-time View" + ] + ] + , UI.div #. "navbar-menu" #+ + [ UI.div #. "navbar-start" #+ [] + , UI.div #. "navbar-end" #+ + [ UI.div #. "navbar-item" #+ [element themeIcon] + , UI.div #. "navbar-item" #+ [element infoIcon] + --, UI.div #. "navbar-item has-dropdown is-hoverable" #+ + -- [ UI.anchor #. "navbar-link" #+ [element notificationsIcon] + -- , UI.div #. "navbar-dropdown is-right" #+ + -- [ element notificationsEventsItem + -- , element notificationsSettingsItem + -- ] + -- ] + ] + ] + ] + +footer :: UI Element +footer = + UI.mkElement "footer" #. "footer rt-view-footer" #+ + [ UI.div #. "columns" #+ + [ UI.div #. "column" #+ + [ string "© IOHK 2015—2022" + ] + , UI.div #. "column has-text-right" #+ + [ UI.anchor # set UI.href "https://github.com/input-output-hk/cardano-node/blob/master/cardano-tracer/README.md" + # set UI.target "_blank" #+ + [ image "has-tooltip-multiline has-tooltip-left rt-view-footer-github" githubSVG + # set dataTooltip "Browse our GitHub repository" + ] + ] + ] + ] + +mkChart + :: UI.Window + -> UI.Timer + -> ChartId + -> String + -> UI Element +mkChart window chartUpdateTimer chartId chartName = do + selectTimeRange <- + UI.select ## (show chartId <> show TimeRangeSelect) #+ + -- Values are ranges in seconds. + [ UI.option # set value "0" # set text "All time" + , UI.option # set value "300" # set text "Last 5 minutes" + , UI.option # set value "900" # set text "Last 15 minutes" + , UI.option # set value "1800" # set text "Last 30 minutes" + , UI.option # set value "3600" # set text "Last 1 hour" + , UI.option # set value "10800" # set text "Last 3 hours" + , UI.option # set value "21600" # set text "Last 6 hours" + ] + selectUpdatePeriod <- + UI.select ## (show chartId <> show UpdatePeriodSelect) #+ + -- Values are periods in seconds. + [ UI.option # set value "0" # set text "Off" + , UI.option # set value "15" # set text "15 seconds" + , UI.option # set value "30" # set text "30 seconds" + , UI.option # set value "60" # set text "1 minute" + , UI.option # set value "300" # set text "5 minutes" + , UI.option # set value "900" # set text "15 minutes" + , UI.option # set value "1800" # set text "30 minutes" + , UI.option # set value "3600" # set text "1 hour" + ] + + on UI.selectionChange selectTimeRange . const $ + whenJustM (readMaybe <$> get value selectTimeRange) $ \(rangeInSec :: Int) -> do + Chart.setTimeRange chartId rangeInSec + when (rangeInSec == 0) $ Chart.resetZoomChartJS chartId + saveChartsSettings window + + on UI.selectionChange selectUpdatePeriod . const $ + whenJustM (readMaybe <$> get value selectUpdatePeriod) $ \(periodInSec :: Int) -> do + whenM (get UI.running chartUpdateTimer) $ UI.stop chartUpdateTimer + unless (periodInSec == 0) $ do + void $ return chartUpdateTimer # set UI.interval (periodInSec * 1000) + UI.start chartUpdateTimer + saveChartsSettings window + + UI.div #. "rt-view-chart-container" #+ + [ UI.div #. "columns" #+ + [ UI.div #. "column mt-1" #+ + [ UI.span #. "rt-view-chart-name" # set text chartName + ] + , UI.div #. "column has-text-right" #+ + [ UI.div #. "field is-grouped mt-3" #+ + [ image "has-tooltip-multiline has-tooltip-top rt-view-chart-icon" timeRangeSVG + # set dataTooltip "Select time range" + , UI.div #. "select is-link is-small mr-4" #+ [element selectTimeRange] + , image "has-tooltip-multiline has-tooltip-top rt-view-chart-icon" refreshSVG + # set dataTooltip "Select update period" + , UI.div #. "select is-link is-small" #+ [element selectUpdatePeriod] + ] + ] + ] + , UI.canvas ## show chartId #. "rt-view-chart-area" #+ [] + ] + +shownState, hiddenState :: String +shownState = "shown" +hiddenState = "hidden" + +changeVisibilityForCharts + :: UI.Window + -> Element + -> Text + -> String + -> UI () +changeVisibilityForCharts window showHideIcon areaId areaName = do + state <- get dataState showHideIcon + let haveToHide = state == shownState + if haveToHide + then do + findAndHide window areaId + void $ element showHideIcon # set html hideSVG + # set dataState hiddenState + # set dataTooltip ("Click to show " <> areaName) + else do + findAndSet showFlex window areaId + void $ element showHideIcon # set html showSVG + # set dataState shownState + # set dataTooltip ("Click to hide " <> areaName) + +mkChartTimer, mkChartTimer' + :: ConnectedNodes + -> History + -> DatasetsIndices + -> DatasetsTimestamps + -> DataName + -> ChartId + -> UI UI.Timer +mkChartTimer = doMakeChartTimer addPointsToChart +mkChartTimer' = doMakeChartTimer addAllPointsToChart + +type PointsAdder = + ConnectedNodes + -> History + -> DatasetsIndices + -> DatasetsTimestamps + -> DataName + -> ChartId + -> UI () + +doMakeChartTimer + :: PointsAdder + -> ConnectedNodes + -> History + -> DatasetsIndices + -> DatasetsTimestamps + -> DataName + -> ChartId + -> UI UI.Timer +doMakeChartTimer addPoints connectedNodes history datasetIndices + datasetTimestamps dataName chartId = do + uiUpdateTimer <- UI.timer # set UI.interval defaultUpdatePeriodInMs + on UI.tick uiUpdateTimer . const $ + addPoints connectedNodes history datasetIndices datasetTimestamps dataName chartId + return uiUpdateTimer + where + defaultUpdatePeriodInMs = 15 * 1000 diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Main.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Main.hs new file mode 100644 index 00000000000..66b9edd857a --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Main.hs @@ -0,0 +1,166 @@ +{-# LANGUAGE OverloadedStrings #-} + +module Cardano.Tracer.Handlers.RTView.UI.HTML.Main + ( mkMainPage + ) where + +import Control.Concurrent.STM.TVar (readTVarIO) +import Control.Monad (void) +import Control.Monad.Extra (whenM) +import Data.List.NonEmpty (NonEmpty) +import qualified Graphics.UI.Threepenny as UI +import Graphics.UI.Threepenny.Core +import System.Time.Extra (sleep) + +import Cardano.Tracer.Configuration +import Cardano.Tracer.Handlers.RTView.State.EraSettings +import Cardano.Tracer.Handlers.RTView.State.Displayed +import Cardano.Tracer.Handlers.RTView.State.Errors +import Cardano.Tracer.Handlers.RTView.State.Historical +import Cardano.Tracer.Handlers.RTView.State.Peers +import Cardano.Tracer.Handlers.RTView.State.TraceObjects +import Cardano.Tracer.Handlers.RTView.UI.CSS.Bulma +import Cardano.Tracer.Handlers.RTView.UI.CSS.Own +import Cardano.Tracer.Handlers.RTView.UI.HTML.Body +import Cardano.Tracer.Handlers.RTView.UI.Img.Icons +import Cardano.Tracer.Handlers.RTView.UI.Charts +import Cardano.Tracer.Handlers.RTView.UI.Theme +import Cardano.Tracer.Handlers.RTView.UI.Utils +import Cardano.Tracer.Handlers.RTView.Update.EKG +import Cardano.Tracer.Handlers.RTView.Update.Errors +import Cardano.Tracer.Handlers.RTView.Update.KES +import Cardano.Tracer.Handlers.RTView.Update.Nodes +import Cardano.Tracer.Handlers.RTView.Update.NodeState +import Cardano.Tracer.Handlers.RTView.Update.Peers +import Cardano.Tracer.Handlers.RTView.Update.Reload +import Cardano.Tracer.Types + +mkMainPage + :: ConnectedNodes + -> DisplayedElements + -> AcceptedMetrics + -> SavedTraceObjects + -> ErasSettings + -> DataPointRequestors + -> PageReloadedFlag + -> NonEmpty LoggingParams + -> Network + -> ResourcesHistory + -> BlockchainHistory + -> TransactionsHistory + -> Errors + -> UI.Window + -> UI () +mkMainPage connectedNodes displayedElements acceptedMetrics savedTO + nodesEraSettings dpRequestors reloadFlag loggingConfig networkConfig + resourcesHistory chainHistory txHistory nodesErrors window = do + void $ return window # set UI.title pageTitle + void $ UI.getHead window #+ + [ UI.link # set UI.rel "icon" + # set UI.href ("data:image/svg+xml;base64," <> faviconSVGBase64) + , UI.meta # set UI.name "viewport" + # set UI.content "width=device-width, initial-scale=1" + -- CSS + , UI.mkElement "style" # set UI.html bulmaCSS + , UI.mkElement "style" # set UI.html bulmaTooltipCSS + , UI.mkElement "style" # set UI.html bulmaPageloaderCSS + , UI.mkElement "style" # set UI.html bulmaSwitchCSS + , UI.mkElement "style" # set UI.html bulmaDividerCSS + , UI.mkElement "style" # set UI.html ownCSS + ] + + colors <- initColors + datasetIndices <- initDatasetsIndices + datasetTimestamps <- initDatasetsTimestamps + peers <- liftIO initPeers + + pageBody <- + mkPageBody + window + networkConfig + connectedNodes + resourcesHistory + chainHistory + txHistory + datasetIndices + datasetTimestamps + + -- Prepare and run the timer, which will hide the page preloader. + preloaderTimer <- UI.timer # set UI.interval 10 + on UI.tick preloaderTimer . const $ do + liftIO $ sleep 0.8 + findAndSet (set UI.class_ "pageloader") window "preloader" + UI.stop preloaderTimer + UI.start preloaderTimer + + restoreTheme window + restoreChartsSettings + + uiErrorsTimer <- UI.timer # set UI.interval 3000 + on UI.tick uiErrorsTimer . const $ + updateNodesErrors window connectedNodes nodesErrors + + whenM (liftIO $ readTVarIO reloadFlag) $ do + updateUIAfterReload + window + connectedNodes + displayedElements + dpRequestors + loggingConfig + colors + datasetIndices + nodesErrors + uiErrorsTimer + liftIO $ pageWasNotReload reloadFlag + + -- Uptime is a real-time clock, so update it every second. + uiUptimeTimer <- UI.timer # set UI.interval 1000 + on UI.tick uiUptimeTimer . const $ + updateNodesUptime connectedNodes displayedElements + + uiEKGTimer <- UI.timer # set UI.interval 1000 + on UI.tick uiEKGTimer . const $ + updateEKGMetrics acceptedMetrics + + uiNodesTimer <- UI.timer # set UI.interval 1500 + on UI.tick uiNodesTimer . const $ + updateNodesUI + window + connectedNodes + displayedElements + acceptedMetrics + savedTO + nodesEraSettings + dpRequestors + loggingConfig + colors + datasetIndices + nodesErrors + uiErrorsTimer + + uiPeersTimer <- UI.timer # set UI.interval 3000 + on UI.tick uiPeersTimer . const $ do + updateNodesPeers window peers savedTO + updateKESInfo window acceptedMetrics nodesEraSettings displayedElements + + uiNodeStateTimer <- UI.timer # set UI.interval 5000 + on UI.tick uiNodeStateTimer . const $ + askNSetNodeState window connectedNodes dpRequestors displayedElements + + UI.start uiUptimeTimer + UI.start uiNodesTimer + UI.start uiNodeStateTimer + UI.start uiPeersTimer + UI.start uiErrorsTimer + UI.start uiEKGTimer + + on UI.disconnect window . const $ do + UI.stop uiNodesTimer + UI.stop uiUptimeTimer + UI.stop uiPeersTimer + UI.stop uiNodeStateTimer + UI.stop uiEKGTimer + UI.stop uiErrorsTimer + liftIO $ pageWasReload reloadFlag + + void $ UI.element pageBody diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/NoNodes.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/NoNodes.hs new file mode 100644 index 00000000000..ab3ebd20b56 --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/NoNodes.hs @@ -0,0 +1,136 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE QuasiQuotes #-} + +module Cardano.Tracer.Handlers.RTView.UI.HTML.NoNodes + ( mkNoNodesInfo + ) where + +import Data.List (intercalate) +import qualified Data.List.NonEmpty as NE +import Data.String.QQ +import qualified Graphics.UI.Threepenny as UI +import Graphics.UI.Threepenny.Core + +import Cardano.Tracer.Configuration +import Cardano.Tracer.Handlers.RTView.UI.Img.Icons +import Cardano.Tracer.Handlers.RTView.UI.Utils + +-- | If the user doesn't see connected nodes - possible reason of it is +-- misconfiguration of 'cardano-tracer' and/or 'cardano-node'. +-- So we have to show basic explanation, which is based on current +-- configuration of 'cardano-tracer'. +mkNoNodesInfo :: Network -> UI Element +mkNoNodesInfo networkConfig = do + closeIt <- UI.button #. "delete" # set (UI.attr "aria-label") "delete" + infoNote <- + UI.mkElement "article" ## "no-nodes-info" + #. "container message is-link rt-view-no-nodes-info" #+ + [ UI.div #. "message-header" #+ + [ UI.p # set text "«Hey, where are my nodes?»" + , element closeIt + ] + , UI.div #. "message-body" #+ + [ UI.p #+ + [ UI.span # set UI.html pleaseWait + ] + , UI.p #. "mt-5" #+ + [ string intro + ] + , UI.p #. "mt-5" #+ + [ UI.span # set UI.html cardanoTracerNote + ] + , UI.p #. "mt-5" #+ + [ UI.span # set UI.html cardanoNodeNote + ] + , UI.p #. "mt-5" #+ + [ UI.span # set UI.html nodeNameNote + ] + , UI.p #. "mt-5" #+ + [ UI.span # set UI.html sshNote + ] + , UI.p #. "mt-5" #+ + [ string "For more details, please read " + , UI.anchor # set UI.href "https://github.com/input-output-hk/cardano-node/blob/master/cardano-tracer/docs/cardano-tracer.md#configuration" + # set text "our documentation" + # set UI.target "_blank" + , image "rt-view-href-icon" externalLinkSVG + , string "." + ] + ] + ] + on UI.click closeIt . const $ element infoNote # hideIt + return infoNote + where + pleaseWait = + "If your nodes and cardano-tracer are configured properly, " + <> "the connection between them will be established automatically, " + <> "but it can take some time." + + intro = + "However, if there is no connection after 1 minute, please check your configuration files." + + cardanoTracerNote = + case networkConfig of + AcceptAt (LocalSocket p) -> + "Currently, your cardano-tracer is configured as a server, " + <> "so it accepts connections from your nodes via the local socket " + <> p <> "." + ConnectTo addrs -> + let manySocks = NE.length addrs > 1 in + "Currently, your cardano-tracer is configured as a client, " + <> "so it connects to your " + <> (if manySocks then "nodes" else "node") + <> " via the local " + <> (if manySocks + then + let socks = map (\(LocalSocket p) -> "" <> p <> "") $ NE.toList addrs + in "sockets " <> intercalate ", " socks <> "." + else + "socket " <> let LocalSocket p = NE.head addrs in p <> ".") + + cardanoNodeNote = + case networkConfig of + AcceptAt (LocalSocket _p) -> + "Correspondingly, your nodes should be configured as clients. Make sure their configuration " + <> "files contain TraceOptionForwarder section like this:" + <> "
" <> traceOptionForwarderInit <> "
" + <> "where PATH_TO_SOCK is a path to a local socket." + ConnectTo _addrs -> + "Correspondingly, your nodes should be configured as servers. Make sure their configuration " + <> "files contain TraceOptionForwarder section like this:" + <> "
" <> traceOptionForwarderResp <> "
" + <> "where PATH_TO_SOCK is a path to a local socket." + + nodeNameNote = + "Also, please add a meaningful name for your nodes using TraceOptionNodeName field. " + <> "For example:
" <> traceOptionNodeName <> "
" + + sshNote = + "If your cardano-tracer and your nodes are running on different machines, the only " + <> "way to connect them is SSH tunneling with your credentials." + +traceOptionForwarderInit :: String +traceOptionForwarderInit = [s| +"TraceOptionForwarder": { + "address": { + "filePath": "PATH_TO_SOCK" + }, + "mode": "Initiator" +} +|] + +traceOptionForwarderResp :: String +traceOptionForwarderResp = [s| +"TraceOptionForwarder": { + "address": { + "filePath": "PATH_TO_SOCK" + }, + "mode": "Responder" +} +|] + +traceOptionNodeName :: String +traceOptionNodeName = [s| +"TraceOptionNodeName": "stk-a-1-IOG1" +|] diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Node/Column.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Node/Column.hs new file mode 100644 index 00000000000..3d8da0c2b05 --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Node/Column.hs @@ -0,0 +1,243 @@ +{-# LANGUAGE OverloadedStrings #-} + +module Cardano.Tracer.Handlers.RTView.UI.HTML.Node.Column + ( addNodeColumn + , deleteNodeColumn + ) where + +import Control.Monad (forM, void) +import Control.Monad.Extra (whenJustM) +import Data.List.NonEmpty (NonEmpty) +import qualified Data.List.NonEmpty as NE +import Data.Text (unpack) +import qualified Graphics.UI.Threepenny as UI +import Graphics.UI.Threepenny.Core +import System.FilePath (()) + +import Cardano.Tracer.Configuration +import Cardano.Tracer.Handlers.RTView.State.Errors +import Cardano.Tracer.Handlers.RTView.UI.HTML.Node.EKG +import Cardano.Tracer.Handlers.RTView.UI.HTML.Node.Errors +import Cardano.Tracer.Handlers.RTView.UI.HTML.Node.Peers +import Cardano.Tracer.Handlers.RTView.UI.JS.Utils +import Cardano.Tracer.Handlers.RTView.UI.Img.Icons +import Cardano.Tracer.Handlers.RTView.UI.Utils +import Cardano.Tracer.Types + +-- | For every connected node the new column should be added. +addNodeColumn + :: UI.Window + -> NonEmpty LoggingParams + -> Errors + -> UI.Timer + -> NodeId + -> UI () +addNodeColumn window loggingConfig nodesErrors updateErrorsTimer nodeId@(NodeId anId) = do + let id' = unpack anId + ls <- logsSettings loggingConfig id' + + peersTable <- mkPeersTable id' + peersDetailsButton <- UI.button ## (id' <> "__node-peers-details-button") + #. "button is-info" + # set UI.enabled False + # set text "Details" + on UI.click peersDetailsButton . const $ element peersTable #. "modal is-active" + + errorsTable <- mkErrorsTable window nodeId nodesErrors updateErrorsTimer + errorsDetailsButton <- UI.button ## (id' <> "__node-errors-details-button") + #. "button is-danger" + # set UI.enabled False + # set text "Details" + on UI.click errorsDetailsButton . const $ element errorsTable #. "modal is-active" + + ekgMetricsWindow <- mkEKGMetricsWindow id' + ekgMetricsButton <- UI.button ## (id' <> "__node-ekg-metrics-button") + #. "button is-info" + # set text "Details" + on UI.click ekgMetricsButton . const $ element ekgMetricsWindow #. "modal is-active" + + addNodeCellH "name" [ image "rt-view-node-chart-label has-tooltip-multiline has-tooltip-left" rectangleSVG + ## (id' <> "__node-chart-label") + # set dataTooltip "Label using for this node on charts" + , UI.span ## (id' <> "__node-name") + #. "has-text-weight-bold is-size-4 rt-view-node-name" + # set text "Node" + ] + addNodeCell "version" [ UI.span ## (id' <> "__node-version") + # set text "—" + ] + addNodeCell "commit" [ UI.anchor ## (id' <> "__node-commit") + #. ("rt-view-href is-family-monospace has-text-weight-normal" + <> " has-tooltip-multiline has-tooltip-right") + # set UI.href "#" + # set UI.target "_blank" + # set dataTooltip "Browse cardano-node repository on this commit" + # set text "—" + , image "rt-view-href-icon" externalLinkSVG + ] + addNodeCell "protocol" [ UI.span ## (id' <> "__node-protocol") + # set text "—" + ] + addNodeCell "era" [ UI.span ## (id' <> "__node-era") + # set text "—" + ] + addNodeCell "epoch" [ string "#" + , UI.span ## (id' <> "__node-epoch-num") # set text "—" + , image "has-tooltip-multiline has-tooltip-top rt-view-epoch-end" endSVG + # set dataTooltip "End date of this epoch" + , UI.span ## (id' <> "__node-epoch-end") # set text "—" + ] + addNodeCell "sync" [ UI.span ## (id' <> "__node-sync-progress") + # set text "—" + ] + addNodeCell "system-start-time" [ UI.span ## (id' <> "__node-system-start-time") + # set text "—" + ] + addNodeCell "start-time" [ UI.span ## (id' <> "__node-start-time") + # set text "—" + ] + addNodeCell "uptime" [ UI.span ## (id' <> "__node-uptime") + # set text "—" + ] + addNodeCell "logs" [ UI.span ## (id' <> "__node-logs") + #+ ls + ] + addNodeCell "block-replay" [ UI.span ## (id' <> "__node-block-replay") + # set html "0 %" + ] + addNodeCell "chunk-validation" [ UI.span ## (id' <> "__node-chunk-validation") + # set text "—" + ] + addNodeCell "update-ledger-db" [ UI.span ## (id' <> "__node-update-ledger-db") + # set html "0 %" + ] + addNodeCell "peers" [ UI.div #. "buttons has-addons" #+ + [ UI.button ## (id' <> "__node-peers-num") + #. "button is-static" + # set text "—" + , element peersDetailsButton + ] + , element peersTable + ] + addNodeCell "errors" [ UI.div #. "buttons has-addons" #+ + [ UI.button ## (id' <> "__node-errors-num") + #. "button is-static" + # set text "0" + , element errorsDetailsButton + ] + , element errorsTable + ] + addNodeCell "leadership" [ UI.span ## (id' <> "__node-leadership") + # set text "—" + ] + addNodeCell "forged-blocks" [ UI.span ## (id' <> "__node-forged-blocks") + # set text "—" + ] + addNodeCell "cannot-forge" [ UI.span ## (id' <> "__node-cannot-forge") + # set text "—" + ] + addNodeCell "missed-slots" [ UI.span ## (id' <> "__node-missed-slots") + # set text "—" + ] + addNodeCell "current-kes-period" [ UI.span ## (id' <> "__node-current-kes-period") + # set text "—" + ] + addNodeCell "op-cert-expiry-kes-period" [ UI.span ## (id' <> "__node-op-cert-expiry-kes-period") + # set text "—" + ] + addNodeCell "remaining-kes-periods" [ UI.span ## (id' <> "__node-remaining-kes-periods") + # set text "—" + ] + addNodeCell "op-cert-start-kes-period" [ UI.span ## (id' <> "__node-op-cert-start-kes-period") + # set text "—" + ] + addNodeCell "days-until-op-cert-renew" [ UI.span ## (id' <> "__node-days-until-op-cert-renew") + # set text "—" + ] + addNodeCell "ekg-metrics" [ UI.div #. "buttons has-addons" #+ + [ UI.button ## (id' <> "__node-ekg-metrics-num") + #. "button is-static" + # set text "—" + , element ekgMetricsButton + ] + , element ekgMetricsWindow + ] + where + addNodeCellH rowId cellContent = + whenJustM (UI.getElementById window ("node-" <> rowId <> "-row")) $ \el -> + void $ element el #+ [ UI.th #. (unpack anId <> "__column_cell") + #+ cellContent + ] + addNodeCell rowId cellContent = + whenJustM (UI.getElementById window ("node-" <> rowId <> "-row")) $ \el -> + void $ element el #+ [ UI.td #. (unpack anId <> "__column_cell") + #+ cellContent + ] + +-- | The new node is already connected, so we can display its logging settings. +logsSettings + :: NonEmpty LoggingParams + -> String + -> UI [UI Element] +logsSettings loggingConfig anId = + forM (NE.toList loggingConfig) $ \(LoggingParams root mode format) -> + case mode of + FileMode -> do + let pathToSubdir = root anId + + copyPath <- UI.button #. "button is-info" + #+ [image "rt-view-copy-icon" copySVG] + on UI.click copyPath . const $ + copyTextToClipboard pathToSubdir + + return $ + UI.p #+ + [ UI.div #. "field has-addons" #+ + [ UI.p #. "control" #+ + [ UI.button #. "button is-static" + # set text (if format == ForHuman then "LOG" else "JSON") + ] + , UI.p #. "control" #+ + [ UI.input #. "input rt-view-logs-input" + # set UI.type_ "text" + # set (UI.attr "readonly") "readonly" + # set UI.value pathToSubdir + ] + , UI.p #. "control" #+ + [ element copyPath + ] + ] + ] + JournalMode -> do + copyId <- UI.button #. "button is-info" + #+ [image "rt-view-copy-icon" copySVG] + on UI.click copyId . const $ + copyTextToClipboard anId + + return $ + UI.p #+ + [ UI.div #. "field has-addons" #+ + [ UI.p #. "control" #+ + [ UI.button #. "button is-static" + # set text "JRNL" + ] + , UI.p #. "control" #+ + [ UI.input #. "input rt-view-logs-input" + # set UI.type_ "text" + # set (UI.attr "readonly") "readonly" + # set UI.value anId + ] + , UI.p #. "control" #+ + [ element copyId + ] + ] + ] + +-- | The node was disconnected, so its column should be deleted. +deleteNodeColumn + :: UI.Window + -> NodeId + -> UI () +deleteNodeColumn window (NodeId anId) = do + let className = anId <> "__column_cell" + findByClassAndDo window className delete' diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Node/EKG.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Node/EKG.hs new file mode 100644 index 00000000000..dbf44952ffe --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Node/EKG.hs @@ -0,0 +1,48 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} + +module Cardano.Tracer.Handlers.RTView.UI.HTML.Node.EKG + ( mkEKGMetricsWindow + ) where + +import qualified Graphics.UI.Threepenny as UI +import Graphics.UI.Threepenny.Core + +import Cardano.Tracer.Handlers.RTView.UI.Utils + +mkEKGMetricsWindow :: String -> UI Element +mkEKGMetricsWindow anId = do + closeIt <- UI.button #. "delete" + metricsWindow <- + UI.div #. "modal" #+ + [ UI.div #. "modal-background" #+ [] + , UI.div #. "modal-card rt-view-ekg-metrics-modal" #+ + [ UI.header #. "modal-card-head rt-view-ekg-metrics-head" #+ + [ UI.p #. "modal-card-title rt-view-ekg-metrics-title" #+ + [ string "EKG metrics from " + , UI.span ## (anId <> "__node-name-for-ekg-metrics") + #. "has-text-weight-bold" + # set text anId + ] + , element closeIt + ] + , UI.mkElement "section" #. "modal-card-body rt-view-ekg-metrics-body" #+ + [ UI.div ## "ekg-metrics-columns" #. "columns" #+ + [ UI.div #. "column" #+ + [ UI.span #. "is-size-4 has-text-weight-bold" # set text "Metric name" + , UI.p #. "mt-3" #+ + [ UI.span ## (anId <> "__node-ekg-metrics-names") #. "is-family-monospace" # set text "—" + ] + ] + , UI.div #. "column has-text-right" #+ + [ UI.span #. "is-size-4 has-text-weight-bold" # set text "Metric value" + , UI.p #. "mt-3" #+ + [ UI.span ## (anId <> "__node-ekg-metrics-values") # set text "—" + ] + ] + ] + ] + ] + ] + on UI.click closeIt . const $ element metricsWindow #. "modal" + return metricsWindow diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Node/Errors.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Node/Errors.hs new file mode 100644 index 00000000000..9384d17bcf3 --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Node/Errors.hs @@ -0,0 +1,121 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} + +module Cardano.Tracer.Handlers.RTView.UI.HTML.Node.Errors + ( mkErrorsTable + ) where + +import Control.Monad (when) +import Control.Monad.Extra (unlessM) +import Data.Text (unpack) +import qualified Graphics.UI.Threepenny as UI +import Graphics.UI.Threepenny.Core + +import Cardano.Tracer.Handlers.RTView.State.Errors +import Cardano.Tracer.Handlers.RTView.UI.Img.Icons +import Cardano.Tracer.Handlers.RTView.UI.Utils +import Cardano.Tracer.Handlers.RTView.Update.Errors +import Cardano.Tracer.Types + +mkErrorsTable + :: UI.Window + -> NodeId + -> Errors + -> UI.Timer + -> UI Element +mkErrorsTable window nodeId@(NodeId anId) nodesErrors updateErrorsTimer = do + let id' = unpack anId + closeIt <- UI.button #. "delete" + deleteAll <- image "has-tooltip-multiline has-tooltip-left rt-view-delete-errors-icon" trashSVG + # set dataTooltip "Click to delete all errors. This action cannot be undone!" + on UI.click deleteAll . const $ + deleteAllErrorMessages window nodeId nodesErrors + + searchMessagesInput <- UI.input #. "input rt-view-search-messages" + # set UI.type_ "text" + # set (UI.attr "placeholder") "Search messages" + searchMessages <- UI.button #. "button is-info" + #+ [image "rt-view-search-errors-icon" searchSVG] + + -- If the user clicked the search button. + on UI.click searchMessages . const $ + searchErrorMessages window searchMessagesInput nodeId nodesErrors updateErrorsTimer + -- If the user hits Enter key. + on UI.keyup searchMessagesInput $ \keyCode -> + when (keyCode == 13) $ + searchErrorMessages window searchMessagesInput nodeId nodesErrors updateErrorsTimer + -- If the user changed text in input... + on UI.valueChange searchMessagesInput $ \inputText -> + when (null inputText) $ + -- ... and this text is empty, it means that search/filter mode is off, + -- so remove "search result" and start the timer for update errors again. + unlessM (get UI.running updateErrorsTimer) $ + -- Ok, the timer is stopped, so we are in search/filter mode already, exit it. + exitSearchMode window nodeId updateErrorsTimer + + sortByTimeIcon <- image "has-tooltip-multiline has-tooltip-right rt-view-sort-icon" sortSVG + # set dataTooltip "Click to sort errors by time" + # set dataState "asc" + on UI.click sortByTimeIcon . const $ + sortErrorsByTime window nodeId sortByTimeIcon nodesErrors + + sortBySeverityIcon <- image "has-tooltip-multiline has-tooltip-right rt-view-sort-icon" sortSVG + # set dataTooltip "Click to sort errors by severity" + # set dataState "asc" + on UI.click sortBySeverityIcon . const $ + sortErrorsBySeverity window nodeId sortBySeverityIcon nodesErrors + + errorsTable <- + UI.div #. "modal" #+ + [ UI.div #. "modal-background" #+ [] + , UI.div #. "modal-card rt-view-errors-modal" #+ + [ UI.header #. "modal-card-head rt-view-errors-head" #+ + [ UI.p #. "modal-card-title rt-view-errors-title" #+ + [ string "Errors from " + , UI.span ## (id' <> "__node-name-for-errors") + #. "has-text-weight-bold" + # set text id' + ] + , element closeIt + ] + , UI.mkElement "section" #. "modal-card-body rt-view-errors-body" #+ + [ UI.div ## (id' <> "__errors-table-container") #. "table-container" #+ + [ UI.table ## (id' <> "__errors-table") #. "table is-fullwidth rt-view-errors-table" #+ + [ UI.mkElement "thead" #+ + [ UI.tr #+ + [ UI.th #. "rt-view-errors-timestamp" #+ + [ string "Timestamp" + , element sortByTimeIcon + ] + , UI.th #. "rt-view-errors-severity" #+ + [ string "Severity" + , element sortBySeverityIcon + ] + , UI.th #+ + [ string "Message" + ] + , UI.th #+ + [ element deleteAll + ] + ] + ] + , UI.mkElement "tbody" ## (id' <> "__node-errors-tbody") + # set dataState "0" + #+ [] + ] + ] + ] + , UI.mkElement "footer" #. "modal-card-foot rt-view-errors-foot" #+ + [ UI.div #. "field has-addons" #+ + [ UI.p #. "control" #+ + [ element searchMessagesInput + ] + , UI.p #. "control" #+ + [ element searchMessages + ] + ] + ] + ] + ] + on UI.click closeIt . const $ element errorsTable #. "modal" + return errorsTable diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Node/Peers.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Node/Peers.hs new file mode 100644 index 00000000000..cde6de695b9 --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Node/Peers.hs @@ -0,0 +1,62 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} + +module Cardano.Tracer.Handlers.RTView.UI.HTML.Node.Peers + ( mkPeersTable + , deletePeerRow + ) where + +import qualified Graphics.UI.Threepenny as UI +import Graphics.UI.Threepenny.Core + +import Cardano.Tracer.Handlers.RTView.State.Peers +import Cardano.Tracer.Handlers.RTView.UI.Utils +import Cardano.Tracer.Types + +mkPeersTable :: String -> UI Element +mkPeersTable anId = do + closeIt <- UI.button #. "delete" + peerTable <- + UI.div #. "modal" #+ + [ UI.div #. "modal-background" #+ [] + , UI.div #. "modal-card rt-view-peer-modal" #+ + [ UI.header #. "modal-card-head rt-view-peer-head" #+ + [ UI.p #. "modal-card-title rt-view-peer-title" #+ + [ string "Peers of " + , UI.span ## (anId <> "__node-name-for-peers") + #. "has-text-weight-bold" + # set text anId + ] + , element closeIt + ] + , UI.mkElement "section" #. "modal-card-body rt-view-peer-body" #+ + [ UI.div ## (anId <> "__peer-table-container") #. "table-container" #+ + [ UI.table ## (anId <> "__peer-table") #. "table is-fullwidth rt-view-peer-table" #+ + [ UI.mkElement "thead" #+ + [ UI.tr #+ + [ UI.th #+ [UI.span # set text "Address"] + , UI.th #+ [UI.span # set text "Status"] + , UI.th #+ [UI.span # set text "Slot no."] + , UI.th #+ [UI.mkElement "abbr" # set UI.title__ "Requests in flight" # set text "Req"] + , UI.th #+ [UI.mkElement "abbr" # set UI.title__ "Blocks in flight" # set text "Blk"] + , UI.th #+ [UI.mkElement "abbr" # set UI.title__ "Bytes in flight" # set text "Bts"] + ] + ] + , UI.mkElement "tbody" ## (anId <> "__node-peers-tbody") #+ [] + ] + ] + ] + ] + ] + on UI.click closeIt . const $ element peerTable #. "modal" + return peerTable + +-- | The peer was disconnected, so its row should be deleted. +deletePeerRow + :: UI.Window + -> NodeId + -> PeerAddress + -> UI () +deletePeerRow window (NodeId anId) peerAddr = do + let peerRowElId = anId <> peerAddr <> "__node-peer-row" + findAndDo window peerRowElId delete' diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Notifications.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Notifications.hs new file mode 100644 index 00000000000..664e86787ab --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/HTML/Notifications.hs @@ -0,0 +1,62 @@ +module Cardano.Tracer.Handlers.RTView.UI.HTML.Notifications + ( mkNotificationsEvents + , mkNotificationsSettings + ) where + +--import qualified Data.Text as T +import qualified Graphics.UI.Threepenny as UI +import Graphics.UI.Threepenny.Core + +--import Cardano.Tracer.Handlers.RTView.UI.JS.Utils +--import Cardano.Tracer.Handlers.RTView.UI.Img.Icons +import Cardano.Tracer.Handlers.RTView.UI.Utils + +mkNotificationsEvents :: UI Element +mkNotificationsEvents = do + closeIt <- UI.button #. "delete" + notifications <- + UI.div #. "modal" #+ + [ UI.div #. "modal-background" #+ [] + , UI.div #. "modal-card" #+ + [ UI.header #. "modal-card-head rt-view-notifications-head" #+ + [ UI.p #. "modal-card-title rt-view-notifications-title" # set text "Notifications: events" + , element closeIt + ] + , UI.mkElement "section" #. "modal-card-body rt-view-notifications-body" #+ + [ UI.div #. "field" #+ + [ UI.input ## "switchRoundedInfo" + #. "switch is-rounded is-info" + # set UI.type_ "checkbox" + # set UI.name "switchRoundedInfo" + , UI.label # set UI.for "switchRoundedInfo" + # set text "Switch info" + ] + ] + ] + ] + on UI.click closeIt . const $ element notifications #. "modal" + return notifications + +mkNotificationsSettings :: UI Element +mkNotificationsSettings = do + closeIt <- UI.button #. "delete" + notifications <- + UI.div #. "modal" #+ + [ UI.div #. "modal-background" #+ [] + , UI.div #. "modal-card" #+ + [ UI.header #. "modal-card-head rt-view-notifications-head" #+ + [ UI.p #. "modal-card-title rt-view-notifications-title" # set text "Notifications: settings" + , element closeIt + ] + , UI.mkElement "section" #. "modal-card-body rt-view-notifications-body" #+ + [ string "SETTINGS" + ] + ] + ] + on UI.click closeIt . const $ element notifications #. "modal" + return notifications + + + + + diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/Img/Icons.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/Img/Icons.hs new file mode 100644 index 00000000000..4f626f8b81e --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/Img/Icons.hs @@ -0,0 +1,354 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Cardano.Tracer.Handlers.RTView.UI.Img.Icons + ( blocksSVG + , calendarSVG + , cardanoLogoSVG + , chainSVG + , commitSVG + , connectedSVG + , dbSVG + , downSVG + , errorsSVG + , faviconSVGBase64 + , healthSVG + , kesSVG + , linuxSVG + , noNodesSVG + , overviewSVG + , peersSVG + , peersNumSVG + , platformSVG + , configSVG + , protocolSVG + , remainingSVG + , rtViewInfoSVG + , rtViewNotifySVG + , rtViewThemeToLightSVG + , rtViewThemeToDarkSVG + , serverSVG + , whatSVG + , versionSVG + , startSVG + , systemStartSVG + , uptimeSVG + , logsSVG + , directorySVG + , copySVG + , externalLinkSVG + , externalLinkWhiteSVG + , rectangleSVG + , showSVG + , hideSVG + , githubSVG + , refreshSVG + , timeRangeSVG + , leaderSVG + , forgeSVG + , notForgeSVG + , missedSVG + , eraSVG + , lengthSVG + , endSVG + , ekgMetricsSVG + , certificateSVG + , csvSVG + , deleteSVG + , trashSVG + , sortSVG + , searchSVG + , eventsSVG + , settingsSVG + ) where + +import Data.String.QQ + +cardanoLogoSVG :: String +cardanoLogoSVG = [s| + +|] + +rtViewInfoSVG :: String +rtViewInfoSVG = [s| + +|] + +rtViewNotifySVG :: String +rtViewNotifySVG = [s| + +|] + +rtViewThemeToLightSVG :: String +rtViewThemeToLightSVG = [s| + +|] + +rtViewThemeToDarkSVG :: String +rtViewThemeToDarkSVG = [s| + +|] + +whatSVG :: String +whatSVG = [s| + +|] + +dbSVG :: String +dbSVG = [s| + +|] + +downSVG :: String +downSVG = [s| + +|] + +protocolSVG :: String +protocolSVG = [s| + +|] + +versionSVG :: String +versionSVG = [s| + +|] + +commitSVG :: String +commitSVG = [s| + +|] + +linuxSVG :: String +linuxSVG = [s| + +|] + +calendarSVG :: String +calendarSVG = [s| + +|] + +healthSVG :: String +healthSVG = [s| + +|] + +overviewSVG :: String +overviewSVG = [s| + +|] + +kesSVG :: String +kesSVG = [s| + +|] + +peersSVG :: String +peersSVG = [s| + +|] + +blocksSVG :: String +blocksSVG = [s| + +|] + +errorsSVG :: String +errorsSVG = [s| + +|] + +noNodesSVG :: String +noNodesSVG = [s| + +|] + +connectedSVG :: String +connectedSVG = [s| + +|] + +remainingSVG :: String +remainingSVG = [s| + +|] + +peersNumSVG :: String +peersNumSVG = [s| + +|] + +chainSVG :: String +chainSVG = [s| + +|] + +faviconSVGBase64 :: String +faviconSVGBase64 = [s| +PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAzNzUgMzQ2LjUxIj48ZyBpZD0iTGF5ZXJfMiIgZGF0YS1uYW1lPSJMYXllciAyIj48ZyBpZD0iTGF5ZXJfMS0yIiBkYXRhLW5hbWU9IkxheWVyIDEiPjxwYXRoIGQ9Ik0xMDIuNzYsMTcyYTI1LjMxLDI1LjMxLDAsMCwwLDIzLjc4LDI2LjY1Yy40OSwwLDEsMCwxLjQ2LDBBMjUuMjYsMjUuMjYsMCwxLDAsMTAyLjc2LDE3MloiIGZpbGw9IiMwMDMzYWQiLz48cGF0aCBkPSJNOC42MiwxNjUuNWE4LjE2LDguMTYsMCwxLDAsNy42OSw4LjYxQTguMTUsOC4xNSwwLDAsMCw4LjYyLDE2NS41WiIgZmlsbD0iIzAwMzNhZCIvPjxwYXRoIGQ9Ik0xMDEuMTYsMjUuNDNhOC4xNiw4LjE2LDAsMSwwLTExLTMuNjJBOC4xOCw4LjE4LDAsMCwwLDEwMS4xNiwyNS40M1oiIGZpbGw9IiMwMDMzYWQiLz48cGF0aCBkPSJNMTI2Ljc4LDcwLjFhMTIuNjEsMTIuNjEsMCwxLDAtMTYuOTQtNS41OUExMi42MiwxMi42MiwwLDAsMCwxMjYuNzgsNzAuMVoiIGZpbGw9IiMwMDMzYWQiLz48cGF0aCBkPSJNNDAuNTgsMTAwLjgyYTEwLjM5LDEwLjM5LDAsMSwwLTMtMTQuMzhBMTAuMzksMTAuMzksMCwwLDAsNDAuNTgsMTAwLjgyWiIgZmlsbD0iIzAwMzNhZCIvPjxwYXRoIGQ9Ik01NS45MywxNjFhMTIuNjIsMTIuNjIsMCwxLDAsMTEuODgsMTMuMzFBMTIuNjIsMTIuNjIsMCwwLDAsNTUuOTMsMTYxWiIgZmlsbD0iIzAwMzNhZCIvPjxwYXRoIGQ9Ik00MiwyNDUuNzJhMTAuMzksMTAuMzksMCwxLDAsMTMuOTUsNC42QTEwLjM3LDEwLjM3LDAsMCwwLDQyLDI0NS43MloiIGZpbGw9IiMwMDMzYWQiLz48cGF0aCBkPSJNOTEsMTM0Ljg5YTE0Ljg0LDE0Ljg0LDAsMSwwLTQuMjctMjAuNTVBMTQuODMsMTQuODMsMCwwLDAsOTEsMTM0Ljg5WiIgZmlsbD0iIzAwMzNhZCIvPjxwYXRoIGQ9Ik0yNDYuNDcsNjkuMWExMi42MiwxMi42MiwwLDEsMC0zLjYzLTE3LjQ3QTEyLjYxLDEyLjYxLDAsMCwwLDI0Ni40Nyw2OS4xWiIgZmlsbD0iIzAwMzNhZCIvPjxwYXRoIGQ9Ik0yNzIuMzUsMjQuNTdBOC4xNiw4LjE2LDAsMSwwLDI3MCwxMy4yNiw4LjE2LDguMTYsMCwwLDAsMjcyLjM1LDI0LjU3WiIgZmlsbD0iIzAwMzNhZCIvPjxwYXRoIGQ9Ik0yNDguNDUsMTQ3LjkxYTI1LjI1LDI1LjI1LDAsMCwwLTIuODcsNTAuNDJjLjQ5LDAsMSwwLDEuNDUsMGEyNS4yNSwyNS4yNSwwLDAsMCwxLjQyLTUwLjQ2WiIgZmlsbD0iIzAwMzNhZCIvPjxwYXRoIGQ9Ik0xMzUuMDgsMTMzLjE0QTI1LjEyLDI1LjEyLDAsMCwwLDE1Ny42NCwxNDdhMjUuMjUsMjUuMjUsMCwwLDAsMjIuNTQtMzYuNjIsMjUuMjUsMjUuMjUsMCwxLDAtNDUuMSwyMi43M1oiIGZpbGw9IiMwMDMzYWQiLz48cGF0aCBkPSJNMzMzLDEwMC43OWExMC4zOSwxMC4zOSwwLDEsMC0xNC00LjZBMTAuNCwxMC40LDAsMCwwLDMzMywxMDAuNzlaIiBmaWxsPSIjMDAzM2FkIi8+PHBhdGggZD0iTTI2OSwxMDguODNhMTQuODQsMTQuODQsMCwxLDAsMTkuOTQsNi41OEExNC44NiwxNC44NiwwLDAsMCwyNjksMTA4LjgzWiIgZmlsbD0iIzAwMzNhZCIvPjxwYXRoIGQ9Ik0xODYuNTUsMjAuNzZhMTAuMzksMTAuMzksMCwxLDAtOS43OS0xMUExMC4zOCwxMC4zOCwwLDAsMCwxODYuNTUsMjAuNzZaIiBmaWxsPSIjMDAzM2FkIi8+PHBhdGggZD0iTTE4Ni40Myw4Ni4xM2ExNC44NCwxNC44NCwwLDEsMC0xNC0xNS42NkExNC44NCwxNC44NCwwLDAsMCwxODYuNDMsODYuMTNaIiBmaWxsPSIjMDAzM2FkIi8+PHBhdGggZD0iTTEwNiwyMzcuNjhhMTQuODQsMTQuODQsMCwxLDAtMTkuOTMtNi41OEExNC44NSwxNC44NSwwLDAsMCwxMDYsMjM3LjY4WiIgZmlsbD0iIzAwMzNhZCIvPjxwYXRoIGQ9Ik0xOTYsMTA3Ljc5YTI1LjIyLDI1LjIyLDAsMSwwLDIxLjE0LTExLjQxQTI1LjI4LDI1LjI4LDAsMCwwLDE5NiwxMDcuNzlaIiBmaWxsPSIjMDAzM2FkIi8+PHBhdGggZD0iTTIzOS45MiwyMTMuMzdhMjUuMjYsMjUuMjYsMCwxLDAtMTEuMTgsMzMuOTFBMjUuMTEsMjUuMTEsMCwwLDAsMjM5LjkyLDIxMy4zN1oiIGZpbGw9IiMwMDMzYWQiLz48cGF0aCBkPSJNMjg0LDIxMS42MmExNC44NCwxNC44NCwwLDEsMCw0LjI3LDIwLjU1QTE0Ljg0LDE0Ljg0LDAsMCwwLDI4NCwyMTEuNjJaIiBmaWxsPSIjMDAzM2FkIi8+PHBhdGggZD0iTTMzMi4zOCwxNzMuNjhhMTIuNjIsMTIuNjIsMCwxLDAtMTMuMzEsMTEuODhBMTIuNjIsMTIuNjIsMCwwLDAsMzMyLjM4LDE3My42OFoiIGZpbGw9IiMwMDMzYWQiLz48cGF0aCBkPSJNMzY3LjMsMTY0LjcxYTguMTYsOC4xNiwwLDEsMCw3LjY5LDguNjFBOC4xNyw4LjE3LDAsMCwwLDM2Ny4zLDE2NC43MVoiIGZpbGw9IiMwMDMzYWQiLz48cGF0aCBkPSJNMzM0LjQyLDI0NS42OGExMC4zOSwxMC4zOSwwLDEsMCwzLDE0LjM5QTEwLjM5LDEwLjM5LDAsMCwwLDMzNC40MiwyNDUuNjhaIiBmaWxsPSIjMDAzM2FkIi8+PHBhdGggZD0iTTEwMi42NSwzMjEuOTRhOC4xNiw4LjE2LDAsMSwwLDIuMzQsMTEuM0E4LjE3LDguMTcsMCwwLDAsMTAyLjY1LDMyMS45NFoiIGZpbGw9IiMwMDMzYWQiLz48cGF0aCBkPSJNMjczLjgzLDMyMS4wOGE4LjE2LDguMTYsMCwxLDAsMTEsMy42MkE4LjE2LDguMTYsMCwwLDAsMjczLjgzLDMyMS4wOFoiIGZpbGw9IiMwMDMzYWQiLz48cGF0aCBkPSJNMTc5LDIzOC43MWEyNS4yNSwyNS4yNSwwLDEsMC0yMS4xNCwxMS40MUEyNS4xLDI1LjEsMCwwLDAsMTc5LDIzOC43MVoiIGZpbGw9IiMwMDMzYWQiLz48cGF0aCBkPSJNMTI4LjUzLDI3Ny40MWExMi42MiwxMi42MiwwLDEsMCwzLjYzLDE3LjQ3QTEyLjYyLDEyLjYyLDAsMCwwLDEyOC41MywyNzcuNDFaIiBmaWxsPSIjMDAzM2FkIi8+PHBhdGggZD0iTTE4Ny4zOCwzMjUuNzRhMTAuMzksMTAuMzksMCwxLDAsOS43OCwxMUExMC4zOSwxMC4zOSwwLDAsMCwxODcuMzgsMzI1Ljc0WiIgZmlsbD0iIzAwMzNhZCIvPjxwYXRoIGQ9Ik0xODcuNDksMjYwLjM3YTE0Ljg0LDE0Ljg0LDAsMSwwLDE0LDE1LjY3QTE0Ljg1LDE0Ljg1LDAsMCwwLDE4Ny40OSwyNjAuMzdaIiBmaWxsPSIjMDAzM2FkIi8+PHBhdGggZD0iTTI0OC4yMSwyNzYuNGExMi42MiwxMi42MiwwLDEsMCwxNyw1LjU5QTEyLjYyLDEyLjYyLDAsMCwwLDI0OC4yMSwyNzYuNFoiIGZpbGw9IiMwMDMzYWQiLz48L2c+PC9nPjwvc3ZnPg== +|] + +platformSVG :: String +platformSVG = [s| + +|] + +configSVG :: String +configSVG = [s| + +|] + +startSVG :: String +startSVG = [s| + +|] + +systemStartSVG :: String +systemStartSVG = [s| + +|] + +uptimeSVG :: String +uptimeSVG = [s| + +|] + +logsSVG :: String +logsSVG = [s| + +|] + +directorySVG :: String +directorySVG = [s| + +|] + +copySVG :: String +copySVG = [s| + +|] + +serverSVG :: String +serverSVG = [s| + +|] + +externalLinkSVG :: String +externalLinkSVG = [s| + +|] + +externalLinkWhiteSVG :: String +externalLinkWhiteSVG = [s| + +|] + +showSVG :: String +showSVG = [s| + +|] + +hideSVG :: String +hideSVG = [s| + +|] + +githubSVG :: String +githubSVG = [s| + +|] + +refreshSVG :: String +refreshSVG = [s| + +|] + +timeRangeSVG :: String +timeRangeSVG = [s| + +|] + +leaderSVG :: String +leaderSVG = [s| + +|] + +forgeSVG :: String +forgeSVG = [s| + +|] + +notForgeSVG :: String +notForgeSVG = [s| + +|] + +missedSVG :: String +missedSVG = [s| + +|] + +eraSVG :: String +eraSVG = [s| + +|] + +lengthSVG :: String +lengthSVG = [s| + +|] + +endSVG :: String +endSVG = [s| + +|] + +ekgMetricsSVG :: String +ekgMetricsSVG = [s| + +|] + +certificateSVG :: String +certificateSVG = [s| + +|] + +csvSVG :: String +csvSVG = [s| + +|] + +deleteSVG :: String +deleteSVG = [s| + +|] + +trashSVG :: String +trashSVG = [s| + +|] + +sortSVG :: String +sortSVG = [s| + +|] + +searchSVG :: String +searchSVG = [s| + +|] + +eventsSVG :: String +eventsSVG = [s| + +|] + +settingsSVG :: String +settingsSVG = [s| + +|] + +rectangleSVG :: String +rectangleSVG = [s| + +|] diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/JS/ChartJS.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/JS/ChartJS.hs new file mode 100644 index 00000000000..1b08cc49874 --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/JS/ChartJS.hs @@ -0,0 +1,55 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Cardano.Tracer.Handlers.RTView.UI.JS.ChartJS + ( chartJS + , chartJSLuxon + , chartJSAdapter + , chartJSPluginZoom + ) where + +import Data.String.QQ + +-- | To avoid run-time dependency from the static content, embed chart.js library in the page's header. +chartJS :: String +chartJS = [s| +/*! + * Chart.js v3.7.1 + * https://www.chartjs.org + * (c) 2022 Chart.js Contributors + * Released under the MIT License + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).Chart=e()}(this,(function(){"use strict";const t="undefined"==typeof window?function(t){return t()}:window.requestAnimationFrame;function e(e,i,s){const n=s||(t=>Array.prototype.slice.call(t));let o=!1,a=[];return function(...s){a=n(s),o||(o=!0,t.call(window,(()=>{o=!1,e.apply(i,a)})))}}function i(t,e){let i;return function(...s){return e?(clearTimeout(i),i=setTimeout(t,e,s)):t.apply(this,s),e}}const s=t=>"start"===t?"left":"end"===t?"right":"center",n=(t,e,i)=>"start"===t?e:"end"===t?i:(e+i)/2,o=(t,e,i,s)=>t===(s?"left":"right")?i:"center"===t?(e+i)/2:e;var a=new class{constructor(){this._request=null,this._charts=new Map,this._running=!1,this._lastDate=void 0}_notify(t,e,i,s){const n=e.listeners[s],o=e.duration;n.forEach((s=>s({chart:t,initial:e.initial,numSteps:o,currentStep:Math.min(i-e.start,o)})))}_refresh(){this._request||(this._running=!0,this._request=t.call(window,(()=>{this._update(),this._request=null,this._running&&this._refresh()})))}_update(t=Date.now()){let e=0;this._charts.forEach(((i,s)=>{if(!i.running||!i.items.length)return;const n=i.items;let o,a=n.length-1,r=!1;for(;a>=0;--a)o=n[a],o._active?(o._total>i.duration&&(i.duration=o._total),o.tick(t),r=!0):(n[a]=n[n.length-1],n.pop());r&&(s.draw(),this._notify(s,i,t,"progress")),n.length||(i.running=!1,this._notify(s,i,t,"complete"),i.initial=!1),e+=n.length})),this._lastDate=t,0===e&&(this._running=!1)}_getAnims(t){const e=this._charts;let i=e.get(t);return i||(i={running:!1,initial:!0,items:[],listeners:{complete:[],progress:[]}},e.set(t,i)),i}listen(t,e,i){this._getAnims(t).listeners[e].push(i)}add(t,e){e&&e.length&&this._getAnims(t).items.push(...e)}has(t){return this._getAnims(t).items.length>0}start(t){const e=this._charts.get(t);e&&(e.running=!0,e.start=Date.now(),e.duration=e.items.reduce(((t,e)=>Math.max(t,e._duration)),0),this._refresh())}running(t){if(!this._running)return!1;const e=this._charts.get(t);return!!(e&&e.running&&e.items.length)}stop(t){const e=this._charts.get(t);if(!e||!e.items.length)return;const i=e.items;let s=i.length-1;for(;s>=0;--s)i[s].cancel();e.items=[],this._notify(t,e,Date.now(),"complete")}remove(t){return this._charts.delete(t)}}; +/*! + * @kurkle/color v0.1.9 + * https://github.com/kurkle/color#readme + * (c) 2020 Jukka Kurkela + * Released under the MIT License + */const r={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15},l="0123456789ABCDEF",h=t=>l[15&t],c=t=>l[(240&t)>>4]+l[15&t],d=t=>(240&t)>>4==(15&t);function u(t){var e=function(t){return d(t.r)&&d(t.g)&&d(t.b)&&d(t.a)}(t)?h:c;return t?"#"+e(t.r)+e(t.g)+e(t.b)+(t.a<255?e(t.a):""):t}function f(t){return t+.5|0}const g=(t,e,i)=>Math.max(Math.min(t,i),e);function p(t){return g(f(2.55*t),0,255)}function m(t){return g(f(255*t),0,255)}function x(t){return g(f(t/2.55)/100,0,1)}function b(t){return g(f(100*t),0,100)}const _=/^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;const y=/^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;function v(t,e,i){const s=e*Math.min(i,1-i),n=(e,n=(e+t/30)%12)=>i-s*Math.max(Math.min(n-3,9-n,1),-1);return[n(0),n(8),n(4)]}function w(t,e,i){const s=(s,n=(s+t/60)%6)=>i-i*e*Math.max(Math.min(n,4-n,1),0);return[s(5),s(3),s(1)]}function M(t,e,i){const s=v(t,1,.5);let n;for(e+i>1&&(n=1/(e+i),e*=n,i*=n),n=0;n<3;n++)s[n]*=1-e-i,s[n]+=e;return s}function k(t){const e=t.r/255,i=t.g/255,s=t.b/255,n=Math.max(e,i,s),o=Math.min(e,i,s),a=(n+o)/2;let r,l,h;return n!==o&&(h=n-o,l=a>.5?h/(2-n-o):h/(n+o),r=n===e?(i-s)/h+(i>16&255,o>>8&255,255&o]}return t}(),T.transparent=[0,0,0,0]);const e=T[t.toLowerCase()];return e&&{r:e[0],g:e[1],b:e[2],a:4===e.length?e[3]:255}}function R(t,e,i){if(t){let s=k(t);s[e]=Math.max(0,Math.min(s[e]+s[e]*i,0===e?360:1)),s=P(s),t.r=s[0],t.g=s[1],t.b=s[2]}}function E(t,e){return t?Object.assign(e||{},t):t}function I(t){var e={r:0,g:0,b:0,a:255};return Array.isArray(t)?t.length>=3&&(e={r:t[0],g:t[1],b:t[2],a:255},t.length>3&&(e.a=m(t[3]))):(e=E(t,{r:0,g:0,b:0,a:1})).a=m(e.a),e}function z(t){return"r"===t.charAt(0)?function(t){const e=_.exec(t);let i,s,n,o=255;if(e){if(e[7]!==i){const t=+e[7];o=255&(e[8]?p(t):255*t)}return i=+e[1],s=+e[3],n=+e[5],i=255&(e[2]?p(i):i),s=255&(e[4]?p(s):s),n=255&(e[6]?p(n):n),{r:i,g:s,b:n,a:o}}}(t):C(t)}class F{constructor(t){if(t instanceof F)return t;const e=typeof t;let i;var s,n,o;"object"===e?i=I(t):"string"===e&&(o=(s=t).length,"#"===s[0]&&(4===o||5===o?n={r:255&17*r[s[1]],g:255&17*r[s[2]],b:255&17*r[s[3]],a:5===o?17*r[s[4]]:255}:7!==o&&9!==o||(n={r:r[s[1]]<<4|r[s[2]],g:r[s[3]]<<4|r[s[4]],b:r[s[5]]<<4|r[s[6]],a:9===o?r[s[7]]<<4|r[s[8]]:255})),i=n||L(t)||z(t)),this._rgb=i,this._valid=!!i}get valid(){return this._valid}get rgb(){var t=E(this._rgb);return t&&(t.a=x(t.a)),t}set rgb(t){this._rgb=I(t)}rgbString(){return this._valid?(t=this._rgb)&&(t.a<255?`rgba(${t.r}, ${t.g}, ${t.b}, ${x(t.a)})`:`rgb(${t.r}, ${t.g}, ${t.b})`):this._rgb;var t}hexString(){return this._valid?u(this._rgb):this._rgb}hslString(){return this._valid?function(t){if(!t)return;const e=k(t),i=e[0],s=b(e[1]),n=b(e[2]);return t.a<255?`hsla(${i}, ${s}%, ${n}%, ${x(t.a)})`:`hsl(${i}, ${s}%, ${n}%)`}(this._rgb):this._rgb}mix(t,e){const i=this;if(t){const s=i.rgb,n=t.rgb;let o;const a=e===o?.5:e,r=2*a-1,l=s.a-n.a,h=((r*l==-1?r:(r+l)/(1+r*l))+1)/2;o=1-h,s.r=255&h*s.r+o*n.r+.5,s.g=255&h*s.g+o*n.g+.5,s.b=255&h*s.b+o*n.b+.5,s.a=a*s.a+(1-a)*n.a,i.rgb=s}return i}clone(){return new F(this.rgb)}alpha(t){return this._rgb.a=m(t),this}clearer(t){return this._rgb.a*=1-t,this}greyscale(){const t=this._rgb,e=f(.3*t.r+.59*t.g+.11*t.b);return t.r=t.g=t.b=e,this}opaquer(t){return this._rgb.a*=1+t,this}negate(){const t=this._rgb;return t.r=255-t.r,t.g=255-t.g,t.b=255-t.b,this}lighten(t){return R(this._rgb,2,t),this}darken(t){return R(this._rgb,2,-t),this}saturate(t){return R(this._rgb,1,t),this}desaturate(t){return R(this._rgb,1,-t),this}rotate(t){return function(t,e){var i=k(t);i[0]=D(i[0]+e),i=P(i),t.r=i[0],t.g=i[1],t.b=i[2]}(this._rgb,t),this}}function B(t){return new F(t)}const V=t=>t instanceof CanvasGradient||t instanceof CanvasPattern;function W(t){return V(t)?t:B(t)}function N(t){return V(t)?t:B(t).saturate(.5).darken(.1).hexString()}function H(){}const j=function(){let t=0;return function(){return t++}}();function $(t){return null==t}function Y(t){if(Array.isArray&&Array.isArray(t))return!0;const e=Object.prototype.toString.call(t);return"[object"===e.substr(0,7)&&"Array]"===e.substr(-6)}function U(t){return null!==t&&"[object Object]"===Object.prototype.toString.call(t)}const X=t=>("number"==typeof t||t instanceof Number)&&isFinite(+t);function q(t,e){return X(t)?t:e}function K(t,e){return void 0===t?e:t}const G=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100:t/e,Z=(t,e)=>"string"==typeof t&&t.endsWith("%")?parseFloat(t)/100*e:+t;function J(t,e,i){if(t&&"function"==typeof t.call)return t.apply(i,e)}function Q(t,e,i,s){let n,o,a;if(Y(t))if(o=t.length,s)for(n=o-1;n>=0;n--)e.call(i,t[n],n);else for(n=0;ni;)t=t[e.substr(i,s-i)],i=s+1,s=rt(e,i);return t}function ht(t){return t.charAt(0).toUpperCase()+t.slice(1)}const ct=t=>void 0!==t,dt=t=>"function"==typeof t,ut=(t,e)=>{if(t.size!==e.size)return!1;for(const i of t)if(!e.has(i))return!1;return!0};function ft(t){return"mouseup"===t.type||"click"===t.type||"contextmenu"===t.type}const gt=Object.create(null),pt=Object.create(null);function mt(t,e){if(!e)return t;const i=e.split(".");for(let e=0,s=i.length;et.chart.platform.getDevicePixelRatio(),this.elements={},this.events=["mousemove","mouseout","click","touchstart","touchmove"],this.font={family:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",size:12,style:"normal",lineHeight:1.2,weight:null},this.hover={},this.hoverBackgroundColor=(t,e)=>N(e.backgroundColor),this.hoverBorderColor=(t,e)=>N(e.borderColor),this.hoverColor=(t,e)=>N(e.color),this.indexAxis="x",this.interaction={mode:"nearest",intersect:!0},this.maintainAspectRatio=!0,this.onHover=null,this.onClick=null,this.parsing=!0,this.plugins={},this.responsive=!0,this.scale=void 0,this.scales={},this.showLine=!0,this.drawActiveElementsOnTop=!0,this.describe(t)}set(t,e){return xt(this,t,e)}get(t){return mt(this,t)}describe(t,e){return xt(pt,t,e)}override(t,e){return xt(gt,t,e)}route(t,e,i,s){const n=mt(this,t),o=mt(this,i),a="_"+e;Object.defineProperties(n,{[a]:{value:n[e],writable:!0},[e]:{enumerable:!0,get(){const t=this[a],e=o[s];return U(t)?Object.assign({},e,t):K(t,e)},set(t){this[a]=t}}})}}({_scriptable:t=>!t.startsWith("on"),_indexable:t=>"events"!==t,hover:{_fallback:"interaction"},interaction:{_scriptable:!1,_indexable:!1}});const _t=Math.PI,yt=2*_t,vt=yt+_t,wt=Number.POSITIVE_INFINITY,Mt=_t/180,kt=_t/2,St=_t/4,Pt=2*_t/3,Dt=Math.log10,Ct=Math.sign;function Ot(t){const e=Math.round(t);t=Lt(t,e,t/1e3)?e:t;const i=Math.pow(10,Math.floor(Dt(t))),s=t/i;return(s<=1?1:s<=2?2:s<=5?5:10)*i}function At(t){const e=[],i=Math.sqrt(t);let s;for(s=1;st-e)).pop(),e}function Tt(t){return!isNaN(parseFloat(t))&&isFinite(t)}function Lt(t,e,i){return Math.abs(t-e)=t}function Et(t,e,i){let s,n,o;for(s=0,n=t.length;sl&&h=Math.min(e,i)-s&&t<=Math.max(e,i)+s}function Ut(t){return!t||$(t.size)||$(t.family)?null:(t.style?t.style+" ":"")+(t.weight?t.weight+" ":"")+t.size+"px "+t.family}function Xt(t,e,i,s,n){let o=e[n];return o||(o=e[n]=t.measureText(n).width,i.push(n)),o>s&&(s=o),s}function qt(t,e,i,s){let n=(s=s||{}).data=s.data||{},o=s.garbageCollect=s.garbageCollect||[];s.font!==e&&(n=s.data={},o=s.garbageCollect=[],s.font=e),t.save(),t.font=e;let a=0;const r=i.length;let l,h,c,d,u;for(l=0;li.length){for(l=0;l0&&t.stroke()}}function Jt(t,e,i){return i=i||.5,!e||t&&t.x>e.left-i&&t.xe.top-i&&t.y0&&""!==o.strokeColor;let l,h;for(t.save(),t.font=n.string,function(t,e){e.translation&&t.translate(e.translation[0],e.translation[1]);$(e.rotation)||t.rotate(e.rotation);e.color&&(t.fillStyle=e.color);e.textAlign&&(t.textAlign=e.textAlign);e.textBaseline&&(t.textBaseline=e.textBaseline)}(t,o),l=0;lt[i]1;)s=o+n>>1,i(s)?o=s:n=s;return{lo:o,hi:n}}const re=(t,e,i)=>ae(t,i,(s=>t[s][e]ae(t,i,(s=>t[s][e]>=i));function he(t,e,i){let s=0,n=t.length;for(;ss&&t[n-1]>i;)n--;return s>0||n{const i="_onData"+ht(e),s=t[e];Object.defineProperty(t,e,{configurable:!0,enumerable:!1,value(...e){const n=s.apply(this,e);return t._chartjs.listeners.forEach((t=>{"function"==typeof t[i]&&t[i](...e)})),n}})})))}function ue(t,e){const i=t._chartjs;if(!i)return;const s=i.listeners,n=s.indexOf(e);-1!==n&&s.splice(n,1),s.length>0||(ce.forEach((e=>{delete t[e]})),delete t._chartjs)}function fe(t){const e=new Set;let i,s;for(i=0,s=t.length;iwindow.getComputedStyle(t,null);function be(t,e){return xe(t).getPropertyValue(e)}const _e=["top","right","bottom","left"];function ye(t,e,i){const s={};i=i?"-"+i:"";for(let n=0;n<4;n++){const o=_e[n];s[o]=parseFloat(t[e+"-"+o+i])||0}return s.width=s.left+s.right,s.height=s.top+s.bottom,s}function ve(t,e){const{canvas:i,currentDevicePixelRatio:s}=e,n=xe(i),o="border-box"===n.boxSizing,a=ye(n,"padding"),r=ye(n,"border","width"),{x:l,y:h,box:c}=function(t,e){const i=t.native||t,s=i.touches,n=s&&s.length?s[0]:i,{offsetX:o,offsetY:a}=n;let r,l,h=!1;if(((t,e,i)=>(t>0||e>0)&&(!i||!i.shadowRoot))(o,a,i.target))r=o,l=a;else{const t=e.getBoundingClientRect();r=n.clientX-t.left,l=n.clientY-t.top,h=!0}return{x:r,y:l,box:h}}(t,i),d=a.left+(c&&r.left),u=a.top+(c&&r.top);let{width:f,height:g}=e;return o&&(f-=a.width+r.width,g-=a.height+r.height),{x:Math.round((l-d)/f*i.width/s),y:Math.round((h-u)/g*i.height/s)}}const we=t=>Math.round(10*t)/10;function Me(t,e,i,s){const n=xe(t),o=ye(n,"margin"),a=me(n.maxWidth,t,"clientWidth")||wt,r=me(n.maxHeight,t,"clientHeight")||wt,l=function(t,e,i){let s,n;if(void 0===e||void 0===i){const o=pe(t);if(o){const t=o.getBoundingClientRect(),a=xe(o),r=ye(a,"border","width"),l=ye(a,"padding");e=t.width-l.width-r.width,i=t.height-l.height-r.height,s=me(a.maxWidth,o,"clientWidth"),n=me(a.maxHeight,o,"clientHeight")}else e=t.clientWidth,i=t.clientHeight}return{width:e,height:i,maxWidth:s||wt,maxHeight:n||wt}}(t,e,i);let{width:h,height:c}=l;if("content-box"===n.boxSizing){const t=ye(n,"border","width"),e=ye(n,"padding");h-=e.width+t.width,c-=e.height+t.height}return h=Math.max(0,h-o.width),c=Math.max(0,s?Math.floor(h/s):c-o.height),h=we(Math.min(h,a,l.maxWidth)),c=we(Math.min(c,r,l.maxHeight)),h&&!c&&(c=we(h/2)),{width:h,height:c}}function ke(t,e,i){const s=e||1,n=Math.floor(t.height*s),o=Math.floor(t.width*s);t.height=n/s,t.width=o/s;const a=t.canvas;return a.style&&(i||!a.style.height&&!a.style.width)&&(a.style.height=`${t.height}px`,a.style.width=`${t.width}px`),(t.currentDevicePixelRatio!==s||a.height!==n||a.width!==o)&&(t.currentDevicePixelRatio=s,a.height=n,a.width=o,t.ctx.setTransform(s,0,0,s,0,0),!0)}const Se=function(){let t=!1;try{const e={get passive(){return t=!0,!1}};window.addEventListener("test",null,e),window.removeEventListener("test",null,e)}catch(t){}return t}();function Pe(t,e){const i=be(t,e),s=i&&i.match(/^(\d+)(\.\d+)?px$/);return s?+s[1]:void 0}function De(t,e){return"native"in t?{x:t.x,y:t.y}:ve(t,e)}function Ce(t,e,i,s){const{controller:n,data:o,_sorted:a}=t,r=n._cachedMeta.iScale;if(r&&e===r.axis&&"r"!==e&&a&&o.length){const t=r._reversePixels?le:re;if(!s)return t(o,e,i);if(n._sharedOptions){const s=o[0],n="function"==typeof s.getRange&&s.getRange(e);if(n){const s=t(o,e,i-n),a=t(o,e,i+n);return{lo:s.lo,hi:a.hi}}}}return{lo:0,hi:o.length-1}}function Oe(t,e,i,s,n){const o=t.getSortedVisibleDatasetMetas(),a=i[e];for(let t=0,i=o.length;t{t[r](n[a],s)&&o.push({element:t,datasetIndex:e,index:i}),t.inRange(n.x,n.y,s)&&(l=!0)})),i.intersect&&!l?[]:o}var Ee={modes:{index(t,e,i,s){const n=De(e,t),o=i.axis||"x",a=i.intersect?Ae(t,n,o,s):Le(t,n,o,!1,s),r=[];return a.length?(t.getSortedVisibleDatasetMetas().forEach((t=>{const e=a[0].index,i=t.data[e];i&&!i.skip&&r.push({element:i,datasetIndex:t.index,index:e})})),r):[]},dataset(t,e,i,s){const n=De(e,t),o=i.axis||"xy";let a=i.intersect?Ae(t,n,o,s):Le(t,n,o,!1,s);if(a.length>0){const e=a[0].datasetIndex,i=t.getDatasetMeta(e).data;a=[];for(let t=0;tAe(t,De(e,t),i.axis||"xy",s),nearest:(t,e,i,s)=>Le(t,De(e,t),i.axis||"xy",i.intersect,s),x:(t,e,i,s)=>Re(t,e,{axis:"x",intersect:i.intersect},s),y:(t,e,i,s)=>Re(t,e,{axis:"y",intersect:i.intersect},s)}};const Ie=new RegExp(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/),ze=new RegExp(/^(normal|italic|initial|inherit|unset|(oblique( -?[0-9]?[0-9]deg)?))$/);function Fe(t,e){const i=(""+t).match(Ie);if(!i||"normal"===i[1])return 1.2*e;switch(t=+i[2],i[3]){case"px":return t;case"%":t/=100}return e*t}function Be(t,e){const i={},s=U(e),n=s?Object.keys(e):e,o=U(t)?s?i=>K(t[i],t[e[i]]):e=>t[e]:()=>t;for(const t of n)i[t]=+o(t)||0;return i}function Ve(t){return Be(t,{top:"y",right:"x",bottom:"y",left:"x"})}function We(t){return Be(t,["topLeft","topRight","bottomLeft","bottomRight"])}function Ne(t){const e=Ve(t);return e.width=e.left+e.right,e.height=e.top+e.bottom,e}function He(t,e){t=t||{},e=e||bt.font;let i=K(t.size,e.size);"string"==typeof i&&(i=parseInt(i,10));let s=K(t.style,e.style);s&&!(""+s).match(ze)&&(console.warn('Invalid font style specified: "'+s+'"'),s="");const n={family:K(t.family,e.family),lineHeight:Fe(K(t.lineHeight,e.lineHeight),i),size:i,style:s,weight:K(t.weight,e.weight),string:""};return n.string=Ut(n),n}function je(t,e,i,s){let n,o,a,r=!0;for(n=0,o=t.length;ni&&0===t?0:t+e;return{min:a(s,-Math.abs(o)),max:a(n,o)}}function Ye(t,e){return Object.assign(Object.create(t),e)}const Ue=["left","top","right","bottom"];function Xe(t,e){return t.filter((t=>t.pos===e))}function qe(t,e){return t.filter((t=>-1===Ue.indexOf(t.pos)&&t.box.axis===e))}function Ke(t,e){return t.sort(((t,i)=>{const s=e?i:t,n=e?t:i;return s.weight===n.weight?s.index-n.index:s.weight-n.weight}))}function Ge(t,e){const i=function(t){const e={};for(const i of t){const{stack:t,pos:s,stackWeight:n}=i;if(!t||!Ue.includes(s))continue;const o=e[t]||(e[t]={count:0,placed:0,weight:0,size:0});o.count++,o.weight+=n}return e}(t),{vBoxMaxWidth:s,hBoxMaxHeight:n}=e;let o,a,r;for(o=0,a=t.length;o{s[t]=Math.max(e[t],i[t])})),s}return s(t?["left","right"]:["top","bottom"])}function ei(t,e,i,s){const n=[];let o,a,r,l,h,c;for(o=0,a=t.length,h=0;ot.box.fullSize)),!0),s=Ke(Xe(e,"left"),!0),n=Ke(Xe(e,"right")),o=Ke(Xe(e,"top"),!0),a=Ke(Xe(e,"bottom")),r=qe(e,"x"),l=qe(e,"y");return{fullSize:i,leftAndTop:s.concat(o),rightAndBottom:n.concat(l).concat(a).concat(r),chartArea:Xe(e,"chartArea"),vertical:s.concat(n).concat(l),horizontal:o.concat(a).concat(r)}}(t.boxes),l=r.vertical,h=r.horizontal;Q(t.boxes,(t=>{"function"==typeof t.beforeLayout&&t.beforeLayout()}));const c=l.reduce(((t,e)=>e.box.options&&!1===e.box.options.display?t:t+1),0)||1,d=Object.freeze({outerWidth:e,outerHeight:i,padding:n,availableWidth:o,availableHeight:a,vBoxMaxWidth:o/2/c,hBoxMaxHeight:a/2}),u=Object.assign({},n);Je(u,Ne(s));const f=Object.assign({maxPadding:u,w:o,h:a,x:n.left,y:n.top},n),g=Ge(l.concat(h),d);ei(r.fullSize,f,d,g),ei(l,f,d,g),ei(h,f,d,g)&&ei(l,f,d,g),function(t){const e=t.maxPadding;function i(i){const s=Math.max(e[i]-t[i],0);return t[i]+=s,s}t.y+=i("top"),t.x+=i("left"),i("right"),i("bottom")}(f),si(r.leftAndTop,f,d,g),f.x+=f.w,f.y+=f.h,si(r.rightAndBottom,f,d,g),t.chartArea={left:f.left,top:f.top,right:f.left+f.w,bottom:f.top+f.h,height:f.h,width:f.w},Q(r.chartArea,(e=>{const i=e.box;Object.assign(i,t.chartArea),i.update(f.w,f.h,{left:0,top:0,right:0,bottom:0})}))}};function oi(t,e=[""],i=t,s,n=(()=>t[0])){ct(s)||(s=mi("_fallback",t));const o={[Symbol.toStringTag]:"Object",_cacheable:!0,_scopes:t,_rootScopes:i,_fallback:s,_getTarget:n,override:n=>oi([n,...t],e,i,s)};return new Proxy(o,{deleteProperty:(e,i)=>(delete e[i],delete e._keys,delete t[0][i],!0),get:(i,s)=>ci(i,s,(()=>function(t,e,i,s){let n;for(const o of e)if(n=mi(li(o,t),i),ct(n))return hi(t,n)?gi(i,s,t,n):n}(s,e,t,i))),getOwnPropertyDescriptor:(t,e)=>Reflect.getOwnPropertyDescriptor(t._scopes[0],e),getPrototypeOf:()=>Reflect.getPrototypeOf(t[0]),has:(t,e)=>xi(t).includes(e),ownKeys:t=>xi(t),set(t,e,i){const s=t._storage||(t._storage=n());return t[e]=s[e]=i,delete t._keys,!0}})}function ai(t,e,i,s){const n={_cacheable:!1,_proxy:t,_context:e,_subProxy:i,_stack:new Set,_descriptors:ri(t,s),setContext:e=>ai(t,e,i,s),override:n=>ai(t.override(n),e,i,s)};return new Proxy(n,{deleteProperty:(e,i)=>(delete e[i],delete t[i],!0),get:(t,e,i)=>ci(t,e,(()=>function(t,e,i){const{_proxy:s,_context:n,_subProxy:o,_descriptors:a}=t;let r=s[e];dt(r)&&a.isScriptable(e)&&(r=function(t,e,i,s){const{_proxy:n,_context:o,_subProxy:a,_stack:r}=i;if(r.has(t))throw new Error("Recursion detected: "+Array.from(r).join("->")+"->"+t);r.add(t),e=e(o,a||s),r.delete(t),hi(t,e)&&(e=gi(n._scopes,n,t,e));return e}(e,r,t,i));Y(r)&&r.length&&(r=function(t,e,i,s){const{_proxy:n,_context:o,_subProxy:a,_descriptors:r}=i;if(ct(o.index)&&s(t))e=e[o.index%e.length];else if(U(e[0])){const i=e,s=n._scopes.filter((t=>t!==i));e=[];for(const l of i){const i=gi(s,n,t,l);e.push(ai(i,o,a&&a[t],r))}}return e}(e,r,t,a.isIndexable));hi(e,r)&&(r=ai(r,n,o&&o[e],a));return r}(t,e,i))),getOwnPropertyDescriptor:(e,i)=>e._descriptors.allKeys?Reflect.has(t,i)?{enumerable:!0,configurable:!0}:void 0:Reflect.getOwnPropertyDescriptor(t,i),getPrototypeOf:()=>Reflect.getPrototypeOf(t),has:(e,i)=>Reflect.has(t,i),ownKeys:()=>Reflect.ownKeys(t),set:(e,i,s)=>(t[i]=s,delete e[i],!0)})}function ri(t,e={scriptable:!0,indexable:!0}){const{_scriptable:i=e.scriptable,_indexable:s=e.indexable,_allKeys:n=e.allKeys}=t;return{allKeys:n,scriptable:i,indexable:s,isScriptable:dt(i)?i:()=>i,isIndexable:dt(s)?s:()=>s}}const li=(t,e)=>t?t+ht(e):e,hi=(t,e)=>U(e)&&"adapters"!==t&&(null===Object.getPrototypeOf(e)||e.constructor===Object);function ci(t,e,i){if(Object.prototype.hasOwnProperty.call(t,e))return t[e];const s=i();return t[e]=s,s}function di(t,e,i){return dt(t)?t(e,i):t}const ui=(t,e)=>!0===t?e:"string"==typeof t?lt(e,t):void 0;function fi(t,e,i,s,n){for(const o of e){const e=ui(i,o);if(e){t.add(e);const o=di(e._fallback,i,n);if(ct(o)&&o!==i&&o!==s)return o}else if(!1===e&&ct(s)&&i!==s)return null}return!1}function gi(t,e,i,s){const n=e._rootScopes,o=di(e._fallback,i,s),a=[...t,...n],r=new Set;r.add(s);let l=pi(r,a,i,o||i,s);return null!==l&&((!ct(o)||o===i||(l=pi(r,a,o,l,s),null!==l))&&oi(Array.from(r),[""],n,o,(()=>function(t,e,i){const s=t._getTarget();e in s||(s[e]={});const n=s[e];if(Y(n)&&U(i))return i;return n}(e,i,s))))}function pi(t,e,i,s,n){for(;i;)i=fi(t,e,i,s,n);return i}function mi(t,e){for(const i of e){if(!i)continue;const e=i[t];if(ct(e))return e}}function xi(t){let e=t._keys;return e||(e=t._keys=function(t){const e=new Set;for(const i of t)for(const t of Object.keys(i).filter((t=>!t.startsWith("_"))))e.add(t);return Array.from(e)}(t._scopes)),e}const bi=Number.EPSILON||1e-14,_i=(t,e)=>e"x"===t?"y":"x";function vi(t,e,i,s){const n=t.skip?e:t,o=e,a=i.skip?e:i,r=Vt(o,n),l=Vt(a,o);let h=r/(r+l),c=l/(r+l);h=isNaN(h)?0:h,c=isNaN(c)?0:c;const d=s*h,u=s*c;return{previous:{x:o.x-d*(a.x-n.x),y:o.y-d*(a.y-n.y)},next:{x:o.x+u*(a.x-n.x),y:o.y+u*(a.y-n.y)}}}function wi(t,e="x"){const i=yi(e),s=t.length,n=Array(s).fill(0),o=Array(s);let a,r,l,h=_i(t,0);for(a=0;a!t.skip))),"monotone"===e.cubicInterpolationMode)wi(t,n);else{let i=s?t[t.length-1]:t[0];for(o=0,a=t.length;o0===t||1===t,Pi=(t,e,i)=>-Math.pow(2,10*(t-=1))*Math.sin((t-e)*yt/i),Di=(t,e,i)=>Math.pow(2,-10*t)*Math.sin((t-e)*yt/i)+1,Ci={linear:t=>t,easeInQuad:t=>t*t,easeOutQuad:t=>-t*(t-2),easeInOutQuad:t=>(t/=.5)<1?.5*t*t:-.5*(--t*(t-2)-1),easeInCubic:t=>t*t*t,easeOutCubic:t=>(t-=1)*t*t+1,easeInOutCubic:t=>(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2),easeInQuart:t=>t*t*t*t,easeOutQuart:t=>-((t-=1)*t*t*t-1),easeInOutQuart:t=>(t/=.5)<1?.5*t*t*t*t:-.5*((t-=2)*t*t*t-2),easeInQuint:t=>t*t*t*t*t,easeOutQuint:t=>(t-=1)*t*t*t*t+1,easeInOutQuint:t=>(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2),easeInSine:t=>1-Math.cos(t*kt),easeOutSine:t=>Math.sin(t*kt),easeInOutSine:t=>-.5*(Math.cos(_t*t)-1),easeInExpo:t=>0===t?0:Math.pow(2,10*(t-1)),easeOutExpo:t=>1===t?1:1-Math.pow(2,-10*t),easeInOutExpo:t=>Si(t)?t:t<.5?.5*Math.pow(2,10*(2*t-1)):.5*(2-Math.pow(2,-10*(2*t-1))),easeInCirc:t=>t>=1?t:-(Math.sqrt(1-t*t)-1),easeOutCirc:t=>Math.sqrt(1-(t-=1)*t),easeInOutCirc:t=>(t/=.5)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1),easeInElastic:t=>Si(t)?t:Pi(t,.075,.3),easeOutElastic:t=>Si(t)?t:Di(t,.075,.3),easeInOutElastic(t){const e=.1125;return Si(t)?t:t<.5?.5*Pi(2*t,e,.45):.5+.5*Di(2*t-1,e,.45)},easeInBack(t){const e=1.70158;return t*t*((e+1)*t-e)},easeOutBack(t){const e=1.70158;return(t-=1)*t*((e+1)*t+e)+1},easeInOutBack(t){let e=1.70158;return(t/=.5)<1?t*t*((1+(e*=1.525))*t-e)*.5:.5*((t-=2)*t*((1+(e*=1.525))*t+e)+2)},easeInBounce:t=>1-Ci.easeOutBounce(1-t),easeOutBounce(t){const e=7.5625,i=2.75;return t<1/i?e*t*t:t<2/i?e*(t-=1.5/i)*t+.75:t<2.5/i?e*(t-=2.25/i)*t+.9375:e*(t-=2.625/i)*t+.984375},easeInOutBounce:t=>t<.5?.5*Ci.easeInBounce(2*t):.5*Ci.easeOutBounce(2*t-1)+.5};function Oi(t,e,i,s){return{x:t.x+i*(e.x-t.x),y:t.y+i*(e.y-t.y)}}function Ai(t,e,i,s){return{x:t.x+i*(e.x-t.x),y:"middle"===s?i<.5?t.y:e.y:"after"===s?i<1?t.y:e.y:i>0?e.y:t.y}}function Ti(t,e,i,s){const n={x:t.cp2x,y:t.cp2y},o={x:e.cp1x,y:e.cp1y},a=Oi(t,n,i),r=Oi(n,o,i),l=Oi(o,e,i),h=Oi(a,r,i),c=Oi(r,l,i);return Oi(h,c,i)}const Li=new Map;function Ri(t,e,i){return function(t,e){e=e||{};const i=t+JSON.stringify(e);let s=Li.get(i);return s||(s=new Intl.NumberFormat(t,e),Li.set(i,s)),s}(e,i).format(t)}function Ei(t,e,i){return t?function(t,e){return{x:i=>t+t+e-i,setWidth(t){e=t},textAlign:t=>"center"===t?t:"right"===t?"left":"right",xPlus:(t,e)=>t-e,leftForLtr:(t,e)=>t-e}}(e,i):{x:t=>t,setWidth(t){},textAlign:t=>t,xPlus:(t,e)=>t+e,leftForLtr:(t,e)=>t}}function Ii(t,e){let i,s;"ltr"!==e&&"rtl"!==e||(i=t.canvas.style,s=[i.getPropertyValue("direction"),i.getPropertyPriority("direction")],i.setProperty("direction",e,"important"),t.prevTextDirection=s)}function zi(t,e){void 0!==e&&(delete t.prevTextDirection,t.canvas.style.setProperty("direction",e[0],e[1]))}function Fi(t){return"angle"===t?{between:Ht,compare:Wt,normalize:Nt}:{between:Yt,compare:(t,e)=>t-e,normalize:t=>t}}function Bi({start:t,end:e,count:i,loop:s,style:n}){return{start:t%i,end:e%i,loop:s&&(e-t+1)%i==0,style:n}}function Vi(t,e,i){if(!i)return[t];const{property:s,start:n,end:o}=i,a=e.length,{compare:r,between:l,normalize:h}=Fi(s),{start:c,end:d,loop:u,style:f}=function(t,e,i){const{property:s,start:n,end:o}=i,{between:a,normalize:r}=Fi(s),l=e.length;let h,c,{start:d,end:u,loop:f}=t;if(f){for(d+=l,u+=l,h=0,c=l;hb||l(n,x,p)&&0!==r(n,x),v=()=>!b||0===r(o,p)||l(o,x,p);for(let t=c,i=c;t<=d;++t)m=e[t%a],m.skip||(p=h(m[s]),p!==x&&(b=l(p,n,o),null===_&&y()&&(_=0===r(p,n)?t:i),null!==_&&v()&&(g.push(Bi({start:_,end:t,loop:u,count:a,style:f})),_=null),i=t,x=p));return null!==_&&g.push(Bi({start:_,end:d,loop:u,count:a,style:f})),g}function Wi(t,e){const i=[],s=t.segments;for(let n=0;nn&&t[o%e].skip;)o--;return o%=e,{start:n,end:o}}(i,n,o,s);if(!0===s)return Hi(t,[{start:a,end:r,loop:o}],i,e);return Hi(t,function(t,e,i,s){const n=t.length,o=[];let a,r=e,l=t[e];for(a=e+1;a<=i;++a){const i=t[a%n];i.skip||i.stop?l.skip||(s=!1,o.push({start:e%n,end:(a-1)%n,loop:s}),e=r=i.stop?a:null):(r=a,l.skip&&(e=a)),l=i}return null!==r&&o.push({start:e%n,end:r%n,loop:s}),o}(i,a,rnull===t||""===t;const Gi=!!Se&&{passive:!0};function Zi(t,e,i){t.canvas.removeEventListener(e,i,Gi)}function Ji(t,e){for(const i of t)if(i===e||i.contains(e))return!0}function Qi(t,e,i){const s=t.canvas,n=new MutationObserver((t=>{let e=!1;for(const i of t)e=e||Ji(i.addedNodes,s),e=e&&!Ji(i.removedNodes,s);e&&i()}));return n.observe(document,{childList:!0,subtree:!0}),n}function ts(t,e,i){const s=t.canvas,n=new MutationObserver((t=>{let e=!1;for(const i of t)e=e||Ji(i.removedNodes,s),e=e&&!Ji(i.addedNodes,s);e&&i()}));return n.observe(document,{childList:!0,subtree:!0}),n}const es=new Map;let is=0;function ss(){const t=window.devicePixelRatio;t!==is&&(is=t,es.forEach(((e,i)=>{i.currentDevicePixelRatio!==t&&e()})))}function ns(t,i,s){const n=t.canvas,o=n&&pe(n);if(!o)return;const a=e(((t,e)=>{const i=o.clientWidth;s(t,e),i{const e=t[0],i=e.contentRect.width,s=e.contentRect.height;0===i&&0===s||a(i,s)}));return r.observe(o),function(t,e){es.size||window.addEventListener("resize",ss),es.set(t,e)}(t,a),r}function os(t,e,i){i&&i.disconnect(),"resize"===e&&function(t){es.delete(t),es.size||window.removeEventListener("resize",ss)}(t)}function as(t,i,s){const n=t.canvas,o=e((e=>{null!==t.ctx&&s(function(t,e){const i=qi[t.type]||t.type,{x:s,y:n}=ve(t,e);return{type:i,chart:e,native:t,x:void 0!==s?s:null,y:void 0!==n?n:null}}(e,t))}),t,(t=>{const e=t[0];return[e,e.offsetX,e.offsetY]}));return function(t,e,i){t.addEventListener(e,i,Gi)}(n,i,o),o}class rs extends Ui{acquireContext(t,e){const i=t&&t.getContext&&t.getContext("2d");return i&&i.canvas===t?(function(t,e){const i=t.style,s=t.getAttribute("height"),n=t.getAttribute("width");if(t.$chartjs={initial:{height:s,width:n,style:{display:i.display,height:i.height,width:i.width}}},i.display=i.display||"block",i.boxSizing=i.boxSizing||"border-box",Ki(n)){const e=Pe(t,"width");void 0!==e&&(t.width=e)}if(Ki(s))if(""===t.style.height)t.height=t.width/(e||2);else{const e=Pe(t,"height");void 0!==e&&(t.height=e)}}(t,e),i):null}releaseContext(t){const e=t.canvas;if(!e.$chartjs)return!1;const i=e.$chartjs.initial;["height","width"].forEach((t=>{const s=i[t];$(s)?e.removeAttribute(t):e.setAttribute(t,s)}));const s=i.style||{};return Object.keys(s).forEach((t=>{e.style[t]=s[t]})),e.width=e.width,delete e.$chartjs,!0}addEventListener(t,e,i){this.removeEventListener(t,e);const s=t.$proxies||(t.$proxies={}),n={attach:Qi,detach:ts,resize:ns}[e]||as;s[e]=n(t,e,i)}removeEventListener(t,e){const i=t.$proxies||(t.$proxies={}),s=i[e];if(!s)return;({attach:os,detach:os,resize:os}[e]||Zi)(t,e,s),i[e]=void 0}getDevicePixelRatio(){return window.devicePixelRatio}getMaximumSize(t,e,i,s){return Me(t,e,i,s)}isAttached(t){const e=pe(t);return!(!e||!e.isConnected)}}function ls(t){return!ge()||"undefined"!=typeof OffscreenCanvas&&t instanceof OffscreenCanvas?Xi:rs}var hs=Object.freeze({__proto__:null,_detectPlatform:ls,BasePlatform:Ui,BasicPlatform:Xi,DomPlatform:rs});const cs="transparent",ds={boolean:(t,e,i)=>i>.5?e:t,color(t,e,i){const s=W(t||cs),n=s.valid&&W(e||cs);return n&&n.valid?n.mix(s,i).hexString():e},number:(t,e,i)=>t+(e-t)*i};class us{constructor(t,e,i,s){const n=e[i];s=je([t.to,s,n,t.from]);const o=je([t.from,n,s]);this._active=!0,this._fn=t.fn||ds[t.type||typeof o],this._easing=Ci[t.easing]||Ci.linear,this._start=Math.floor(Date.now()+(t.delay||0)),this._duration=this._total=Math.floor(t.duration),this._loop=!!t.loop,this._target=e,this._prop=i,this._from=o,this._to=s,this._promises=void 0}active(){return this._active}update(t,e,i){if(this._active){this._notify(!1);const s=this._target[this._prop],n=i-this._start,o=this._duration-n;this._start=i,this._duration=Math.floor(Math.max(o,t.duration)),this._total+=n,this._loop=!!t.loop,this._to=je([t.to,e,s,t.from]),this._from=je([t.from,s,e])}}cancel(){this._active&&(this.tick(Date.now()),this._active=!1,this._notify(!1))}tick(t){const e=t-this._start,i=this._duration,s=this._prop,n=this._from,o=this._loop,a=this._to;let r;if(this._active=n!==a&&(o||e1?2-r:r,r=this._easing(Math.min(1,Math.max(0,r))),this._target[s]=this._fn(n,a,r))}wait(){const t=this._promises||(this._promises=[]);return new Promise(((e,i)=>{t.push({res:e,rej:i})}))}_notify(t){const e=t?"res":"rej",i=this._promises||[];for(let t=0;t"onProgress"!==t&&"onComplete"!==t&&"fn"!==t}),bt.set("animations",{colors:{type:"color",properties:["color","borderColor","backgroundColor"]},numbers:{type:"number",properties:["x","y","borderWidth","radius","tension"]}}),bt.describe("animations",{_fallback:"animation"}),bt.set("transitions",{active:{animation:{duration:400}},resize:{animation:{duration:0}},show:{animations:{colors:{from:"transparent"},visible:{type:"boolean",duration:0}}},hide:{animations:{colors:{to:"transparent"},visible:{type:"boolean",easing:"linear",fn:t=>0|t}}}});class gs{constructor(t,e){this._chart=t,this._properties=new Map,this.configure(e)}configure(t){if(!U(t))return;const e=this._properties;Object.getOwnPropertyNames(t).forEach((i=>{const s=t[i];if(!U(s))return;const n={};for(const t of fs)n[t]=s[t];(Y(s.properties)&&s.properties||[i]).forEach((t=>{t!==i&&e.has(t)||e.set(t,n)}))}))}_animateOptions(t,e){const i=e.options,s=function(t,e){if(!e)return;let i=t.options;if(!i)return void(t.options=e);i.$shared&&(t.options=i=Object.assign({},i,{$shared:!1,$animations:{}}));return i}(t,i);if(!s)return[];const n=this._createAnimations(s,i);return i.$shared&&function(t,e){const i=[],s=Object.keys(e);for(let e=0;e{t.options=i}),(()=>{})),n}_createAnimations(t,e){const i=this._properties,s=[],n=t.$animations||(t.$animations={}),o=Object.keys(e),a=Date.now();let r;for(r=o.length-1;r>=0;--r){const l=o[r];if("$"===l.charAt(0))continue;if("options"===l){s.push(...this._animateOptions(t,e));continue}const h=e[l];let c=n[l];const d=i.get(l);if(c){if(d&&c.active()){c.update(d,h,a);continue}c.cancel()}d&&d.duration?(n[l]=c=new us(d,t,l,h),s.push(c)):t[l]=h}return s}update(t,e){if(0===this._properties.size)return void Object.assign(t,e);const i=this._createAnimations(t,e);return i.length?(a.add(this._chart,i),!0):void 0}}function ps(t,e){const i=t&&t.options||{},s=i.reverse,n=void 0===i.min?e:0,o=void 0===i.max?e:0;return{start:s?o:n,end:s?n:o}}function ms(t,e){const i=[],s=t._getSortedDatasetMetas(e);let n,o;for(n=0,o=s.length;n0||!i&&e<0)return n.index}return null}function vs(t,e){const{chart:i,_cachedMeta:s}=t,n=i._stacks||(i._stacks={}),{iScale:o,vScale:a,index:r}=s,l=o.axis,h=a.axis,c=function(t,e,i){return`${t.id}.${e.id}.${i.stack||i.type}`}(o,a,s),d=e.length;let u;for(let t=0;ti[t].axis===e)).shift()}function Ms(t,e){const i=t.controller.index,s=t.vScale&&t.vScale.axis;if(s){e=e||t._parsed;for(const t of e){const e=t._stacks;if(!e||void 0===e[s]||void 0===e[s][i])return;delete e[s][i]}}}const ks=t=>"reset"===t||"none"===t,Ss=(t,e)=>e?t:Object.assign({},t);class Ps{constructor(t,e){this.chart=t,this._ctx=t.ctx,this.index=e,this._cachedDataOpts={},this._cachedMeta=this.getMeta(),this._type=this._cachedMeta.type,this.options=void 0,this._parsing=!1,this._data=void 0,this._objectData=void 0,this._sharedOptions=void 0,this._drawStart=void 0,this._drawCount=void 0,this.enableOptionSharing=!1,this.$context=void 0,this._syncList=[],this.initialize()}initialize(){const t=this._cachedMeta;this.configure(),this.linkScales(),t._stacked=bs(t.vScale,t),this.addElements()}updateIndex(t){this.index!==t&&Ms(this._cachedMeta),this.index=t}linkScales(){const t=this.chart,e=this._cachedMeta,i=this.getDataset(),s=(t,e,i,s)=>"x"===t?e:"r"===t?s:i,n=e.xAxisID=K(i.xAxisID,ws(t,"x")),o=e.yAxisID=K(i.yAxisID,ws(t,"y")),a=e.rAxisID=K(i.rAxisID,ws(t,"r")),r=e.indexAxis,l=e.iAxisID=s(r,n,o,a),h=e.vAxisID=s(r,o,n,a);e.xScale=this.getScaleForId(n),e.yScale=this.getScaleForId(o),e.rScale=this.getScaleForId(a),e.iScale=this.getScaleForId(l),e.vScale=this.getScaleForId(h)}getDataset(){return this.chart.data.datasets[this.index]}getMeta(){return this.chart.getDatasetMeta(this.index)}getScaleForId(t){return this.chart.scales[t]}_getOtherScale(t){const e=this._cachedMeta;return t===e.iScale?e.vScale:e.iScale}reset(){this._update("reset")}_destroy(){const t=this._cachedMeta;this._data&&ue(this._data,this),t._stacked&&Ms(t)}_dataCheck(){const t=this.getDataset(),e=t.data||(t.data=[]),i=this._data;if(U(e))this._data=function(t){const e=Object.keys(t),i=new Array(e.length);let s,n,o;for(s=0,n=e.length;s0&&i._parsed[t-1];if(!1===this._parsing)i._parsed=s,i._sorted=!0,h=s;else{h=Y(s[t])?this.parseArrayData(i,s,t,e):U(s[t])?this.parseObjectData(i,s,t,e):this.parsePrimitiveData(i,s,t,e);const n=()=>null===l[a]||d&&l[a]t&&!e.hidden&&e._stacked&&{keys:ms(i,!0),values:null})(e,i,this.chart),l={min:Number.POSITIVE_INFINITY,max:Number.NEGATIVE_INFINITY},{min:h,max:c}=function(t){const{min:e,max:i,minDefined:s,maxDefined:n}=t.getUserBounds();return{min:s?e:Number.NEGATIVE_INFINITY,max:n?i:Number.POSITIVE_INFINITY}}(a);let d,u;function f(){u=s[d];const e=u[a.axis];return!X(u[t.axis])||h>e||c=0;--d)if(!f()){this.updateRangeFromParsed(l,t,u,r);break}return l}getAllParsedValues(t){const e=this._cachedMeta._parsed,i=[];let s,n,o;for(s=0,n=e.length;s=0&&tthis.getContext(i,s)),c);return f.$shared&&(f.$shared=r,n[o]=Object.freeze(Ss(f,r))),f}_resolveAnimations(t,e,i){const s=this.chart,n=this._cachedDataOpts,o=`animation-${e}`,a=n[o];if(a)return a;let r;if(!1!==s.options.animation){const s=this.chart.config,n=s.datasetAnimationScopeKeys(this._type,e),o=s.getOptionScopes(this.getDataset(),n);r=s.createResolver(o,this.getContext(t,i,e))}const l=new gs(s,r&&r.animations);return r&&r._cacheable&&(n[o]=Object.freeze(l)),l}getSharedOptions(t){if(t.$shared)return this._sharedOptions||(this._sharedOptions=Object.assign({},t))}includeOptions(t,e){return!e||ks(t)||this.chart._animationsDisabled}updateElement(t,e,i,s){ks(s)?Object.assign(t,i):this._resolveAnimations(e,s).update(t,i)}updateSharedOptions(t,e,i){t&&!ks(e)&&this._resolveAnimations(void 0,e).update(t,i)}_setStyle(t,e,i,s){t.active=s;const n=this.getStyle(e,s);this._resolveAnimations(e,i,s).update(t,{options:!s&&this.getSharedOptions(n)||n})}removeHoverStyle(t,e,i){this._setStyle(t,i,"active",!1)}setHoverStyle(t,e,i){this._setStyle(t,i,"active",!0)}_removeDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!1)}_setDatasetHoverStyle(){const t=this._cachedMeta.dataset;t&&this._setStyle(t,void 0,"active",!0)}_resyncElements(t){const e=this._data,i=this._cachedMeta.data;for(const[t,e,i]of this._syncList)this[t](e,i);this._syncList=[];const s=i.length,n=e.length,o=Math.min(n,s);o&&this.parse(0,o),n>s?this._insertElements(s,n-s,t):n{for(t.length+=e,a=t.length-1;a>=o;a--)t[a]=t[a-e]};for(r(n),a=t;a{s[t]=i[t]&&i[t].active()?i[t]._to:this[t]})),s}}Ds.defaults={},Ds.defaultRoutes=void 0;const Cs={values:t=>Y(t)?t:""+t,numeric(t,e,i){if(0===t)return"0";const s=this.chart.options.locale;let n,o=t;if(i.length>1){const e=Math.max(Math.abs(i[0].value),Math.abs(i[i.length-1].value));(e<1e-4||e>1e15)&&(n="scientific"),o=function(t,e){let i=e.length>3?e[2].value-e[1].value:e[1].value-e[0].value;Math.abs(i)>=1&&t!==Math.floor(t)&&(i=t-Math.floor(t));return i}(t,i)}const a=Dt(Math.abs(o)),r=Math.max(Math.min(-1*Math.floor(a),20),0),l={notation:n,minimumFractionDigits:r,maximumFractionDigits:r};return Object.assign(l,this.options.ticks.format),Ri(t,s,l)},logarithmic(t,e,i){if(0===t)return"0";const s=t/Math.pow(10,Math.floor(Dt(t)));return 1===s||2===s||5===s?Cs.numeric.call(this,t,e,i):""}};var Os={formatters:Cs};function As(t,e){const i=t.options.ticks,s=i.maxTicksLimit||function(t){const e=t.options.offset,i=t._tickSize(),s=t._length/i+(e?0:1),n=t._maxLength/i;return Math.floor(Math.min(s,n))}(t),n=i.major.enabled?function(t){const e=[];let i,s;for(i=0,s=t.length;is)return function(t,e,i,s){let n,o=0,a=i[0];for(s=Math.ceil(s),n=0;nn)return e}return Math.max(n,1)}(n,e,s);if(o>0){let t,i;const s=o>1?Math.round((r-a)/(o-1)):null;for(Ts(e,l,h,$(s)?0:a-s,a),t=0,i=o-1;te.lineWidth,tickColor:(t,e)=>e.color,offset:!1,borderDash:[],borderDashOffset:0,borderWidth:1},title:{display:!1,text:"",padding:{top:4,bottom:4}},ticks:{minRotation:0,maxRotation:50,mirror:!1,textStrokeWidth:0,textStrokeColor:"",padding:3,display:!0,autoSkip:!0,autoSkipPadding:3,labelOffset:0,callback:Os.formatters.values,minor:{},major:{},align:"center",crossAlign:"near",showLabelBackdrop:!1,backdropColor:"rgba(255, 255, 255, 0.75)",backdropPadding:2}}),bt.route("scale.ticks","color","","color"),bt.route("scale.grid","color","","borderColor"),bt.route("scale.grid","borderColor","","borderColor"),bt.route("scale.title","color","","color"),bt.describe("scale",{_fallback:!1,_scriptable:t=>!t.startsWith("before")&&!t.startsWith("after")&&"callback"!==t&&"parser"!==t,_indexable:t=>"borderDash"!==t&&"tickBorderDash"!==t}),bt.describe("scales",{_fallback:"scale"}),bt.describe("scale.ticks",{_scriptable:t=>"backdropPadding"!==t&&"callback"!==t,_indexable:t=>"backdropPadding"!==t});const Ls=(t,e,i)=>"top"===e||"left"===e?t[e]+i:t[e]-i;function Rs(t,e){const i=[],s=t.length/e,n=t.length;let o=0;for(;oa+r)))return h}function Is(t){return t.drawTicks?t.tickLength:0}function zs(t,e){if(!t.display)return 0;const i=He(t.font,e),s=Ne(t.padding);return(Y(t.text)?t.text.length:1)*i.lineHeight+s.height}function Fs(t,e,i){let n=s(t);return(i&&"right"!==e||!i&&"right"===e)&&(n=(t=>"left"===t?"right":"right"===t?"left":t)(n)),n}class Bs extends Ds{constructor(t){super(),this.id=t.id,this.type=t.type,this.options=void 0,this.ctx=t.ctx,this.chart=t.chart,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this._margins={left:0,right:0,top:0,bottom:0},this.maxWidth=void 0,this.maxHeight=void 0,this.paddingTop=void 0,this.paddingBottom=void 0,this.paddingLeft=void 0,this.paddingRight=void 0,this.axis=void 0,this.labelRotation=void 0,this.min=void 0,this.max=void 0,this._range=void 0,this.ticks=[],this._gridLineItems=null,this._labelItems=null,this._labelSizes=null,this._length=0,this._maxLength=0,this._longestTextCache={},this._startPixel=void 0,this._endPixel=void 0,this._reversePixels=!1,this._userMax=void 0,this._userMin=void 0,this._suggestedMax=void 0,this._suggestedMin=void 0,this._ticksLength=0,this._borderValue=0,this._cache={},this._dataLimitsCached=!1,this.$context=void 0}init(t){this.options=t.setContext(this.getContext()),this.axis=t.axis,this._userMin=this.parse(t.min),this._userMax=this.parse(t.max),this._suggestedMin=this.parse(t.suggestedMin),this._suggestedMax=this.parse(t.suggestedMax)}parse(t,e){return t}getUserBounds(){let{_userMin:t,_userMax:e,_suggestedMin:i,_suggestedMax:s}=this;return t=q(t,Number.POSITIVE_INFINITY),e=q(e,Number.NEGATIVE_INFINITY),i=q(i,Number.POSITIVE_INFINITY),s=q(s,Number.NEGATIVE_INFINITY),{min:q(t,i),max:q(e,s),minDefined:X(t),maxDefined:X(e)}}getMinMax(t){let e,{min:i,max:s,minDefined:n,maxDefined:o}=this.getUserBounds();if(n&&o)return{min:i,max:s};const a=this.getMatchingVisibleMetas();for(let r=0,l=a.length;rs?s:i,s=n&&i>s?i:s,{min:q(i,q(s,i)),max:q(s,q(i,s))}}getPadding(){return{left:this.paddingLeft||0,top:this.paddingTop||0,right:this.paddingRight||0,bottom:this.paddingBottom||0}}getTicks(){return this.ticks}getLabels(){const t=this.chart.data;return this.options.labels||(this.isHorizontal()?t.xLabels:t.yLabels)||t.labels||[]}beforeLayout(){this._cache={},this._dataLimitsCached=!1}beforeUpdate(){J(this.options.beforeUpdate,[this])}update(t,e,i){const{beginAtZero:s,grace:n,ticks:o}=this.options,a=o.sampleSize;this.beforeUpdate(),this.maxWidth=t,this.maxHeight=e,this._margins=i=Object.assign({left:0,right:0,top:0,bottom:0},i),this.ticks=null,this._labelSizes=null,this._gridLineItems=null,this._labelItems=null,this.beforeSetDimensions(),this.setDimensions(),this.afterSetDimensions(),this._maxLength=this.isHorizontal()?this.width+i.left+i.right:this.height+i.top+i.bottom,this._dataLimitsCached||(this.beforeDataLimits(),this.determineDataLimits(),this.afterDataLimits(),this._range=$e(this,n,s),this._dataLimitsCached=!0),this.beforeBuildTicks(),this.ticks=this.buildTicks()||[],this.afterBuildTicks();const r=a=n||i<=1||!this.isHorizontal())return void(this.labelRotation=s);const h=this._getLabelSizes(),c=h.widest.width,d=h.highest.height,u=jt(this.chart.width-c,0,this.maxWidth);o=t.offset?this.maxWidth/i:u/(i-1),c+6>o&&(o=u/(i-(t.offset?.5:1)),a=this.maxHeight-Is(t.grid)-e.padding-zs(t.title,this.chart.options.font),r=Math.sqrt(c*c+d*d),l=zt(Math.min(Math.asin(jt((h.highest.height+6)/o,-1,1)),Math.asin(jt(a/r,-1,1))-Math.asin(jt(d/r,-1,1)))),l=Math.max(s,Math.min(n,l))),this.labelRotation=l}afterCalculateLabelRotation(){J(this.options.afterCalculateLabelRotation,[this])}beforeFit(){J(this.options.beforeFit,[this])}fit(){const t={width:0,height:0},{chart:e,options:{ticks:i,title:s,grid:n}}=this,o=this._isVisible(),a=this.isHorizontal();if(o){const o=zs(s,e.options.font);if(a?(t.width=this.maxWidth,t.height=Is(n)+o):(t.height=this.maxHeight,t.width=Is(n)+o),i.display&&this.ticks.length){const{first:e,last:s,widest:n,highest:o}=this._getLabelSizes(),r=2*i.padding,l=It(this.labelRotation),h=Math.cos(l),c=Math.sin(l);if(a){const e=i.mirror?0:c*n.width+h*o.height;t.height=Math.min(this.maxHeight,t.height+e+r)}else{const e=i.mirror?0:h*n.width+c*o.height;t.width=Math.min(this.maxWidth,t.width+e+r)}this._calculatePadding(e,s,c,h)}}this._handleMargins(),a?(this.width=this._length=e.width-this._margins.left-this._margins.right,this.height=t.height):(this.width=t.width,this.height=this._length=e.height-this._margins.top-this._margins.bottom)}_calculatePadding(t,e,i,s){const{ticks:{align:n,padding:o},position:a}=this.options,r=0!==this.labelRotation,l="top"!==a&&"x"===this.axis;if(this.isHorizontal()){const a=this.getPixelForTick(0)-this.left,h=this.right-this.getPixelForTick(this.ticks.length-1);let c=0,d=0;r?l?(c=s*t.width,d=i*e.height):(c=i*t.height,d=s*e.width):"start"===n?d=e.width:"end"===n?c=t.width:(c=t.width/2,d=e.width/2),this.paddingLeft=Math.max((c-a+o)*this.width/(this.width-a),0),this.paddingRight=Math.max((d-h+o)*this.width/(this.width-h),0)}else{let i=e.height/2,s=t.height/2;"start"===n?(i=0,s=t.height):"end"===n&&(i=e.height,s=0),this.paddingTop=i+o,this.paddingBottom=s+o}}_handleMargins(){this._margins&&(this._margins.left=Math.max(this.paddingLeft,this._margins.left),this._margins.top=Math.max(this.paddingTop,this._margins.top),this._margins.right=Math.max(this.paddingRight,this._margins.right),this._margins.bottom=Math.max(this.paddingBottom,this._margins.bottom))}afterFit(){J(this.options.afterFit,[this])}isHorizontal(){const{axis:t,position:e}=this.options;return"top"===e||"bottom"===e||"x"===t}isFullSize(){return this.options.fullSize}_convertTicksToLabels(t){let e,i;for(this.beforeTickToLabelConversion(),this.generateTickLabels(t),e=0,i=t.length;e{const i=t.gc,s=i.length/2;let n;if(s>e){for(n=0;n({width:n[t]||0,height:o[t]||0});return{first:v(0),last:v(e-1),widest:v(_),highest:v(y),widths:n,heights:o}}getLabelForValue(t){return t}getPixelForValue(t,e){return NaN}getValueForPixel(t){}getPixelForTick(t){const e=this.ticks;return t<0||t>e.length-1?null:this.getPixelForValue(e[t].value)}getPixelForDecimal(t){this._reversePixels&&(t=1-t);const e=this._startPixel+t*this._length;return $t(this._alignToPixels?Kt(this.chart,e,0):e)}getDecimalForPixel(t){const e=(t-this._startPixel)/this._length;return this._reversePixels?1-e:e}getBasePixel(){return this.getPixelForValue(this.getBaseValue())}getBaseValue(){const{min:t,max:e}=this;return t<0&&e<0?e:t>0&&e>0?t:0}getContext(t){const e=this.ticks||[];if(t>=0&&ta*s?a/i:r/s:r*s0}_computeGridLineItems(t){const e=this.axis,i=this.chart,s=this.options,{grid:n,position:o}=s,a=n.offset,r=this.isHorizontal(),l=this.ticks.length+(a?1:0),h=Is(n),c=[],d=n.setContext(this.getContext()),u=d.drawBorder?d.borderWidth:0,f=u/2,g=function(t){return Kt(i,t,u)};let p,m,x,b,_,y,v,w,M,k,S,P;if("top"===o)p=g(this.bottom),y=this.bottom-h,w=p-f,k=g(t.top)+f,P=t.bottom;else if("bottom"===o)p=g(this.top),k=t.top,P=g(t.bottom)-f,y=p+f,w=this.top+h;else if("left"===o)p=g(this.right),_=this.right-h,v=p-f,M=g(t.left)+f,S=t.right;else if("right"===o)p=g(this.left),M=t.left,S=g(t.right)-f,_=p+f,v=this.left+h;else if("x"===e){if("center"===o)p=g((t.top+t.bottom)/2+.5);else if(U(o)){const t=Object.keys(o)[0],e=o[t];p=g(this.chart.scales[t].getPixelForValue(e))}k=t.top,P=t.bottom,y=p+f,w=y+h}else if("y"===e){if("center"===o)p=g((t.left+t.right)/2);else if(U(o)){const t=Object.keys(o)[0],e=o[t];p=g(this.chart.scales[t].getPixelForValue(e))}_=p-f,v=_-h,M=t.left,S=t.right}const D=K(s.ticks.maxTicksLimit,l),C=Math.max(1,Math.ceil(l/D));for(m=0;me.value===t));if(i>=0){return e.setContext(this.getContext(i)).lineWidth}return 0}drawGrid(t){const e=this.options.grid,i=this.ctx,s=this._gridLineItems||(this._gridLineItems=this._computeGridLineItems(t));let n,o;const a=(t,e,s)=>{s.width&&s.color&&(i.save(),i.lineWidth=s.width,i.strokeStyle=s.color,i.setLineDash(s.borderDash||[]),i.lineDashOffset=s.borderDashOffset,i.beginPath(),i.moveTo(t.x,t.y),i.lineTo(e.x,e.y),i.stroke(),i.restore())};if(e.display)for(n=0,o=s.length;n{this.drawBackground(),this.drawGrid(t),this.drawTitle()}},{z:i+1,draw:()=>{this.drawBorder()}},{z:e,draw:t=>{this.drawLabels(t)}}]:[{z:e,draw:t=>{this.draw(t)}}]}getMatchingVisibleMetas(t){const e=this.chart.getSortedVisibleDatasetMetas(),i=this.axis+"AxisID",s=[];let n,o;for(n=0,o=e.length;n{const s=i.split("."),n=s.pop(),o=[t].concat(s).join("."),a=e[i].split("."),r=a.pop(),l=a.join(".");bt.route(o,n,l,r)}))}(e,t.defaultRoutes);t.descriptors&&bt.describe(e,t.descriptors)}(t,o,i),this.override&&bt.override(t.id,t.overrides)),o}get(t){return this.items[t]}unregister(t){const e=this.items,i=t.id,s=this.scope;i in e&&delete e[i],s&&i in bt[s]&&(delete bt[s][i],this.override&&delete gt[i])}}var Ws=new class{constructor(){this.controllers=new Vs(Ps,"datasets",!0),this.elements=new Vs(Ds,"elements"),this.plugins=new Vs(Object,"plugins"),this.scales=new Vs(Bs,"scales"),this._typedRegistries=[this.controllers,this.scales,this.elements]}add(...t){this._each("register",t)}remove(...t){this._each("unregister",t)}addControllers(...t){this._each("register",t,this.controllers)}addElements(...t){this._each("register",t,this.elements)}addPlugins(...t){this._each("register",t,this.plugins)}addScales(...t){this._each("register",t,this.scales)}getController(t){return this._get(t,this.controllers,"controller")}getElement(t){return this._get(t,this.elements,"element")}getPlugin(t){return this._get(t,this.plugins,"plugin")}getScale(t){return this._get(t,this.scales,"scale")}removeControllers(...t){this._each("unregister",t,this.controllers)}removeElements(...t){this._each("unregister",t,this.elements)}removePlugins(...t){this._each("unregister",t,this.plugins)}removeScales(...t){this._each("unregister",t,this.scales)}_each(t,e,i){[...e].forEach((e=>{const s=i||this._getRegistryForType(e);i||s.isForType(e)||s===this.plugins&&e.id?this._exec(t,s,e):Q(e,(e=>{const s=i||this._getRegistryForType(e);this._exec(t,s,e)}))}))}_exec(t,e,i){const s=ht(t);J(i["before"+s],[],i),e[t](i),J(i["after"+s],[],i)}_getRegistryForType(t){for(let e=0;et.filter((t=>!e.some((e=>t.plugin.id===e.plugin.id))));this._notify(s(e,i),t,"stop"),this._notify(s(i,e),t,"start")}}function Hs(t,e){return e||!1!==t?!0===t?{}:t:null}function js(t,e,i,s){const n=t.pluginScopeKeys(e),o=t.getOptionScopes(i,n);return t.createResolver(o,s,[""],{scriptable:!1,indexable:!1,allKeys:!0})}function $s(t,e){const i=bt.datasets[t]||{};return((e.datasets||{})[t]||{}).indexAxis||e.indexAxis||i.indexAxis||"x"}function Ys(t,e){return"x"===t||"y"===t?t:e.axis||("top"===(i=e.position)||"bottom"===i?"x":"left"===i||"right"===i?"y":void 0)||t.charAt(0).toLowerCase();var i}function Us(t){const e=t.options||(t.options={});e.plugins=K(e.plugins,{}),e.scales=function(t,e){const i=gt[t.type]||{scales:{}},s=e.scales||{},n=$s(t.type,e),o=Object.create(null),a=Object.create(null);return Object.keys(s).forEach((t=>{const e=s[t];if(!U(e))return console.error(`Invalid scale configuration for scale: ${t}`);if(e._proxy)return console.warn(`Ignoring resolver passed as options for scale: ${t}`);const r=Ys(t,e),l=function(t,e){return t===e?"_index_":"_value_"}(r,n),h=i.scales||{};o[r]=o[r]||t,a[t]=ot(Object.create(null),[{axis:r},e,h[r],h[l]])})),t.data.datasets.forEach((i=>{const n=i.type||t.type,r=i.indexAxis||$s(n,e),l=(gt[n]||{}).scales||{};Object.keys(l).forEach((t=>{const e=function(t,e){let i=t;return"_index_"===t?i=e:"_value_"===t&&(i="x"===e?"y":"x"),i}(t,r),n=i[e+"AxisID"]||o[e]||e;a[n]=a[n]||Object.create(null),ot(a[n],[{axis:e},s[n],l[t]])}))})),Object.keys(a).forEach((t=>{const e=a[t];ot(e,[bt.scales[e.type],bt.scale])})),a}(t,e)}function Xs(t){return(t=t||{}).datasets=t.datasets||[],t.labels=t.labels||[],t}const qs=new Map,Ks=new Set;function Gs(t,e){let i=qs.get(t);return i||(i=e(),qs.set(t,i),Ks.add(i)),i}const Zs=(t,e,i)=>{const s=lt(e,i);void 0!==s&&t.add(s)};class Js{constructor(t){this._config=function(t){return(t=t||{}).data=Xs(t.data),Us(t),t}(t),this._scopeCache=new Map,this._resolverCache=new Map}get platform(){return this._config.platform}get type(){return this._config.type}set type(t){this._config.type=t}get data(){return this._config.data}set data(t){this._config.data=Xs(t)}get options(){return this._config.options}set options(t){this._config.options=t}get plugins(){return this._config.plugins}update(){const t=this._config;this.clearCache(),Us(t)}clearCache(){this._scopeCache.clear(),this._resolverCache.clear()}datasetScopeKeys(t){return Gs(t,(()=>[[`datasets.${t}`,""]]))}datasetAnimationScopeKeys(t,e){return Gs(`${t}.transition.${e}`,(()=>[[`datasets.${t}.transitions.${e}`,`transitions.${e}`],[`datasets.${t}`,""]]))}datasetElementScopeKeys(t,e){return Gs(`${t}-${e}`,(()=>[[`datasets.${t}.elements.${e}`,`datasets.${t}`,`elements.${e}`,""]]))}pluginScopeKeys(t){const e=t.id;return Gs(`${this.type}-plugin-${e}`,(()=>[[`plugins.${e}`,...t.additionalOptionScopes||[]]]))}_cachedScopes(t,e){const i=this._scopeCache;let s=i.get(t);return s&&!e||(s=new Map,i.set(t,s)),s}getOptionScopes(t,e,i){const{options:s,type:n}=this,o=this._cachedScopes(t,i),a=o.get(e);if(a)return a;const r=new Set;e.forEach((e=>{t&&(r.add(t),e.forEach((e=>Zs(r,t,e)))),e.forEach((t=>Zs(r,s,t))),e.forEach((t=>Zs(r,gt[n]||{},t))),e.forEach((t=>Zs(r,bt,t))),e.forEach((t=>Zs(r,pt,t)))}));const l=Array.from(r);return 0===l.length&&l.push(Object.create(null)),Ks.has(e)&&o.set(e,l),l}chartOptionScopes(){const{options:t,type:e}=this;return[t,gt[e]||{},bt.datasets[e]||{},{type:e},bt,pt]}resolveNamedOptions(t,e,i,s=[""]){const n={$shared:!0},{resolver:o,subPrefixes:a}=Qs(this._resolverCache,t,s);let r=o;if(function(t,e){const{isScriptable:i,isIndexable:s}=ri(t);for(const n of e){const e=i(n),o=s(n),a=(o||e)&&t[n];if(e&&(dt(a)||tn(a))||o&&Y(a))return!0}return!1}(o,e)){n.$shared=!1;r=ai(o,i=dt(i)?i():i,this.createResolver(t,i,a))}for(const t of e)n[t]=r[t];return n}createResolver(t,e,i=[""],s){const{resolver:n}=Qs(this._resolverCache,t,i);return U(e)?ai(n,e,void 0,s):n}}function Qs(t,e,i){let s=t.get(e);s||(s=new Map,t.set(e,s));const n=i.join();let o=s.get(n);if(!o){o={resolver:oi(e,i),subPrefixes:i.filter((t=>!t.toLowerCase().includes("hover")))},s.set(n,o)}return o}const tn=t=>U(t)&&Object.getOwnPropertyNames(t).reduce(((e,i)=>e||dt(t[i])),!1);const en=["top","bottom","left","right","chartArea"];function sn(t,e){return"top"===t||"bottom"===t||-1===en.indexOf(t)&&"x"===e}function nn(t,e){return function(i,s){return i[t]===s[t]?i[e]-s[e]:i[t]-s[t]}}function on(t){const e=t.chart,i=e.options.animation;e.notifyPlugins("afterRender"),J(i&&i.onComplete,[t],e)}function an(t){const e=t.chart,i=e.options.animation;J(i&&i.onProgress,[t],e)}function rn(t){return ge()&&"string"==typeof t?t=document.getElementById(t):t&&t.length&&(t=t[0]),t&&t.canvas&&(t=t.canvas),t}const ln={},hn=t=>{const e=rn(t);return Object.values(ln).filter((t=>t.canvas===e)).pop()};function cn(t,e,i){const s=Object.keys(t);for(const n of s){const s=+n;if(s>=e){const o=t[n];delete t[n],(i>0||s>e)&&(t[s+i]=o)}}}class dn{constructor(t,e){const s=this.config=new Js(e),n=rn(t),o=hn(n);if(o)throw new Error("Canvas is already in use. Chart with ID '"+o.id+"' must be destroyed before the canvas can be reused.");const r=s.createResolver(s.chartOptionScopes(),this.getContext());this.platform=new(s.platform||ls(n)),this.platform.updateConfig(s);const l=this.platform.acquireContext(n,r.aspectRatio),h=l&&l.canvas,c=h&&h.height,d=h&&h.width;this.id=j(),this.ctx=l,this.canvas=h,this.width=d,this.height=c,this._options=r,this._aspectRatio=this.aspectRatio,this._layers=[],this._metasets=[],this._stacks=void 0,this.boxes=[],this.currentDevicePixelRatio=void 0,this.chartArea=void 0,this._active=[],this._lastEvent=void 0,this._listeners={},this._responsiveListeners=void 0,this._sortedMetasets=[],this.scales={},this._plugins=new Ns,this.$proxies={},this._hiddenIndices={},this.attached=!1,this._animationsDisabled=void 0,this.$context=void 0,this._doResize=i((t=>this.update(t)),r.resizeDelay||0),this._dataChanges=[],ln[this.id]=this,l&&h?(a.listen(this,"complete",on),a.listen(this,"progress",an),this._initialize(),this.attached&&this.update()):console.error("Failed to create chart: can't acquire context from the given item")}get aspectRatio(){const{options:{aspectRatio:t,maintainAspectRatio:e},width:i,height:s,_aspectRatio:n}=this;return $(t)?e&&n?n:s?i/s:null:t}get data(){return this.config.data}set data(t){this.config.data=t}get options(){return this._options}set options(t){this.config.options=t}_initialize(){return this.notifyPlugins("beforeInit"),this.options.responsive?this.resize():ke(this,this.options.devicePixelRatio),this.bindEvents(),this.notifyPlugins("afterInit"),this}clear(){return Gt(this.canvas,this.ctx),this}stop(){return a.stop(this),this}resize(t,e){a.running(this)?this._resizeBeforeDraw={width:t,height:e}:this._resize(t,e)}_resize(t,e){const i=this.options,s=this.canvas,n=i.maintainAspectRatio&&this.aspectRatio,o=this.platform.getMaximumSize(s,t,e,n),a=i.devicePixelRatio||this.platform.getDevicePixelRatio(),r=this.width?"resize":"attach";this.width=o.width,this.height=o.height,this._aspectRatio=this.aspectRatio,ke(this,a,!0)&&(this.notifyPlugins("resize",{size:o}),J(i.onResize,[this,o],this),this.attached&&this._doResize(r)&&this.render())}ensureScalesHaveIDs(){Q(this.options.scales||{},((t,e)=>{t.id=e}))}buildOrUpdateScales(){const t=this.options,e=t.scales,i=this.scales,s=Object.keys(i).reduce(((t,e)=>(t[e]=!1,t)),{});let n=[];e&&(n=n.concat(Object.keys(e).map((t=>{const i=e[t],s=Ys(t,i),n="r"===s,o="x"===s;return{options:i,dposition:n?"chartArea":o?"bottom":"left",dtype:n?"radialLinear":o?"category":"linear"}})))),Q(n,(e=>{const n=e.options,o=n.id,a=Ys(o,n),r=K(n.type,e.dtype);void 0!==n.position&&sn(n.position,a)===sn(e.dposition)||(n.position=e.dposition),s[o]=!0;let l=null;if(o in i&&i[o].type===r)l=i[o];else{l=new(Ws.getScale(r))({id:o,type:r,ctx:this.ctx,chart:this}),i[l.id]=l}l.init(n,t)})),Q(s,((t,e)=>{t||delete i[e]})),Q(i,(t=>{ni.configure(this,t,t.options),ni.addBox(this,t)}))}_updateMetasets(){const t=this._metasets,e=this.data.datasets.length,i=t.length;if(t.sort(((t,e)=>t.index-e.index)),i>e){for(let t=e;te.length&&delete this._stacks,t.forEach(((t,i)=>{0===e.filter((e=>e===t._dataset)).length&&this._destroyDatasetMeta(i)}))}buildOrUpdateControllers(){const t=[],e=this.data.datasets;let i,s;for(this._removeUnreferencedMetasets(),i=0,s=e.length;i{this.getDatasetMeta(e).controller.reset()}),this)}reset(){this._resetElements(),this.notifyPlugins("reset")}update(t){const e=this.config;e.update();const i=this._options=e.createResolver(e.chartOptionScopes(),this.getContext()),s=this._animationsDisabled=!i.animation;if(this._updateScales(),this._checkEventBindings(),this._updateHiddenIndices(),this._plugins.invalidate(),!1===this.notifyPlugins("beforeUpdate",{mode:t,cancelable:!0}))return;const n=this.buildOrUpdateControllers();this.notifyPlugins("beforeElementsUpdate");let o=0;for(let t=0,e=this.data.datasets.length;t{t.reset()})),this._updateDatasets(t),this.notifyPlugins("afterUpdate",{mode:t}),this._layers.sort(nn("z","_idx"));const{_active:a,_lastEvent:r}=this;r?this._eventHandler(r,!0):a.length&&this._updateHoverStyles(a,a,!0),this.render()}_updateScales(){Q(this.scales,(t=>{ni.removeBox(this,t)})),this.ensureScalesHaveIDs(),this.buildOrUpdateScales()}_checkEventBindings(){const t=this.options,e=new Set(Object.keys(this._listeners)),i=new Set(t.events);ut(e,i)&&!!this._responsiveListeners===t.responsive||(this.unbindEvents(),this.bindEvents())}_updateHiddenIndices(){const{_hiddenIndices:t}=this,e=this._getUniformDataChanges()||[];for(const{method:i,start:s,count:n}of e){cn(t,s,"_removeElements"===i?-n:n)}}_getUniformDataChanges(){const t=this._dataChanges;if(!t||!t.length)return;this._dataChanges=[];const e=this.data.datasets.length,i=e=>new Set(t.filter((t=>t[0]===e)).map(((t,e)=>e+","+t.splice(1).join(",")))),s=i(0);for(let t=1;tt.split(","))).map((t=>({method:t[1],start:+t[2],count:+t[3]})))}_updateLayout(t){if(!1===this.notifyPlugins("beforeLayout",{cancelable:!0}))return;ni.update(this,this.width,this.height,t);const e=this.chartArea,i=e.width<=0||e.height<=0;this._layers=[],Q(this.boxes,(t=>{i&&"chartArea"===t.position||(t.configure&&t.configure(),this._layers.push(...t._layers()))}),this),this._layers.forEach(((t,e)=>{t._idx=e})),this.notifyPlugins("afterLayout")}_updateDatasets(t){if(!1!==this.notifyPlugins("beforeDatasetsUpdate",{mode:t,cancelable:!0})){for(let t=0,e=this.data.datasets.length;t=0;--e)this._drawDataset(t[e]);this.notifyPlugins("afterDatasetsDraw")}_drawDataset(t){const e=this.ctx,i=t._clip,s=!i.disabled,n=this.chartArea,o={meta:t,index:t.index,cancelable:!0};!1!==this.notifyPlugins("beforeDatasetDraw",o)&&(s&&Qt(e,{left:!1===i.left?0:n.left-i.left,right:!1===i.right?this.width:n.right+i.right,top:!1===i.top?0:n.top-i.top,bottom:!1===i.bottom?this.height:n.bottom+i.bottom}),t.controller.draw(),s&&te(e),o.cancelable=!1,this.notifyPlugins("afterDatasetDraw",o))}getElementsAtEventForMode(t,e,i,s){const n=Ee.modes[e];return"function"==typeof n?n(this,t,i,s):[]}getDatasetMeta(t){const e=this.data.datasets[t],i=this._metasets;let s=i.filter((t=>t&&t._dataset===e)).pop();return s||(s={type:null,data:[],dataset:null,controller:null,hidden:null,xAxisID:null,yAxisID:null,order:e&&e.order||0,index:t,_dataset:e,_parsed:[],_sorted:!1},i.push(s)),s}getContext(){return this.$context||(this.$context=Ye(null,{chart:this,type:"chart"}))}getVisibleDatasetCount(){return this.getSortedVisibleDatasetMetas().length}isDatasetVisible(t){const e=this.data.datasets[t];if(!e)return!1;const i=this.getDatasetMeta(t);return"boolean"==typeof i.hidden?!i.hidden:!e.hidden}setDatasetVisibility(t,e){this.getDatasetMeta(t).hidden=!e}toggleDataVisibility(t){this._hiddenIndices[t]=!this._hiddenIndices[t]}getDataVisibility(t){return!this._hiddenIndices[t]}_updateVisibility(t,e,i){const s=i?"show":"hide",n=this.getDatasetMeta(t),o=n.controller._resolveAnimations(void 0,s);ct(e)?(n.data[e].hidden=!i,this.update()):(this.setDatasetVisibility(t,i),o.update(n,{visible:i}),this.update((e=>e.datasetIndex===t?s:void 0)))}hide(t,e){this._updateVisibility(t,e,!1)}show(t,e){this._updateVisibility(t,e,!0)}_destroyDatasetMeta(t){const e=this._metasets[t];e&&e.controller&&e.controller._destroy(),delete this._metasets[t]}_stop(){let t,e;for(this.stop(),a.remove(this),t=0,e=this.data.datasets.length;t{e.addEventListener(this,i,s),t[i]=s},s=(t,e,i)=>{t.offsetX=e,t.offsetY=i,this._eventHandler(t)};Q(this.options.events,(t=>i(t,s)))}bindResponsiveEvents(){this._responsiveListeners||(this._responsiveListeners={});const t=this._responsiveListeners,e=this.platform,i=(i,s)=>{e.addEventListener(this,i,s),t[i]=s},s=(i,s)=>{t[i]&&(e.removeEventListener(this,i,s),delete t[i])},n=(t,e)=>{this.canvas&&this.resize(t,e)};let o;const a=()=>{s("attach",a),this.attached=!0,this.resize(),i("resize",n),i("detach",o)};o=()=>{this.attached=!1,s("resize",n),this._stop(),this._resize(0,0),i("attach",a)},e.isAttached(this.canvas)?a():o()}unbindEvents(){Q(this._listeners,((t,e)=>{this.platform.removeEventListener(this,e,t)})),this._listeners={},Q(this._responsiveListeners,((t,e)=>{this.platform.removeEventListener(this,e,t)})),this._responsiveListeners=void 0}updateHoverStyle(t,e,i){const s=i?"set":"remove";let n,o,a,r;for("dataset"===e&&(n=this.getDatasetMeta(t[0].datasetIndex),n.controller["_"+s+"DatasetHoverStyle"]()),a=0,r=t.length;a{const i=this.getDatasetMeta(t);if(!i)throw new Error("No dataset found at index "+t);return{datasetIndex:t,element:i.data[e],index:e}}));!tt(i,e)&&(this._active=i,this._lastEvent=null,this._updateHoverStyles(i,e))}notifyPlugins(t,e,i){return this._plugins.notify(this,t,e,i)}_updateHoverStyles(t,e,i){const s=this.options.hover,n=(t,e)=>t.filter((t=>!e.some((e=>t.datasetIndex===e.datasetIndex&&t.index===e.index)))),o=n(e,t),a=i?t:n(t,e);o.length&&this.updateHoverStyle(o,s.mode,!1),a.length&&s.mode&&this.updateHoverStyle(a,s.mode,!0)}_eventHandler(t,e){const i={event:t,replay:e,cancelable:!0,inChartArea:Jt(t,this.chartArea,this._minPadding)},s=e=>(e.options.events||this.options.events).includes(t.native.type);if(!1===this.notifyPlugins("beforeEvent",i,s))return;const n=this._handleEvent(t,e,i.inChartArea);return i.cancelable=!1,this.notifyPlugins("afterEvent",i,s),(n||i.changed)&&this.render(),this}_handleEvent(t,e,i){const{_active:s=[],options:n}=this,o=e,a=this._getActiveElements(t,s,i,o),r=ft(t),l=function(t,e,i,s){return i&&"mouseout"!==t.type?s?e:t:null}(t,this._lastEvent,i,r);i&&(this._lastEvent=null,J(n.onHover,[t,a,this],this),r&&J(n.onClick,[t,a,this],this));const h=!tt(a,s);return(h||e)&&(this._active=a,this._updateHoverStyles(a,s,e)),this._lastEvent=l,h}_getActiveElements(t,e,i,s){if("mouseout"===t.type)return[];if(!i)return e;const n=this.options.hover;return this.getElementsAtEventForMode(t,n.mode,n,s)}}const un=()=>Q(dn.instances,(t=>t._plugins.invalidate())),fn=!0;function gn(){throw new Error("This method is not implemented: Check that a complete date adapter is provided.")}Object.defineProperties(dn,{defaults:{enumerable:fn,value:bt},instances:{enumerable:fn,value:ln},overrides:{enumerable:fn,value:gt},registry:{enumerable:fn,value:Ws},version:{enumerable:fn,value:"3.7.1"},getChart:{enumerable:fn,value:hn},register:{enumerable:fn,value:(...t)=>{Ws.add(...t),un()}},unregister:{enumerable:fn,value:(...t)=>{Ws.remove(...t),un()}}});class pn{constructor(t){this.options=t||{}}formats(){return gn()}parse(t,e){return gn()}format(t,e){return gn()}add(t,e,i){return gn()}diff(t,e,i){return gn()}startOf(t,e,i){return gn()}endOf(t,e){return gn()}}pn.override=function(t){Object.assign(pn.prototype,t)};var mn={_date:pn};function xn(t){const e=t.iScale,i=function(t,e){if(!t._cache.$bar){const i=t.getMatchingVisibleMetas(e);let s=[];for(let e=0,n=i.length;et-e)))}return t._cache.$bar}(e,t.type);let s,n,o,a,r=e._length;const l=()=>{32767!==o&&-32768!==o&&(ct(a)&&(r=Math.min(r,Math.abs(o-a)||r)),a=o)};for(s=0,n=i.length;sMath.abs(r)&&(l=r,h=a),e[i.axis]=h,e._custom={barStart:l,barEnd:h,start:n,end:o,min:a,max:r}}(t,e,i,s):e[i.axis]=i.parse(t,s),e}function _n(t,e,i,s){const n=t.iScale,o=t.vScale,a=n.getLabels(),r=n===o,l=[];let h,c,d,u;for(h=i,c=i+s;ht.x,i="left",s="right"):(e=t.base=i?1:-1)}(c,e,o)*n,d===o&&(p-=c/2),h=p+c),p===e.getPixelForValue(o)){const t=Ct(c)*e.getLineWidthForValue(o)/2;p+=t,c-=t}return{size:c,base:p,head:h,center:h+c/2}}_calculateBarIndexPixels(t,e){const i=e.scale,s=this.options,n=s.skipNull,o=K(s.maxBarThickness,1/0);let a,r;if(e.grouped){const i=n?this._getStackCount(t):e.stackCount,l="flex"===s.barThickness?function(t,e,i,s){const n=e.pixels,o=n[t];let a=t>0?n[t-1]:null,r=t=0;--i)e=Math.max(e,t[i].size(this.resolveDataElementOptions(i))/2);return e>0&&e}getLabelAndValue(t){const e=this._cachedMeta,{xScale:i,yScale:s}=e,n=this.getParsed(t),o=i.getLabelForValue(n.x),a=s.getLabelForValue(n.y),r=n._custom;return{label:e.label,value:"("+o+", "+a+(r?", "+r:"")+")"}}update(t){const e=this._cachedMeta.data;this.updateElements(e,0,e.length,t)}updateElements(t,e,i,s){const n="reset"===s,{iScale:o,vScale:a}=this._cachedMeta,r=this.resolveDataElementOptions(e,s),l=this.getSharedOptions(r),h=this.includeOptions(s,l),c=o.axis,d=a.axis;for(let r=e;r""}}}};class Dn extends Ps{constructor(t,e){super(t,e),this.enableOptionSharing=!0,this.innerRadius=void 0,this.outerRadius=void 0,this.offsetX=void 0,this.offsetY=void 0}linkScales(){}parse(t,e){const i=this.getDataset().data,s=this._cachedMeta;if(!1===this._parsing)s._parsed=i;else{let n,o,a=t=>+i[t];if(U(i[t])){const{key:t="value"}=this._parsing;a=e=>+lt(i[e],t)}for(n=t,o=t+e;nHt(t,r,l,!0)?1:Math.max(e,e*i,s,s*i),g=(t,e,s)=>Ht(t,r,l,!0)?-1:Math.min(e,e*i,s,s*i),p=f(0,h,d),m=f(kt,c,u),x=g(_t,h,d),b=g(_t+kt,c,u);s=(p-x)/2,n=(m-b)/2,o=-(p+x)/2,a=-(m+b)/2}return{ratioX:s,ratioY:n,offsetX:o,offsetY:a}}(c,h,r),p=(i.width-o)/d,m=(i.height-o)/u,x=Math.max(Math.min(p,m)/2,0),b=Z(this.options.radius,x),_=(b-Math.max(b*r,0))/this._getVisibleDatasetWeightTotal();this.offsetX=f*b,this.offsetY=g*b,s.total=this.calculateTotal(),this.outerRadius=b-_*this._getRingWeightOffset(this.index),this.innerRadius=Math.max(this.outerRadius-_*l,0),this.updateElements(n,0,n.length,t)}_circumference(t,e){const i=this.options,s=this._cachedMeta,n=this._getCircumference();return e&&i.animation.animateRotate||!this.chart.getDataVisibility(t)||null===s._parsed[t]||s.data[t].hidden?0:this.calculateCircumference(s._parsed[t]*n/yt)}updateElements(t,e,i,s){const n="reset"===s,o=this.chart,a=o.chartArea,r=o.options.animation,l=(a.left+a.right)/2,h=(a.top+a.bottom)/2,c=n&&r.animateScale,d=c?0:this.innerRadius,u=c?0:this.outerRadius,f=this.resolveDataElementOptions(e,s),g=this.getSharedOptions(f),p=this.includeOptions(s,g);let m,x=this._getRotation();for(m=0;m0&&!isNaN(t)?yt*(Math.abs(t)/e):0}getLabelAndValue(t){const e=this._cachedMeta,i=this.chart,s=i.data.labels||[],n=Ri(e._parsed[t],i.options.locale);return{label:s[t]||"",value:n}}getMaxBorderWidth(t){let e=0;const i=this.chart;let s,n,o,a,r;if(!t)for(s=0,n=i.data.datasets.length;s"spacing"!==t,_indexable:t=>"spacing"!==t},Dn.overrides={aspectRatio:1,plugins:{legend:{labels:{generateLabels(t){const e=t.data;if(e.labels.length&&e.datasets.length){const{labels:{pointStyle:i}}=t.legend.options;return e.labels.map(((e,s)=>{const n=t.getDatasetMeta(0).controller.getStyle(s);return{text:e,fillStyle:n.backgroundColor,strokeStyle:n.borderColor,lineWidth:n.borderWidth,pointStyle:i,hidden:!t.getDataVisibility(s),index:s}}))}return[]}},onClick(t,e,i){i.chart.toggleDataVisibility(e.index),i.chart.update()}},tooltip:{callbacks:{title:()=>"",label(t){let e=t.label;const i=": "+t.formattedValue;return Y(e)?(e=e.slice(),e[0]+=i):e+=i,e}}}}};class Cn extends Ps{initialize(){this.enableOptionSharing=!0,super.initialize()}update(t){const e=this._cachedMeta,{dataset:i,data:s=[],_dataset:n}=e,o=this.chart._animationsDisabled;let{start:a,count:r}=function(t,e,i){const s=e.length;let n=0,o=s;if(t._sorted){const{iScale:a,_parsed:r}=t,l=a.axis,{min:h,max:c,minDefined:d,maxDefined:u}=a.getUserBounds();d&&(n=jt(Math.min(re(r,a.axis,h).lo,i?s:re(e,l,a.getPixelForValue(h)).lo),0,s-1)),o=u?jt(Math.max(re(r,a.axis,c).hi+1,i?0:re(e,l,a.getPixelForValue(c)).hi+1),n,s)-n:s-n}return{start:n,count:o}}(e,s,o);this._drawStart=a,this._drawCount=r,function(t){const{xScale:e,yScale:i,_scaleRanges:s}=t,n={xmin:e.min,xmax:e.max,ymin:i.min,ymax:i.max};if(!s)return t._scaleRanges=n,!0;const o=s.xmin!==e.min||s.xmax!==e.max||s.ymin!==i.min||s.ymax!==i.max;return Object.assign(s,n),o}(e)&&(a=0,r=s.length),i._chart=this.chart,i._datasetIndex=this.index,i._decimated=!!n._decimated,i.points=s;const l=this.resolveDatasetElementOptions(t);this.options.showLine||(l.borderWidth=0),l.segment=this.options.segment,this.updateElement(i,void 0,{animated:!o,options:l},t),this.updateElements(s,a,r,t)}updateElements(t,e,i,s){const n="reset"===s,{iScale:o,vScale:a,_stacked:r,_dataset:l}=this._cachedMeta,h=this.resolveDataElementOptions(e,s),c=this.getSharedOptions(h),d=this.includeOptions(s,c),u=o.axis,f=a.axis,{spanGaps:g,segment:p}=this.options,m=Tt(g)?g:Number.POSITIVE_INFINITY,x=this.chart._animationsDisabled||n||"none"===s;let b=e>0&&this.getParsed(e-1);for(let h=e;h0&&i[u]-b[u]>m,p&&(g.parsed=i,g.raw=l.data[h]),d&&(g.options=c||this.resolveDataElementOptions(h,e.active?"active":s)),x||this.updateElement(e,h,g,s),b=i}this.updateSharedOptions(c,s,h)}getMaxOverflow(){const t=this._cachedMeta,e=t.dataset,i=e.options&&e.options.borderWidth||0,s=t.data||[];if(!s.length)return i;const n=s[0].size(this.resolveDataElementOptions(0)),o=s[s.length-1].size(this.resolveDataElementOptions(s.length-1));return Math.max(i,n,o)/2}draw(){const t=this._cachedMeta;t.dataset.updateControlPoints(this.chart.chartArea,t.iScale.axis),super.draw()}}Cn.id="line",Cn.defaults={datasetElementType:"line",dataElementType:"point",showLine:!0,spanGaps:!1},Cn.overrides={scales:{_index_:{type:"category"},_value_:{type:"linear"}}};class On extends Ps{constructor(t,e){super(t,e),this.innerRadius=void 0,this.outerRadius=void 0}getLabelAndValue(t){const e=this._cachedMeta,i=this.chart,s=i.data.labels||[],n=Ri(e._parsed[t].r,i.options.locale);return{label:s[t]||"",value:n}}update(t){const e=this._cachedMeta.data;this._updateRadius(),this.updateElements(e,0,e.length,t)}_updateRadius(){const t=this.chart,e=t.chartArea,i=t.options,s=Math.min(e.right-e.left,e.bottom-e.top),n=Math.max(s/2,0),o=(n-Math.max(i.cutoutPercentage?n/100*i.cutoutPercentage:1,0))/t.getVisibleDatasetCount();this.outerRadius=n-o*this.index,this.innerRadius=this.outerRadius-o}updateElements(t,e,i,s){const n="reset"===s,o=this.chart,a=this.getDataset(),r=o.options.animation,l=this._cachedMeta.rScale,h=l.xCenter,c=l.yCenter,d=l.getIndexAngle(0)-.5*_t;let u,f=d;const g=360/this.countVisibleElements();for(u=0;u{!isNaN(t.data[s])&&this.chart.getDataVisibility(s)&&i++})),i}_computeAngle(t,e,i){return this.chart.getDataVisibility(t)?It(this.resolveDataElementOptions(t,e).angle||i):0}}On.id="polarArea",On.defaults={dataElementType:"arc",animation:{animateRotate:!0,animateScale:!0},animations:{numbers:{type:"number",properties:["x","y","startAngle","endAngle","innerRadius","outerRadius"]}},indexAxis:"r",startAngle:0},On.overrides={aspectRatio:1,plugins:{legend:{labels:{generateLabels(t){const e=t.data;if(e.labels.length&&e.datasets.length){const{labels:{pointStyle:i}}=t.legend.options;return e.labels.map(((e,s)=>{const n=t.getDatasetMeta(0).controller.getStyle(s);return{text:e,fillStyle:n.backgroundColor,strokeStyle:n.borderColor,lineWidth:n.borderWidth,pointStyle:i,hidden:!t.getDataVisibility(s),index:s}}))}return[]}},onClick(t,e,i){i.chart.toggleDataVisibility(e.index),i.chart.update()}},tooltip:{callbacks:{title:()=>"",label:t=>t.chart.data.labels[t.dataIndex]+": "+t.formattedValue}}},scales:{r:{type:"radialLinear",angleLines:{display:!1},beginAtZero:!0,grid:{circular:!0},pointLabels:{display:!1},startAngle:0}}};class An extends Dn{}An.id="pie",An.defaults={cutout:0,rotation:0,circumference:360,radius:"100%"};class Tn extends Ps{getLabelAndValue(t){const e=this._cachedMeta.vScale,i=this.getParsed(t);return{label:e.getLabels()[t],value:""+e.getLabelForValue(i[e.axis])}}update(t){const e=this._cachedMeta,i=e.dataset,s=e.data||[],n=e.iScale.getLabels();if(i.points=s,"resize"!==t){const e=this.resolveDatasetElementOptions(t);this.options.showLine||(e.borderWidth=0);const o={_loop:!0,_fullLoop:n.length===s.length,options:e};this.updateElement(i,void 0,o,t)}this.updateElements(s,0,s.length,t)}updateElements(t,e,i,s){const n=this.getDataset(),o=this._cachedMeta.rScale,a="reset"===s;for(let r=e;r"",label:t=>"("+t.label+", "+t.formattedValue+")"}}},scales:{x:{type:"linear"},y:{type:"linear"}}};var Rn=Object.freeze({__proto__:null,BarController:Sn,BubbleController:Pn,DoughnutController:Dn,LineController:Cn,PolarAreaController:On,PieController:An,RadarController:Tn,ScatterController:Ln});function En(t,e,i){const{startAngle:s,pixelMargin:n,x:o,y:a,outerRadius:r,innerRadius:l}=e;let h=n/r;t.beginPath(),t.arc(o,a,r,s-h,i+h),l>n?(h=n/l,t.arc(o,a,l,i+h,s-h,!0)):t.arc(o,a,n,i+kt,s-kt),t.closePath(),t.clip()}function In(t,e,i,s){const n=Be(t.options.borderRadius,["outerStart","outerEnd","innerStart","innerEnd"]);const o=(i-e)/2,a=Math.min(o,s*e/2),r=t=>{const e=(i-Math.min(o,t))*s/2;return jt(t,0,Math.min(o,e))};return{outerStart:r(n.outerStart),outerEnd:r(n.outerEnd),innerStart:jt(n.innerStart,0,a),innerEnd:jt(n.innerEnd,0,a)}}function zn(t,e,i,s){return{x:i+t*Math.cos(e),y:s+t*Math.sin(e)}}function Fn(t,e,i,s,n){const{x:o,y:a,startAngle:r,pixelMargin:l,innerRadius:h}=e,c=Math.max(e.outerRadius+s+i-l,0),d=h>0?h+s+i+l:0;let u=0;const f=n-r;if(s){const t=((h>0?h-s:0)+(c>0?c-s:0))/2;u=(f-(0!==t?f*t/(t+s):f))/2}const g=(f-Math.max(.001,f*c-i/_t)/c)/2,p=r+g+u,m=n-g-u,{outerStart:x,outerEnd:b,innerStart:_,innerEnd:y}=In(e,d,c,m-p),v=c-x,w=c-b,M=p+x/v,k=m-b/w,S=d+_,P=d+y,D=p+_/S,C=m-y/P;if(t.beginPath(),t.arc(o,a,c,M,k),b>0){const e=zn(w,k,o,a);t.arc(e.x,e.y,b,k,m+kt)}const O=zn(P,m,o,a);if(t.lineTo(O.x,O.y),y>0){const e=zn(P,C,o,a);t.arc(e.x,e.y,y,m+kt,C+Math.PI)}if(t.arc(o,a,d,m-y/d,p+_/d,!0),_>0){const e=zn(S,D,o,a);t.arc(e.x,e.y,_,D+Math.PI,p-kt)}const A=zn(v,p,o,a);if(t.lineTo(A.x,A.y),x>0){const e=zn(v,M,o,a);t.arc(e.x,e.y,x,p-kt,M)}t.closePath()}function Bn(t,e,i,s,n){const{options:o}=e,{borderWidth:a,borderJoinStyle:r}=o,l="inner"===o.borderAlign;a&&(l?(t.lineWidth=2*a,t.lineJoin=r||"round"):(t.lineWidth=a,t.lineJoin=r||"bevel"),e.fullCircles&&function(t,e,i){const{x:s,y:n,startAngle:o,pixelMargin:a,fullCircles:r}=e,l=Math.max(e.outerRadius-a,0),h=e.innerRadius+a;let c;for(i&&En(t,e,o+yt),t.beginPath(),t.arc(s,n,h,o+yt,o,!0),c=0;c=yt||Ht(n,a,r),f=Yt(o,l+d,h+d);return u&&f}getCenterPoint(t){const{x:e,y:i,startAngle:s,endAngle:n,innerRadius:o,outerRadius:a}=this.getProps(["x","y","startAngle","endAngle","innerRadius","outerRadius","circumference"],t),{offset:r,spacing:l}=this.options,h=(s+n)/2,c=(o+a+l+r)/2;return{x:e+Math.cos(h)*c,y:i+Math.sin(h)*c}}tooltipPosition(t){return this.getCenterPoint(t)}draw(t){const{options:e,circumference:i}=this,s=(e.offset||0)/2,n=(e.spacing||0)/2;if(this.pixelMargin="inner"===e.borderAlign?.33:0,this.fullCircles=i>yt?Math.floor(i/yt):0,0===i||this.innerRadius<0||this.outerRadius<0)return;t.save();let o=0;if(s){o=s/2;const e=(this.startAngle+this.endAngle)/2;t.translate(Math.cos(e)*o,Math.sin(e)*o),this.circumference>=_t&&(o=s)}t.fillStyle=e.backgroundColor,t.strokeStyle=e.borderColor;const a=function(t,e,i,s){const{fullCircles:n,startAngle:o,circumference:a}=e;let r=e.endAngle;if(n){Fn(t,e,i,s,o+yt);for(let e=0;er&&o>r;return{count:s,start:l,loop:e.loop,ilen:h(a+(h?r-t:t))%o,_=()=>{f!==g&&(t.lineTo(m,g),t.lineTo(m,f),t.lineTo(m,p))};for(l&&(d=n[b(0)],t.moveTo(d.x,d.y)),c=0;c<=r;++c){if(d=n[b(c)],d.skip)continue;const e=d.x,i=d.y,s=0|e;s===u?(ig&&(g=i),m=(x*m+e)/++x):(_(),t.lineTo(e,i),u=s,x=0,f=g=i),p=i}_()}function Yn(t){const e=t.options,i=e.borderDash&&e.borderDash.length;return!(t._decimated||t._loop||e.tension||"monotone"===e.cubicInterpolationMode||e.stepped||i)?$n:jn}Vn.id="arc",Vn.defaults={borderAlign:"center",borderColor:"#fff",borderJoinStyle:void 0,borderRadius:0,borderWidth:2,offset:0,spacing:0,angle:void 0},Vn.defaultRoutes={backgroundColor:"backgroundColor"};const Un="function"==typeof Path2D;function Xn(t,e,i,s){Un&&!e.options.segment?function(t,e,i,s){let n=e._path;n||(n=e._path=new Path2D,e.path(n,i,s)&&n.closePath()),Wn(t,e.options),t.stroke(n)}(t,e,i,s):function(t,e,i,s){const{segments:n,options:o}=e,a=Yn(e);for(const r of n)Wn(t,o,r.style),t.beginPath(),a(t,e,r,{start:i,end:i+s-1})&&t.closePath(),t.stroke()}(t,e,i,s)}class qn extends Ds{constructor(t){super(),this.animated=!0,this.options=void 0,this._chart=void 0,this._loop=void 0,this._fullLoop=void 0,this._path=void 0,this._points=void 0,this._segments=void 0,this._decimated=!1,this._pointsUpdated=!1,this._datasetIndex=void 0,t&&Object.assign(this,t)}updateControlPoints(t,e){const i=this.options;if((i.tension||"monotone"===i.cubicInterpolationMode)&&!i.stepped&&!this._pointsUpdated){const s=i.spanGaps?this._loop:this._fullLoop;ki(this._points,i,t,s,e),this._pointsUpdated=!0}}set points(t){this._points=t,delete this._segments,delete this._path,this._pointsUpdated=!1}get points(){return this._points}get segments(){return this._segments||(this._segments=Ni(this,this.options.segment))}first(){const t=this.segments,e=this.points;return t.length&&e[t[0].start]}last(){const t=this.segments,e=this.points,i=t.length;return i&&e[t[i-1].end]}interpolate(t,e){const i=this.options,s=t[e],n=this.points,o=Wi(this,{property:e,start:s,end:s});if(!o.length)return;const a=[],r=function(t){return t.stepped?Ai:t.tension||"monotone"===t.cubicInterpolationMode?Ti:Oi}(i);let l,h;for(l=0,h=o.length;l"borderDash"!==t&&"fill"!==t};class Gn extends Ds{constructor(t){super(),this.options=void 0,this.parsed=void 0,this.skip=void 0,this.stop=void 0,t&&Object.assign(this,t)}inRange(t,e,i){const s=this.options,{x:n,y:o}=this.getProps(["x","y"],i);return Math.pow(t-n,2)+Math.pow(e-o,2){oo(t)}))}var ro={id:"decimation",defaults:{algorithm:"min-max",enabled:!1},beforeElementsUpdate:(t,e,i)=>{if(!i.enabled)return void ao(t);const s=t.width;t.data.datasets.forEach(((e,n)=>{const{_data:o,indexAxis:a}=e,r=t.getDatasetMeta(n),l=o||e.data;if("y"===je([a,t.options.indexAxis]))return;if("line"!==r.type)return;const h=t.scales[r.xAxisID];if("linear"!==h.type&&"time"!==h.type)return;if(t.options.parsing)return;let{start:c,count:d}=function(t,e){const i=e.length;let s,n=0;const{iScale:o}=t,{min:a,max:r,minDefined:l,maxDefined:h}=o.getUserBounds();return l&&(n=jt(re(e,o.axis,a).lo,0,i-1)),s=h?jt(re(e,o.axis,r).hi+1,n,i)-n:i-n,{start:n,count:s}}(r,l);if(d<=(i.threshold||4*s))return void oo(e);let u;switch($(o)&&(e._data=l,delete e.data,Object.defineProperty(e,"data",{configurable:!0,enumerable:!0,get:function(){return this._decimated},set:function(t){this._data=t}})),i.algorithm){case"lttb":u=function(t,e,i,s,n){const o=n.samples||s;if(o>=i)return t.slice(e,e+i);const a=[],r=(i-2)/(o-2);let l=0;const h=e+i-1;let c,d,u,f,g,p=e;for(a[l++]=t[p],c=0;cu&&(u=f,d=t[s],g=s);a[l++]=d,p=g}return a[l++]=t[h],a}(l,c,d,s,i);break;case"min-max":u=function(t,e,i,s){let n,o,a,r,l,h,c,d,u,f,g=0,p=0;const m=[],x=e+i-1,b=t[e].x,_=t[x].x-b;for(n=e;nf&&(f=r,c=n),g=(p*g+o.x)/++p;else{const i=n-1;if(!$(h)&&!$(c)){const e=Math.min(h,c),s=Math.max(h,c);e!==d&&e!==i&&m.push({...t[e],x:g}),s!==d&&s!==i&&m.push({...t[s],x:g})}n>0&&i!==d&&m.push(t[i]),m.push(o),l=e,p=0,u=f=r,h=c=d=n}}return m}(l,c,d,s);break;default:throw new Error(`Unsupported decimation algorithm '${i.algorithm}'`)}e._decimated=u}))},destroy(t){ao(t)}};function lo(t,e,i){const s=function(t){const e=t.options,i=e.fill;let s=K(i&&i.target,i);return void 0===s&&(s=!!e.backgroundColor),!1!==s&&null!==s&&(!0===s?"origin":s)}(t);if(U(s))return!isNaN(s.value)&&s;let n=parseFloat(s);return X(n)&&Math.floor(n)===n?("-"!==s[0]&&"+"!==s[0]||(n=e+n),!(n===e||n<0||n>=i)&&n):["origin","start","end","stack","shape"].indexOf(s)>=0&&s}class ho{constructor(t){this.x=t.x,this.y=t.y,this.radius=t.radius}pathSegment(t,e,i){const{x:s,y:n,radius:o}=this;return e=e||{start:0,end:yt},t.arc(s,n,o,e.end,e.start,!0),!i.bounds}interpolate(t){const{x:e,y:i,radius:s}=this,n=t.angle;return{x:e+Math.cos(n)*s,y:i+Math.sin(n)*s,angle:n}}}function co(t){return(t.scale||{}).getPointPositionForValue?function(t){const{scale:e,fill:i}=t,s=e.options,n=e.getLabels().length,o=[],a=s.reverse?e.max:e.min,r=s.reverse?e.min:e.max;let l,h,c;if(c="start"===i?a:"end"===i?r:U(i)?i.value:e.getBaseValue(),s.grid.circular)return h=e.getPointPositionForValue(0,a),new ho({x:h.x,y:h.y,radius:e.getDistanceFromCenterForValue(c)});for(l=0;lt;e--){const t=i[e];if(!isNaN(t.x)&&!isNaN(t.y))break}return e}function fo(t,e,i){const s=[];for(let n=0;n{e=uo(t,e,n);const a=n[t],r=n[e];null!==s?(o.push({x:a.x,y:s}),o.push({x:r.x,y:s})):null!==i&&(o.push({x:i,y:a.y}),o.push({x:i,y:r.y}))})),o}(t,e),i.length?new qn({points:i,options:{tension:0},_loop:s,_fullLoop:s}):null}function xo(t,e,i){let s=t[e].fill;const n=[e];let o;if(!i)return s;for(;!1!==s&&-1===n.indexOf(s);){if(!X(s))return s;if(o=t[s],!o)return!1;if(o.visible)return s;n.push(s),s=o.fill}return!1}function bo(t,e,i){const{segments:s,points:n}=e;let o=!0,a=!1;t.beginPath();for(const r of s){const{start:s,end:l}=r,h=n[s],c=n[uo(s,l,n)];o?(t.moveTo(h.x,h.y),o=!1):(t.lineTo(h.x,i),t.lineTo(h.x,h.y)),a=!!e.pathSegment(t,r,{move:a}),a?t.closePath():t.lineTo(c.x,i)}t.lineTo(e.first().x,i),t.closePath(),t.clip()}function _o(t,e,i,s){if(s)return;let n=e[t],o=i[t];return"angle"===t&&(n=Nt(n),o=Nt(o)),{property:t,start:n,end:o}}function yo(t,e,i,s){return t&&e?s(t[i],e[i]):t?t[i]:e?e[i]:0}function vo(t,e,i){const{top:s,bottom:n}=e.chart.chartArea,{property:o,start:a,end:r}=i||{};"x"===o&&(t.beginPath(),t.rect(a,s,r-a,n-s),t.clip())}function wo(t,e,i,s){const n=e.interpolate(i,s);n&&t.lineTo(n.x,n.y)}function Mo(t,e){const{line:i,target:s,property:n,color:o,scale:a}=e,r=function(t,e,i){const s=t.segments,n=t.points,o=e.points,a=[];for(const t of s){let{start:s,end:r}=t;r=uo(s,r,n);const l=_o(i,n[s],n[r],t.loop);if(!e.segments){a.push({source:t,target:l,start:n[s],end:n[r]});continue}const h=Wi(e,l);for(const e of h){const s=_o(i,o[e.start],o[e.end],e.loop),r=Vi(t,n,s);for(const t of r)a.push({source:t,target:e,start:{[i]:yo(l,s,"start",Math.max)},end:{[i]:yo(l,s,"end",Math.min)}})}}return a}(i,s,n);for(const{source:e,target:l,start:h,end:c}of r){const{style:{backgroundColor:r=o}={}}=e,d=!0!==s;t.save(),t.fillStyle=r,vo(t,a,d&&_o(n,h,c)),t.beginPath();const u=!!i.pathSegment(t,e);let f;if(d){u?t.closePath():wo(t,s,c,n);const e=!!s.pathSegment(t,l,{move:u,reverse:!0});f=u&&e,f||wo(t,s,h,n)}t.closePath(),t.fill(f?"evenodd":"nonzero"),t.restore()}}function ko(t,e,i){const s=po(e),{line:n,scale:o,axis:a}=e,r=n.options,l=r.fill,h=r.backgroundColor,{above:c=h,below:d=h}=l||{};s&&n.points.length&&(Qt(t,i),function(t,e){const{line:i,target:s,above:n,below:o,area:a,scale:r}=e,l=i._loop?"angle":e.axis;t.save(),"x"===l&&o!==n&&(bo(t,s,a.top),Mo(t,{line:i,target:s,color:n,scale:r,property:l}),t.restore(),t.save(),bo(t,s,a.bottom)),Mo(t,{line:i,target:s,color:o,scale:r,property:l}),t.restore()}(t,{line:n,target:s,above:c,below:d,area:i,scale:o,axis:a}),te(t))}var So={id:"filler",afterDatasetsUpdate(t,e,i){const s=(t.data.datasets||[]).length,n=[];let o,a,r,l;for(a=0;a=0;--e){const i=n[e].$filler;i&&(i.line.updateControlPoints(o,i.axis),s&&ko(t.ctx,i,o))}},beforeDatasetsDraw(t,e,i){if("beforeDatasetsDraw"!==i.drawTime)return;const s=t.getSortedVisibleDatasetMetas();for(let e=s.length-1;e>=0;--e){const i=s[e].$filler;i&&ko(t.ctx,i,t.chartArea)}},beforeDatasetDraw(t,e,i){const s=e.meta.$filler;s&&!1!==s.fill&&"beforeDatasetDraw"===i.drawTime&&ko(t.ctx,s,t.chartArea)},defaults:{propagate:!0,drawTime:"beforeDatasetDraw"}};const Po=(t,e)=>{let{boxHeight:i=e,boxWidth:s=e}=t;return t.usePointStyle&&(i=Math.min(i,e),s=Math.min(s,e)),{boxWidth:s,boxHeight:i,itemHeight:Math.max(e,i)}};class Do extends Ds{constructor(t){super(),this._added=!1,this.legendHitBoxes=[],this._hoveredItem=null,this.doughnutMode=!1,this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this.legendItems=void 0,this.columnSizes=void 0,this.lineWidths=void 0,this.maxHeight=void 0,this.maxWidth=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.height=void 0,this.width=void 0,this._margins=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,e,i){this.maxWidth=t,this.maxHeight=e,this._margins=i,this.setDimensions(),this.buildLabels(),this.fit()}setDimensions(){this.isHorizontal()?(this.width=this.maxWidth,this.left=this._margins.left,this.right=this.width):(this.height=this.maxHeight,this.top=this._margins.top,this.bottom=this.height)}buildLabels(){const t=this.options.labels||{};let e=J(t.generateLabels,[this.chart],this)||[];t.filter&&(e=e.filter((e=>t.filter(e,this.chart.data)))),t.sort&&(e=e.sort(((e,i)=>t.sort(e,i,this.chart.data)))),this.options.reverse&&e.reverse(),this.legendItems=e}fit(){const{options:t,ctx:e}=this;if(!t.display)return void(this.width=this.height=0);const i=t.labels,s=He(i.font),n=s.size,o=this._computeTitleHeight(),{boxWidth:a,itemHeight:r}=Po(i,n);let l,h;e.font=s.string,this.isHorizontal()?(l=this.maxWidth,h=this._fitRows(o,n,a,r)+10):(h=this.maxHeight,l=this._fitCols(o,n,a,r)+10),this.width=Math.min(l,t.maxWidth||this.maxWidth),this.height=Math.min(h,t.maxHeight||this.maxHeight)}_fitRows(t,e,i,s){const{ctx:n,maxWidth:o,options:{labels:{padding:a}}}=this,r=this.legendHitBoxes=[],l=this.lineWidths=[0],h=s+a;let c=t;n.textAlign="left",n.textBaseline="middle";let d=-1,u=-h;return this.legendItems.forEach(((t,f)=>{const g=i+e/2+n.measureText(t.text).width;(0===f||l[l.length-1]+g+2*a>o)&&(c+=h,l[l.length-(f>0?0:1)]=0,u+=h,d++),r[f]={left:0,top:u,row:d,width:g,height:s},l[l.length-1]+=g+a})),c}_fitCols(t,e,i,s){const{ctx:n,maxHeight:o,options:{labels:{padding:a}}}=this,r=this.legendHitBoxes=[],l=this.columnSizes=[],h=o-t;let c=a,d=0,u=0,f=0,g=0;return this.legendItems.forEach(((t,o)=>{const p=i+e/2+n.measureText(t.text).width;o>0&&u+s+2*a>h&&(c+=d+a,l.push({width:d,height:u}),f+=d+a,g++,d=u=0),r[o]={left:f,top:u,col:g,width:p,height:s},d=Math.max(d,p),u+=s+a})),c+=d,l.push({width:d,height:u}),c}adjustHitBoxes(){if(!this.options.display)return;const t=this._computeTitleHeight(),{legendHitBoxes:e,options:{align:i,labels:{padding:s},rtl:o}}=this,a=Ei(o,this.left,this.width);if(this.isHorizontal()){let o=0,r=n(i,this.left+s,this.right-this.lineWidths[o]);for(const l of e)o!==l.row&&(o=l.row,r=n(i,this.left+s,this.right-this.lineWidths[o])),l.top+=this.top+t+s,l.left=a.leftForLtr(a.x(r),l.width),r+=l.width+s}else{let o=0,r=n(i,this.top+t+s,this.bottom-this.columnSizes[o].height);for(const l of e)l.col!==o&&(o=l.col,r=n(i,this.top+t+s,this.bottom-this.columnSizes[o].height)),l.top=r,l.left+=this.left+s,l.left=a.leftForLtr(a.x(l.left),l.width),r+=l.height+s}}isHorizontal(){return"top"===this.options.position||"bottom"===this.options.position}draw(){if(this.options.display){const t=this.ctx;Qt(t,this),this._draw(),te(t)}}_draw(){const{options:t,columnSizes:e,lineWidths:i,ctx:s}=this,{align:a,labels:r}=t,l=bt.color,h=Ei(t.rtl,this.left,this.width),c=He(r.font),{color:d,padding:u}=r,f=c.size,g=f/2;let p;this.drawTitle(),s.textAlign=h.textAlign("left"),s.textBaseline="middle",s.lineWidth=.5,s.font=c.string;const{boxWidth:m,boxHeight:x,itemHeight:b}=Po(r,f),_=this.isHorizontal(),y=this._computeTitleHeight();p=_?{x:n(a,this.left+u,this.right-i[0]),y:this.top+u+y,line:0}:{x:this.left+u,y:n(a,this.top+y+u,this.bottom-e[0].height),line:0},Ii(this.ctx,t.textDirection);const v=b+u;this.legendItems.forEach(((w,M)=>{s.strokeStyle=w.fontColor||d,s.fillStyle=w.fontColor||d;const k=s.measureText(w.text).width,S=h.textAlign(w.textAlign||(w.textAlign=r.textAlign)),P=m+g+k;let D=p.x,C=p.y;h.setWidth(this.width),_?M>0&&D+P+u>this.right&&(C=p.y+=v,p.line++,D=p.x=n(a,this.left+u,this.right-i[p.line])):M>0&&C+v>this.bottom&&(D=p.x=D+e[p.line].width+u,p.line++,C=p.y=n(a,this.top+y+u,this.bottom-e[p.line].height));!function(t,e,i){if(isNaN(m)||m<=0||isNaN(x)||x<0)return;s.save();const n=K(i.lineWidth,1);if(s.fillStyle=K(i.fillStyle,l),s.lineCap=K(i.lineCap,"butt"),s.lineDashOffset=K(i.lineDashOffset,0),s.lineJoin=K(i.lineJoin,"miter"),s.lineWidth=n,s.strokeStyle=K(i.strokeStyle,l),s.setLineDash(K(i.lineDash,[])),r.usePointStyle){const o={radius:m*Math.SQRT2/2,pointStyle:i.pointStyle,rotation:i.rotation,borderWidth:n},a=h.xPlus(t,m/2);Zt(s,o,a,e+g)}else{const o=e+Math.max((f-x)/2,0),a=h.leftForLtr(t,m),r=We(i.borderRadius);s.beginPath(),Object.values(r).some((t=>0!==t))?oe(s,{x:a,y:o,w:m,h:x,radius:r}):s.rect(a,o,m,x),s.fill(),0!==n&&s.stroke()}s.restore()}(h.x(D),C,w),D=o(S,D+m+g,_?D+P:this.right,t.rtl),function(t,e,i){se(s,i.text,t,e+b/2,c,{strikethrough:i.hidden,textAlign:h.textAlign(i.textAlign)})}(h.x(D),C,w),_?p.x+=P+u:p.y+=v})),zi(this.ctx,t.textDirection)}drawTitle(){const t=this.options,e=t.title,i=He(e.font),o=Ne(e.padding);if(!e.display)return;const a=Ei(t.rtl,this.left,this.width),r=this.ctx,l=e.position,h=i.size/2,c=o.top+h;let d,u=this.left,f=this.width;if(this.isHorizontal())f=Math.max(...this.lineWidths),d=this.top+c,u=n(t.align,u,this.right-f);else{const e=this.columnSizes.reduce(((t,e)=>Math.max(t,e.height)),0);d=c+n(t.align,this.top,this.bottom-e-t.labels.padding-this._computeTitleHeight())}const g=n(l,u,u+f);r.textAlign=a.textAlign(s(l)),r.textBaseline="middle",r.strokeStyle=e.color,r.fillStyle=e.color,r.font=i.string,se(r,e.text,g,d,i)}_computeTitleHeight(){const t=this.options.title,e=He(t.font),i=Ne(t.padding);return t.display?e.lineHeight+i.height:0}_getLegendItemAt(t,e){let i,s,n;if(Yt(t,this.left,this.right)&&Yt(e,this.top,this.bottom))for(n=this.legendHitBoxes,i=0;it.chart.options.color,boxWidth:40,padding:10,generateLabels(t){const e=t.data.datasets,{labels:{usePointStyle:i,pointStyle:s,textAlign:n,color:o}}=t.legend.options;return t._getSortedDatasetMetas().map((t=>{const a=t.controller.getStyle(i?0:void 0),r=Ne(a.borderWidth);return{text:e[t.index].label,fillStyle:a.backgroundColor,fontColor:o,hidden:!t.visible,lineCap:a.borderCapStyle,lineDash:a.borderDash,lineDashOffset:a.borderDashOffset,lineJoin:a.borderJoinStyle,lineWidth:(r.width+r.height)/4,strokeStyle:a.borderColor,pointStyle:s||a.pointStyle,rotation:a.rotation,textAlign:n||a.textAlign,borderRadius:0,datasetIndex:t.index}}),this)}},title:{color:t=>t.chart.options.color,display:!1,position:"center",text:""}},descriptors:{_scriptable:t=>!t.startsWith("on"),labels:{_scriptable:t=>!["generateLabels","filter","sort"].includes(t)}}};class Oo extends Ds{constructor(t){super(),this.chart=t.chart,this.options=t.options,this.ctx=t.ctx,this._padding=void 0,this.top=void 0,this.bottom=void 0,this.left=void 0,this.right=void 0,this.width=void 0,this.height=void 0,this.position=void 0,this.weight=void 0,this.fullSize=void 0}update(t,e){const i=this.options;if(this.left=0,this.top=0,!i.display)return void(this.width=this.height=this.right=this.bottom=0);this.width=this.right=t,this.height=this.bottom=e;const s=Y(i.text)?i.text.length:1;this._padding=Ne(i.padding);const n=s*He(i.font).lineHeight+this._padding.height;this.isHorizontal()?this.height=n:this.width=n}isHorizontal(){const t=this.options.position;return"top"===t||"bottom"===t}_drawArgs(t){const{top:e,left:i,bottom:s,right:o,options:a}=this,r=a.align;let l,h,c,d=0;return this.isHorizontal()?(h=n(r,i,o),c=e+t,l=o-i):("left"===a.position?(h=i+t,c=n(r,s,e),d=-.5*_t):(h=o-t,c=n(r,e,s),d=.5*_t),l=s-e),{titleX:h,titleY:c,maxWidth:l,rotation:d}}draw(){const t=this.ctx,e=this.options;if(!e.display)return;const i=He(e.font),n=i.lineHeight/2+this._padding.top,{titleX:o,titleY:a,maxWidth:r,rotation:l}=this._drawArgs(n);se(t,e.text,0,0,i,{color:e.color,maxWidth:r,rotation:l,textAlign:s(e.align),textBaseline:"middle",translation:[o,a]})}}var Ao={id:"title",_element:Oo,start(t,e,i){!function(t,e){const i=new Oo({ctx:t.ctx,options:e,chart:t});ni.configure(t,i,e),ni.addBox(t,i),t.titleBlock=i}(t,i)},stop(t){const e=t.titleBlock;ni.removeBox(t,e),delete t.titleBlock},beforeUpdate(t,e,i){const s=t.titleBlock;ni.configure(t,s,i),s.options=i},defaults:{align:"center",display:!1,font:{weight:"bold"},fullSize:!0,padding:10,position:"top",text:"",weight:2e3},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const To=new WeakMap;var Lo={id:"subtitle",start(t,e,i){const s=new Oo({ctx:t.ctx,options:i,chart:t});ni.configure(t,s,i),ni.addBox(t,s),To.set(t,s)},stop(t){ni.removeBox(t,To.get(t)),To.delete(t)},beforeUpdate(t,e,i){const s=To.get(t);ni.configure(t,s,i),s.options=i},defaults:{align:"center",display:!1,font:{weight:"normal"},fullSize:!0,padding:0,position:"top",text:"",weight:1500},defaultRoutes:{color:"color"},descriptors:{_scriptable:!0,_indexable:!1}};const Ro={average(t){if(!t.length)return!1;let e,i,s=0,n=0,o=0;for(e=0,i=t.length;e-1?t.split("\n"):t}function zo(t,e){const{element:i,datasetIndex:s,index:n}=e,o=t.getDatasetMeta(s).controller,{label:a,value:r}=o.getLabelAndValue(n);return{chart:t,label:a,parsed:o.getParsed(n),raw:t.data.datasets[s].data[n],formattedValue:r,dataset:o.getDataset(),dataIndex:n,datasetIndex:s,element:i}}function Fo(t,e){const i=t.chart.ctx,{body:s,footer:n,title:o}=t,{boxWidth:a,boxHeight:r}=e,l=He(e.bodyFont),h=He(e.titleFont),c=He(e.footerFont),d=o.length,u=n.length,f=s.length,g=Ne(e.padding);let p=g.height,m=0,x=s.reduce(((t,e)=>t+e.before.length+e.lines.length+e.after.length),0);if(x+=t.beforeBody.length+t.afterBody.length,d&&(p+=d*h.lineHeight+(d-1)*e.titleSpacing+e.titleMarginBottom),x){p+=f*(e.displayColors?Math.max(r,l.lineHeight):l.lineHeight)+(x-f)*l.lineHeight+(x-1)*e.bodySpacing}u&&(p+=e.footerMarginTop+u*c.lineHeight+(u-1)*e.footerSpacing);let b=0;const _=function(t){m=Math.max(m,i.measureText(t).width+b)};return i.save(),i.font=h.string,Q(t.title,_),i.font=l.string,Q(t.beforeBody.concat(t.afterBody),_),b=e.displayColors?a+2+e.boxPadding:0,Q(s,(t=>{Q(t.before,_),Q(t.lines,_),Q(t.after,_)})),b=0,i.font=c.string,Q(t.footer,_),i.restore(),m+=g.width,{width:m,height:p}}function Bo(t,e,i,s){const{x:n,width:o}=i,{width:a,chartArea:{left:r,right:l}}=t;let h="center";return"center"===s?h=n<=(r+l)/2?"left":"right":n<=o/2?h="left":n>=a-o/2&&(h="right"),function(t,e,i,s){const{x:n,width:o}=s,a=i.caretSize+i.caretPadding;return"left"===t&&n+o+a>e.width||"right"===t&&n-o-a<0||void 0}(h,t,e,i)&&(h="center"),h}function Vo(t,e,i){const s=i.yAlign||e.yAlign||function(t,e){const{y:i,height:s}=e;return it.height-s/2?"bottom":"center"}(t,i);return{xAlign:i.xAlign||e.xAlign||Bo(t,e,i,s),yAlign:s}}function Wo(t,e,i,s){const{caretSize:n,caretPadding:o,cornerRadius:a}=t,{xAlign:r,yAlign:l}=i,h=n+o,{topLeft:c,topRight:d,bottomLeft:u,bottomRight:f}=We(a);let g=function(t,e){let{x:i,width:s}=t;return"right"===e?i-=s:"center"===e&&(i-=s/2),i}(e,r);const p=function(t,e,i){let{y:s,height:n}=t;return"top"===e?s+=i:s-="bottom"===e?n+i:n/2,s}(e,l,h);return"center"===l?"left"===r?g+=h:"right"===r&&(g-=h):"left"===r?g-=Math.max(c,u)+n:"right"===r&&(g+=Math.max(d,f)+n),{x:jt(g,0,s.width-e.width),y:jt(p,0,s.height-e.height)}}function No(t,e,i){const s=Ne(i.padding);return"center"===e?t.x+t.width/2:"right"===e?t.x+t.width-s.right:t.x+s.left}function Ho(t){return Eo([],Io(t))}function jo(t,e){const i=e&&e.dataset&&e.dataset.tooltip&&e.dataset.tooltip.callbacks;return i?t.override(i):t}class $o extends Ds{constructor(t){super(),this.opacity=0,this._active=[],this._eventPosition=void 0,this._size=void 0,this._cachedAnimations=void 0,this._tooltipItems=[],this.$animations=void 0,this.$context=void 0,this.chart=t.chart||t._chart,this._chart=this.chart,this.options=t.options,this.dataPoints=void 0,this.title=void 0,this.beforeBody=void 0,this.body=void 0,this.afterBody=void 0,this.footer=void 0,this.xAlign=void 0,this.yAlign=void 0,this.x=void 0,this.y=void 0,this.height=void 0,this.width=void 0,this.caretX=void 0,this.caretY=void 0,this.labelColors=void 0,this.labelPointStyles=void 0,this.labelTextColors=void 0}initialize(t){this.options=t,this._cachedAnimations=void 0,this.$context=void 0}_resolveAnimations(){const t=this._cachedAnimations;if(t)return t;const e=this.chart,i=this.options.setContext(this.getContext()),s=i.enabled&&e.options.animation&&i.animations,n=new gs(this.chart,s);return s._cacheable&&(this._cachedAnimations=Object.freeze(n)),n}getContext(){return this.$context||(this.$context=(t=this.chart.getContext(),e=this,i=this._tooltipItems,Ye(t,{tooltip:e,tooltipItems:i,type:"tooltip"})));var t,e,i}getTitle(t,e){const{callbacks:i}=e,s=i.beforeTitle.apply(this,[t]),n=i.title.apply(this,[t]),o=i.afterTitle.apply(this,[t]);let a=[];return a=Eo(a,Io(s)),a=Eo(a,Io(n)),a=Eo(a,Io(o)),a}getBeforeBody(t,e){return Ho(e.callbacks.beforeBody.apply(this,[t]))}getBody(t,e){const{callbacks:i}=e,s=[];return Q(t,(t=>{const e={before:[],lines:[],after:[]},n=jo(i,t);Eo(e.before,Io(n.beforeLabel.call(this,t))),Eo(e.lines,n.label.call(this,t)),Eo(e.after,Io(n.afterLabel.call(this,t))),s.push(e)})),s}getAfterBody(t,e){return Ho(e.callbacks.afterBody.apply(this,[t]))}getFooter(t,e){const{callbacks:i}=e,s=i.beforeFooter.apply(this,[t]),n=i.footer.apply(this,[t]),o=i.afterFooter.apply(this,[t]);let a=[];return a=Eo(a,Io(s)),a=Eo(a,Io(n)),a=Eo(a,Io(o)),a}_createItems(t){const e=this._active,i=this.chart.data,s=[],n=[],o=[];let a,r,l=[];for(a=0,r=e.length;at.filter(e,s,n,i)))),t.itemSort&&(l=l.sort(((e,s)=>t.itemSort(e,s,i)))),Q(l,(e=>{const i=jo(t.callbacks,e);s.push(i.labelColor.call(this,e)),n.push(i.labelPointStyle.call(this,e)),o.push(i.labelTextColor.call(this,e))})),this.labelColors=s,this.labelPointStyles=n,this.labelTextColors=o,this.dataPoints=l,l}update(t,e){const i=this.options.setContext(this.getContext()),s=this._active;let n,o=[];if(s.length){const t=Ro[i.position].call(this,s,this._eventPosition);o=this._createItems(i),this.title=this.getTitle(o,i),this.beforeBody=this.getBeforeBody(o,i),this.body=this.getBody(o,i),this.afterBody=this.getAfterBody(o,i),this.footer=this.getFooter(o,i);const e=this._size=Fo(this,i),a=Object.assign({},t,e),r=Vo(this.chart,i,a),l=Wo(i,a,r,this.chart);this.xAlign=r.xAlign,this.yAlign=r.yAlign,n={opacity:1,x:l.x,y:l.y,width:e.width,height:e.height,caretX:t.x,caretY:t.y}}else 0!==this.opacity&&(n={opacity:0});this._tooltipItems=o,this.$context=void 0,n&&this._resolveAnimations().update(this,n),t&&i.external&&i.external.call(this,{chart:this.chart,tooltip:this,replay:e})}drawCaret(t,e,i,s){const n=this.getCaretPosition(t,i,s);e.lineTo(n.x1,n.y1),e.lineTo(n.x2,n.y2),e.lineTo(n.x3,n.y3)}getCaretPosition(t,e,i){const{xAlign:s,yAlign:n}=this,{caretSize:o,cornerRadius:a}=i,{topLeft:r,topRight:l,bottomLeft:h,bottomRight:c}=We(a),{x:d,y:u}=t,{width:f,height:g}=e;let p,m,x,b,_,y;return"center"===n?(_=u+g/2,"left"===s?(p=d,m=p-o,b=_+o,y=_-o):(p=d+f,m=p+o,b=_-o,y=_+o),x=p):(m="left"===s?d+Math.max(r,h)+o:"right"===s?d+f-Math.max(l,c)-o:this.caretX,"top"===n?(b=u,_=b-o,p=m-o,x=m+o):(b=u+g,_=b+o,p=m+o,x=m-o),y=b),{x1:p,x2:m,x3:x,y1:b,y2:_,y3:y}}drawTitle(t,e,i){const s=this.title,n=s.length;let o,a,r;if(n){const l=Ei(i.rtl,this.x,this.width);for(t.x=No(this,i.titleAlign,i),e.textAlign=l.textAlign(i.titleAlign),e.textBaseline="middle",o=He(i.titleFont),a=i.titleSpacing,e.fillStyle=i.titleColor,e.font=o.string,r=0;r0!==t))?(t.beginPath(),t.fillStyle=n.multiKeyBackground,oe(t,{x:e,y:g,w:l,h:r,radius:a}),t.fill(),t.stroke(),t.fillStyle=o.backgroundColor,t.beginPath(),oe(t,{x:i,y:g+1,w:l-2,h:r-2,radius:a}),t.fill()):(t.fillStyle=n.multiKeyBackground,t.fillRect(e,g,l,r),t.strokeRect(e,g,l,r),t.fillStyle=o.backgroundColor,t.fillRect(i,g+1,l-2,r-2))}t.fillStyle=this.labelTextColors[i]}drawBody(t,e,i){const{body:s}=this,{bodySpacing:n,bodyAlign:o,displayColors:a,boxHeight:r,boxWidth:l,boxPadding:h}=i,c=He(i.bodyFont);let d=c.lineHeight,u=0;const f=Ei(i.rtl,this.x,this.width),g=function(i){e.fillText(i,f.x(t.x+u),t.y+d/2),t.y+=d+n},p=f.textAlign(o);let m,x,b,_,y,v,w;for(e.textAlign=o,e.textBaseline="middle",e.font=c.string,t.x=No(this,p,i),e.fillStyle=i.bodyColor,Q(this.beforeBody,g),u=a&&"right"!==p?"center"===o?l/2+h:l+2+h:0,_=0,v=s.length;_0&&e.stroke()}_updateAnimationTarget(t){const e=this.chart,i=this.$animations,s=i&&i.x,n=i&&i.y;if(s||n){const i=Ro[t.position].call(this,this._active,this._eventPosition);if(!i)return;const o=this._size=Fo(this,t),a=Object.assign({},i,this._size),r=Vo(e,t,a),l=Wo(t,a,r,e);s._to===l.x&&n._to===l.y||(this.xAlign=r.xAlign,this.yAlign=r.yAlign,this.width=o.width,this.height=o.height,this.caretX=i.x,this.caretY=i.y,this._resolveAnimations().update(this,l))}}draw(t){const e=this.options.setContext(this.getContext());let i=this.opacity;if(!i)return;this._updateAnimationTarget(e);const s={width:this.width,height:this.height},n={x:this.x,y:this.y};i=Math.abs(i)<.001?0:i;const o=Ne(e.padding),a=this.title.length||this.beforeBody.length||this.body.length||this.afterBody.length||this.footer.length;e.enabled&&a&&(t.save(),t.globalAlpha=i,this.drawBackground(n,t,s,e),Ii(t,e.textDirection),n.y+=o.top,this.drawTitle(n,t,e),this.drawBody(n,t,e),this.drawFooter(n,t,e),zi(t,e.textDirection),t.restore())}getActiveElements(){return this._active||[]}setActiveElements(t,e){const i=this._active,s=t.map((({datasetIndex:t,index:e})=>{const i=this.chart.getDatasetMeta(t);if(!i)throw new Error("Cannot find a dataset at index "+t);return{datasetIndex:t,element:i.data[e],index:e}})),n=!tt(i,s),o=this._positionChanged(s,e);(n||o)&&(this._active=s,this._eventPosition=e,this._ignoreReplayEvents=!0,this.update(!0))}handleEvent(t,e,i=!0){if(e&&this._ignoreReplayEvents)return!1;this._ignoreReplayEvents=!1;const s=this.options,n=this._active||[],o=this._getActiveElements(t,n,e,i),a=this._positionChanged(o,t),r=e||!tt(o,n)||a;return r&&(this._active=o,(s.enabled||s.external)&&(this._eventPosition={x:t.x,y:t.y},this.update(!0,e))),r}_getActiveElements(t,e,i,s){const n=this.options;if("mouseout"===t.type)return[];if(!s)return e;const o=this.chart.getElementsAtEventForMode(t,n.mode,n,i);return n.reverse&&o.reverse(),o}_positionChanged(t,e){const{caretX:i,caretY:s,options:n}=this,o=Ro[n.position].call(this,t,e);return!1!==o&&(i!==o.x||s!==o.y)}}$o.positioners=Ro;var Yo={id:"tooltip",_element:$o,positioners:Ro,afterInit(t,e,i){i&&(t.tooltip=new $o({chart:t,options:i}))},beforeUpdate(t,e,i){t.tooltip&&t.tooltip.initialize(i)},reset(t,e,i){t.tooltip&&t.tooltip.initialize(i)},afterDraw(t){const e=t.tooltip,i={tooltip:e};!1!==t.notifyPlugins("beforeTooltipDraw",i)&&(e&&e.draw(t.ctx),t.notifyPlugins("afterTooltipDraw",i))},afterEvent(t,e){if(t.tooltip){const i=e.replay;t.tooltip.handleEvent(e.event,i,e.inChartArea)&&(e.changed=!0)}},defaults:{enabled:!0,external:null,position:"average",backgroundColor:"rgba(0,0,0,0.8)",titleColor:"#fff",titleFont:{weight:"bold"},titleSpacing:2,titleMarginBottom:6,titleAlign:"left",bodyColor:"#fff",bodySpacing:2,bodyFont:{},bodyAlign:"left",footerColor:"#fff",footerSpacing:2,footerMarginTop:6,footerFont:{weight:"bold"},footerAlign:"left",padding:6,caretPadding:2,caretSize:5,cornerRadius:6,boxHeight:(t,e)=>e.bodyFont.size,boxWidth:(t,e)=>e.bodyFont.size,multiKeyBackground:"#fff",displayColors:!0,boxPadding:0,borderColor:"rgba(0,0,0,0)",borderWidth:0,animation:{duration:400,easing:"easeOutQuart"},animations:{numbers:{type:"number",properties:["x","y","width","height","caretX","caretY"]},opacity:{easing:"linear",duration:200}},callbacks:{beforeTitle:H,title(t){if(t.length>0){const e=t[0],i=e.chart.data.labels,s=i?i.length:0;if(this&&this.options&&"dataset"===this.options.mode)return e.dataset.label||"";if(e.label)return e.label;if(s>0&&e.dataIndex"filter"!==t&&"itemSort"!==t&&"external"!==t,_indexable:!1,callbacks:{_scriptable:!1,_indexable:!1},animation:{_fallback:!1},animations:{_fallback:"animation"}},additionalOptionScopes:["interaction"]},Uo=Object.freeze({__proto__:null,Decimation:ro,Filler:So,Legend:Co,SubTitle:Lo,Title:Ao,Tooltip:Yo});function Xo(t,e,i,s){const n=t.indexOf(e);if(-1===n)return((t,e,i,s)=>("string"==typeof e?(i=t.push(e)-1,s.unshift({index:i,label:e})):isNaN(e)&&(i=null),i))(t,e,i,s);return n!==t.lastIndexOf(e)?i:n}class qo extends Bs{constructor(t){super(t),this._startValue=void 0,this._valueRange=0,this._addedLabels=[]}init(t){const e=this._addedLabels;if(e.length){const t=this.getLabels();for(const{index:i,label:s}of e)t[i]===s&&t.splice(i,1);this._addedLabels=[]}super.init(t)}parse(t,e){if($(t))return null;const i=this.getLabels();return((t,e)=>null===t?null:jt(Math.round(t),0,e))(e=isFinite(e)&&i[e]===t?e:Xo(i,t,K(e,t),this._addedLabels),i.length-1)}determineDataLimits(){const{minDefined:t,maxDefined:e}=this.getUserBounds();let{min:i,max:s}=this.getMinMax(!0);"ticks"===this.options.bounds&&(t||(i=0),e||(s=this.getLabels().length-1)),this.min=i,this.max=s}buildTicks(){const t=this.min,e=this.max,i=this.options.offset,s=[];let n=this.getLabels();n=0===t&&e===n.length-1?n:n.slice(t,e+1),this._valueRange=Math.max(n.length-(i?0:1),1),this._startValue=this.min-(i?.5:0);for(let i=t;i<=e;i++)s.push({value:i});return s}getLabelForValue(t){const e=this.getLabels();return t>=0&&te.length-1?null:this.getPixelForValue(e[t].value)}getValueForPixel(t){return Math.round(this._startValue+this.getDecimalForPixel(t)*this._valueRange)}getBasePixel(){return this.bottom}}function Ko(t,e,{horizontal:i,minRotation:s}){const n=It(s),o=(i?Math.sin(n):Math.cos(n))||.001,a=.75*e*(""+t).length;return Math.min(e/o,a)}qo.id="category",qo.defaults={ticks:{callback:qo.prototype.getLabelForValue}};class Go extends Bs{constructor(t){super(t),this.start=void 0,this.end=void 0,this._startValue=void 0,this._endValue=void 0,this._valueRange=0}parse(t,e){return $(t)||("number"==typeof t||t instanceof Number)&&!isFinite(+t)?null:+t}handleTickRangeOptions(){const{beginAtZero:t}=this.options,{minDefined:e,maxDefined:i}=this.getUserBounds();let{min:s,max:n}=this;const o=t=>s=e?s:t,a=t=>n=i?n:t;if(t){const t=Ct(s),e=Ct(n);t<0&&e<0?a(0):t>0&&e>0&&o(0)}if(s===n){let e=1;(n>=Number.MAX_SAFE_INTEGER||s<=Number.MIN_SAFE_INTEGER)&&(e=Math.abs(.05*n)),a(n+e),t||o(s-e)}this.min=s,this.max=n}getTickLimit(){const t=this.options.ticks;let e,{maxTicksLimit:i,stepSize:s}=t;return s?(e=Math.ceil(this.max/s)-Math.floor(this.min/s)+1,e>1e3&&(console.warn(`scales.${this.id}.ticks.stepSize: ${s} would result generating up to ${e} ticks. Limiting to 1000.`),e=1e3)):(e=this.computeTickLimit(),i=i||11),i&&(e=Math.min(i,e)),e}computeTickLimit(){return Number.POSITIVE_INFINITY}buildTicks(){const t=this.options,e=t.ticks;let i=this.getTickLimit();i=Math.max(2,i);const s=function(t,e){const i=[],{bounds:s,step:n,min:o,max:a,precision:r,count:l,maxTicks:h,maxDigits:c,includeBounds:d}=t,u=n||1,f=h-1,{min:g,max:p}=e,m=!$(o),x=!$(a),b=!$(l),_=(p-g)/(c+1);let y,v,w,M,k=Ot((p-g)/f/u)*u;if(k<1e-14&&!m&&!x)return[{value:g},{value:p}];M=Math.ceil(p/k)-Math.floor(g/k),M>f&&(k=Ot(M*k/f/u)*u),$(r)||(y=Math.pow(10,r),k=Math.ceil(k*y)/y),"ticks"===s?(v=Math.floor(g/k)*k,w=Math.ceil(p/k)*k):(v=g,w=p),m&&x&&n&&Rt((a-o)/n,k/1e3)?(M=Math.round(Math.min((a-o)/k,h)),k=(a-o)/M,v=o,w=a):b?(v=m?o:v,w=x?a:w,M=l-1,k=(w-v)/M):(M=(w-v)/k,M=Lt(M,Math.round(M),k/1e3)?Math.round(M):Math.ceil(M));const S=Math.max(Ft(k),Ft(v));y=Math.pow(10,$(r)?S:r),v=Math.round(v*y)/y,w=Math.round(w*y)/y;let P=0;for(m&&(d&&v!==o?(i.push({value:o}),v0?i:null;this._zero=!0}determineDataLimits(){const{min:t,max:e}=this.getMinMax(!0);this.min=X(t)?Math.max(0,t):null,this.max=X(e)?Math.max(0,e):null,this.options.beginAtZero&&(this._zero=!0),this.handleTickRangeOptions()}handleTickRangeOptions(){const{minDefined:t,maxDefined:e}=this.getUserBounds();let i=this.min,s=this.max;const n=e=>i=t?i:e,o=t=>s=e?s:t,a=(t,e)=>Math.pow(10,Math.floor(Dt(t))+e);i===s&&(i<=0?(n(1),o(10)):(n(a(i,-1)),o(a(s,1)))),i<=0&&n(a(s,-1)),s<=0&&o(a(i,1)),this._zero&&this.min!==this._suggestedMin&&i===a(this.min,0)&&n(a(i,-1)),this.min=i,this.max=s}buildTicks(){const t=this.options,e=function(t,e){const i=Math.floor(Dt(e.max)),s=Math.ceil(e.max/Math.pow(10,i)),n=[];let o=q(t.min,Math.pow(10,Math.floor(Dt(e.min)))),a=Math.floor(Dt(o)),r=Math.floor(o/Math.pow(10,a)),l=a<0?Math.pow(10,Math.abs(a)):1;do{n.push({value:o,major:Jo(o)}),++r,10===r&&(r=1,++a,l=a>=0?1:l),o=Math.round(r*Math.pow(10,a)*l)/l}while(an?{start:e-i,end:e}:{start:e,end:e+i}}function ia(t){const e={l:t.left+t._padding.left,r:t.right-t._padding.right,t:t.top+t._padding.top,b:t.bottom-t._padding.bottom},i=Object.assign({},e),s=[],n=[],o=t._pointLabels.length,a=t.options.pointLabels,r=a.centerPointLabels?_t/o:0;for(let d=0;de.r&&(r=(s.end-e.r)/o,t.r=Math.max(t.r,e.r+r)),n.starte.b&&(l=(n.end-e.b)/a,t.b=Math.max(t.b,e.b+l))}function na(t){return 0===t||180===t?"center":t<180?"left":"right"}function oa(t,e,i){return"right"===i?t-=e:"center"===i&&(t-=e/2),t}function aa(t,e,i){return 90===i||270===i?t-=e/2:(i>270||i<90)&&(t-=e),t}function ra(t,e,i,s){const{ctx:n}=t;if(i)n.arc(t.xCenter,t.yCenter,e,0,yt);else{let i=t.getPointPosition(0,e);n.moveTo(i.x,i.y);for(let o=1;o{const i=J(this.options.pointLabels.callback,[t,e],this);return i||0===i?i:""})).filter(((t,e)=>this.chart.getDataVisibility(e)))}fit(){const t=this.options;t.display&&t.pointLabels.display?ia(this):this.setCenterPoint(0,0,0,0)}setCenterPoint(t,e,i,s){this.xCenter+=Math.floor((t-e)/2),this.yCenter+=Math.floor((i-s)/2),this.drawingArea-=Math.min(this.drawingArea/2,Math.max(t,e,i,s))}getIndexAngle(t){return Nt(t*(yt/(this._pointLabels.length||1))+It(this.options.startAngle||0))}getDistanceFromCenterForValue(t){if($(t))return NaN;const e=this.drawingArea/(this.max-this.min);return this.options.reverse?(this.max-t)*e:(t-this.min)*e}getValueForDistanceFromCenter(t){if($(t))return NaN;const e=t/(this.drawingArea/(this.max-this.min));return this.options.reverse?this.max-e:this.min+e}getPointLabelContext(t){const e=this._pointLabels||[];if(t>=0&&t=0;n--){const e=s.setContext(t.getPointLabelContext(n)),o=He(e.font),{x:a,y:r,textAlign:l,left:h,top:c,right:d,bottom:u}=t._pointLabelItems[n],{backdropColor:f}=e;if(!$(f)){const t=Ne(e.backdropPadding);i.fillStyle=f,i.fillRect(h-t.left,c-t.top,d-h+t.width,u-c+t.height)}se(i,t._pointLabels[n],a,r+o.lineHeight/2,o,{color:e.color,textAlign:l,textBaseline:"middle"})}}(this,n),s.display&&this.ticks.forEach(((t,e)=>{if(0!==e){a=this.getDistanceFromCenterForValue(t.value);!function(t,e,i,s){const n=t.ctx,o=e.circular,{color:a,lineWidth:r}=e;!o&&!s||!a||!r||i<0||(n.save(),n.strokeStyle=a,n.lineWidth=r,n.setLineDash(e.borderDash),n.lineDashOffset=e.borderDashOffset,n.beginPath(),ra(t,i,o,s),n.closePath(),n.stroke(),n.restore())}(this,s.setContext(this.getContext(e-1)),a,n)}})),i.display){for(t.save(),o=n-1;o>=0;o--){const s=i.setContext(this.getPointLabelContext(o)),{color:n,lineWidth:l}=s;l&&n&&(t.lineWidth=l,t.strokeStyle=n,t.setLineDash(s.borderDash),t.lineDashOffset=s.borderDashOffset,a=this.getDistanceFromCenterForValue(e.ticks.reverse?this.min:this.max),r=this.getPointPosition(o,a),t.beginPath(),t.moveTo(this.xCenter,this.yCenter),t.lineTo(r.x,r.y),t.stroke())}t.restore()}}drawBorder(){}drawLabels(){const t=this.ctx,e=this.options,i=e.ticks;if(!i.display)return;const s=this.getIndexAngle(0);let n,o;t.save(),t.translate(this.xCenter,this.yCenter),t.rotate(s),t.textAlign="center",t.textBaseline="middle",this.ticks.forEach(((s,a)=>{if(0===a&&!e.reverse)return;const r=i.setContext(this.getContext(a)),l=He(r.font);if(n=this.getDistanceFromCenterForValue(this.ticks[a].value),r.showLabelBackdrop){t.font=l.string,o=t.measureText(s.label).width,t.fillStyle=r.backdropColor;const e=Ne(r.backdropPadding);t.fillRect(-o/2-e.left,-n-l.size/2-e.top,o+e.width,l.size+e.height)}se(t,s.label,0,-n,l,{color:r.color})})),t.restore()}drawTitle(){}}la.id="radialLinear",la.defaults={display:!0,animate:!0,position:"chartArea",angleLines:{display:!0,lineWidth:1,borderDash:[],borderDashOffset:0},grid:{circular:!1},startAngle:0,ticks:{showLabelBackdrop:!0,callback:Os.formatters.numeric},pointLabels:{backdropColor:void 0,backdropPadding:2,display:!0,font:{size:10},callback:t=>t,padding:5,centerPointLabels:!1}},la.defaultRoutes={"angleLines.color":"borderColor","pointLabels.color":"color","ticks.color":"color"},la.descriptors={angleLines:{_fallback:"grid"}};const ha={millisecond:{common:!0,size:1,steps:1e3},second:{common:!0,size:1e3,steps:60},minute:{common:!0,size:6e4,steps:60},hour:{common:!0,size:36e5,steps:24},day:{common:!0,size:864e5,steps:30},week:{common:!1,size:6048e5,steps:4},month:{common:!0,size:2628e6,steps:12},quarter:{common:!1,size:7884e6,steps:4},year:{common:!0,size:3154e7}},ca=Object.keys(ha);function da(t,e){return t-e}function ua(t,e){if($(e))return null;const i=t._adapter,{parser:s,round:n,isoWeekday:o}=t._parseOpts;let a=e;return"function"==typeof s&&(a=s(a)),X(a)||(a="string"==typeof s?i.parse(a,s):i.parse(a)),null===a?null:(n&&(a="week"!==n||!Tt(o)&&!0!==o?i.startOf(a,n):i.startOf(a,"isoWeek",o)),+a)}function fa(t,e,i,s){const n=ca.length;for(let o=ca.indexOf(t);o=e?i[s]:i[n]]=!0}}else t[e]=!0}function pa(t,e,i){const s=[],n={},o=e.length;let a,r;for(a=0;a=0&&(e[l].major=!0);return e}(t,s,n,i):s}class ma extends Bs{constructor(t){super(t),this._cache={data:[],labels:[],all:[]},this._unit="day",this._majorUnit=void 0,this._offsets={},this._normalized=!1,this._parseOpts=void 0}init(t,e){const i=t.time||(t.time={}),s=this._adapter=new mn._date(t.adapters.date);ot(i.displayFormats,s.formats()),this._parseOpts={parser:i.parser,round:i.round,isoWeekday:i.isoWeekday},super.init(t),this._normalized=e.normalized}parse(t,e){return void 0===t?null:ua(this,t)}beforeLayout(){super.beforeLayout(),this._cache={data:[],labels:[],all:[]}}determineDataLimits(){const t=this.options,e=this._adapter,i=t.time.unit||"day";let{min:s,max:n,minDefined:o,maxDefined:a}=this.getUserBounds();function r(t){o||isNaN(t.min)||(s=Math.min(s,t.min)),a||isNaN(t.max)||(n=Math.max(n,t.max))}o&&a||(r(this._getLabelBounds()),"ticks"===t.bounds&&"labels"===t.ticks.source||r(this.getMinMax(!1))),s=X(s)&&!isNaN(s)?s:+e.startOf(Date.now(),i),n=X(n)&&!isNaN(n)?n:+e.endOf(Date.now(),i)+1,this.min=Math.min(s,n-1),this.max=Math.max(s+1,n)}_getLabelBounds(){const t=this.getLabelTimestamps();let e=Number.POSITIVE_INFINITY,i=Number.NEGATIVE_INFINITY;return t.length&&(e=t[0],i=t[t.length-1]),{min:e,max:i}}buildTicks(){const t=this.options,e=t.time,i=t.ticks,s="labels"===i.source?this.getLabelTimestamps():this._generate();"ticks"===t.bounds&&s.length&&(this.min=this._userMin||s[0],this.max=this._userMax||s[s.length-1]);const n=this.min,o=he(s,n,this.max);return this._unit=e.unit||(i.autoSkip?fa(e.minUnit,this.min,this.max,this._getLabelCapacity(n)):function(t,e,i,s,n){for(let o=ca.length-1;o>=ca.indexOf(i);o--){const i=ca[o];if(ha[i].common&&t._adapter.diff(n,s,i)>=e-1)return i}return ca[i?ca.indexOf(i):0]}(this,o.length,e.minUnit,this.min,this.max)),this._majorUnit=i.major.enabled&&"year"!==this._unit?function(t){for(let e=ca.indexOf(t)+1,i=ca.length;e1e5*a)throw new Error(e+" and "+i+" are too far apart with stepSize of "+a+" "+o);const f="data"===s.ticks.source&&this.getDataTimestamps();for(c=u,d=0;ct-e)).map((t=>+t))}getLabelForValue(t){const e=this._adapter,i=this.options.time;return i.tooltipFormat?e.format(t,i.tooltipFormat):e.format(t,i.displayFormats.datetime)}_tickFormatFunction(t,e,i,s){const n=this.options,o=n.time.displayFormats,a=this._unit,r=this._majorUnit,l=a&&o[a],h=r&&o[r],c=i[e],d=r&&h&&c&&c.major,u=this._adapter.format(t,s||(d?h:l)),f=n.ticks.callback;return f?J(f,[u,e,i],this):u}generateTickLabels(t){let e,i,s;for(e=0,i=t.length;e0?a:1}getDataTimestamps(){let t,e,i=this._cache.data||[];if(i.length)return i;const s=this.getMatchingVisibleMetas();if(this._normalized&&s.length)return this._cache.data=s[0].controller.getAllParsedValues(this);for(t=0,e=s.length;t=t[r].pos&&e<=t[l].pos&&({lo:r,hi:l}=re(t,"pos",e)),({pos:s,time:o}=t[r]),({pos:n,time:a}=t[l])):(e>=t[r].time&&e<=t[l].time&&({lo:r,hi:l}=re(t,"time",e)),({time:s,pos:o}=t[r]),({time:n,pos:a}=t[l]));const h=n-s;return h?o+(a-o)*(e-s)/h:o}ma.id="time",ma.defaults={bounds:"data",adapters:{},time:{parser:!1,unit:!1,round:!1,isoWeekday:!1,minUnit:"millisecond",displayFormats:{}},ticks:{source:"auto",major:{enabled:!1}}};class ba extends ma{constructor(t){super(t),this._table=[],this._minPos=void 0,this._tableRange=void 0}initOffsets(){const t=this._getTimestampsForTable(),e=this._table=this.buildLookupTable(t);this._minPos=xa(e,this.min),this._tableRange=xa(e,this.max)-this._minPos,super.initOffsets(t)}buildLookupTable(t){const{min:e,max:i}=this,s=[],n=[];let o,a,r,l,h;for(o=0,a=t.length;o=e&&l<=i&&s.push(l);if(s.length<2)return[{time:e,pos:0},{time:i,pos:1}];for(o=0,a=s.length;oe.length)&&(t=e.length);for(var n=0,r=new Array(t);n=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var n=function(e){function t(){return e.apply(this,arguments)||this}return i(t,e),t}(t(Error)),d=function(t){function e(e){return t.call(this,"Invalid DateTime: "+e.toMessage())||this}return i(e,t),e}(n),h=function(t){function e(e){return t.call(this,"Invalid Interval: "+e.toMessage())||this}return i(e,t),e}(n),y=function(t){function e(e){return t.call(this,"Invalid Duration: "+e.toMessage())||this}return i(e,t),e}(n),T=function(e){function t(){return e.apply(this,arguments)||this}return i(t,e),t}(n),v=function(t){function e(e){return t.call(this,"Invalid unit "+e)||this}return i(e,t),e}(n),p=function(e){function t(){return e.apply(this,arguments)||this}return i(t,e),t}(n),m=function(e){function t(){return e.call(this,"Zone is an abstract class")||this}return i(t,e),t}(n),g="numeric",w="short",S="long",b={year:g,month:g,day:g},O={year:g,month:w,day:g},M={year:g,month:w,day:g,weekday:w},N={year:g,month:S,day:g},D={year:g,month:S,day:g,weekday:S},E={hour:g,minute:g},V={hour:g,minute:g,second:g},I={hour:g,minute:g,second:g,timeZoneName:w},x={hour:g,minute:g,second:g,timeZoneName:S},C={hour:g,minute:g,hourCycle:"h23"},F={hour:g,minute:g,second:g,hourCycle:"h23"},L={hour:g,minute:g,second:g,hourCycle:"h23",timeZoneName:w},Z={hour:g,minute:g,second:g,hourCycle:"h23",timeZoneName:S},A={year:g,month:g,day:g,hour:g,minute:g},z={year:g,month:g,day:g,hour:g,minute:g,second:g},j={year:g,month:w,day:g,hour:g,minute:g},q={year:g,month:w,day:g,hour:g,minute:g,second:g},_={year:g,month:w,day:g,weekday:w,hour:g,minute:g},U={year:g,month:S,day:g,hour:g,minute:g,timeZoneName:w},R={year:g,month:S,day:g,hour:g,minute:g,second:g,timeZoneName:w},H={year:g,month:S,day:g,weekday:S,hour:g,minute:g,timeZoneName:S},P={year:g,month:S,day:g,weekday:S,hour:g,minute:g,second:g,timeZoneName:S};function W(e){return void 0===e}function J(e){return"number"==typeof e}function Y(e){return"number"==typeof e&&e%1==0}function G(){try{return"undefined"!=typeof Intl&&!!Intl.RelativeTimeFormat}catch(e){return!1}}function $(e,n,r){if(0!==e.length)return e.reduce(function(e,t){t=[n(t),t];return e&&r(e[0],t[0])===e[0]?e:t},null)[1]}function B(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function Q(e,t,n){return Y(e)&&t<=e&&e<=n}function K(e,t){void 0===t&&(t=2);t=e<0?"-"+(""+-e).padStart(t,"0"):(""+e).padStart(t,"0");return t}function X(e){if(!W(e)&&null!==e&&""!==e)return parseInt(e,10)}function ee(e){if(!W(e)&&null!==e&&""!==e)return parseFloat(e)}function te(e){if(!W(e)&&null!==e&&""!==e){e=1e3*parseFloat("0."+e);return Math.floor(e)}}function ne(e,t,n){void 0===n&&(n=!1);t=Math.pow(10,t);return(n?Math.trunc:Math.round)(e*t)/t}function re(e){return e%4==0&&(e%100!=0||e%400==0)}function ie(e){return re(e)?366:365}function oe(e,t){var n,r=(n=t-1)-(r=12)*Math.floor(n/r)+1;return 2==r?re(e+(t-r)/12)?29:28:[31,null,31,30,31,30,31,31,30,31,30,31][r-1]}function ue(e){var t=Date.UTC(e.year,e.month-1,e.day,e.hour,e.minute,e.second,e.millisecond);return e.year<100&&0<=e.year&&(t=new Date(t)).setUTCFullYear(t.getUTCFullYear()-1900),+t}function ae(e){var t=(e+Math.floor(e/4)-Math.floor(e/100)+Math.floor(e/400))%7,e=e-1,e=(e+Math.floor(e/4)-Math.floor(e/100)+Math.floor(e/400))%7;return 4==t||3==e?53:52}function se(e){return 99Gt.indexOf(s)&&Qt(this.matrix,u,d,i,s)}else J(u[s])&&(o[s]=u[s])}for(r in o)0!==o[r]&&(i[l]+=r===l?o[r]:o[r]/this.matrix[l][r]);return Bt(this,{values:i},!0).normalize()},e.negate=function(){if(!this.isValid)return this;for(var e={},t=0,n=Object.keys(this.values);te},e.isBefore=function(e){return!!this.isValid&&this.e<=e},e.contains=function(e){return!!this.isValid&&(this.s<=e&&this.e>e)},e.set=function(e){var t=void 0===e?{}:e,e=t.start,t=t.end;return this.isValid?c.fromDateTimes(e||this.s,t||this.e):this},e.splitAt=function(){var t=this;if(!this.isValid)return[];for(var e=arguments.length,n=new Array(e),r=0;r+this.e?this.e:s;o.push(c.fromDateTimes(u,s)),u=s,a+=1}return o},e.splitBy=function(e){var t=Kt.fromDurationLike(e);if(!this.isValid||!t.isValid||0===t.as("milliseconds"))return[];for(var n=this.s,r=1,i=[];n+this.e?this.e:o;i.push(c.fromDateTimes(n,o)),n=o,r+=1}return i},e.divideEqually=function(e){return this.isValid?this.splitBy(this.length()/e).slice(0,e):[]},e.overlaps=function(e){return this.e>e.s&&this.s=e.e)},e.equals=function(e){return!(!this.isValid||!e.isValid)&&(this.s.equals(e.s)&&this.e.equals(e.e))},e.intersection=function(e){if(!this.isValid)return this;var t=(this.s>e.s?this:e).s,e=(this.ee.e?this:e).e;return c.fromDateTimes(t,e)},c.merge=function(e){var t=e.sort(function(e,t){return e.s-t.s}).reduce(function(e,t){var n=e[0],e=e[1];return e?e.overlaps(t)||e.abutsStart(t)?[n,e.union(t)]:[n.concat([e]),t]:[n,t]},[[],null]),e=t[0],t=t[1];return t&&e.push(t),e},c.xor=function(e){for(var t=null,n=0,r=[],i=e.map(function(e){return[{time:e.s,type:"s"},{time:e.e,type:"e"}]}),o=k((e=Array.prototype).concat.apply(e,i).sort(function(e,t){return e.time-t.time}));!(u=o()).done;)var u=u.value,t=1===(n+="s"===u.type?1:-1)?u.time:(t&&+t!=+u.time&&r.push(c.fromDateTimes(t,u.time)),null);return c.merge(r)},e.difference=function(){for(var t=this,e=arguments.length,n=new Array(e),r=0;rae(n)?(t=n+1,o=1):t=n,s({weekYear:t,weekNumber:o,weekday:i},me(e))}function In(e){var t,n=e.weekYear,r=e.weekNumber,i=e.weekday,o=Nn(n,1,4),u=ie(n),o=7*r+i-o-3;o<1?o+=ie(t=n-1):uthis.valueOf(),r=rn(n?this:e,n?e:this,t,r);return n?r.negate():r},e.diffNow=function(e,t){return void 0===e&&(e="milliseconds"),void 0===t&&(t={}),this.diff(w.now(),e,t)},e.until=function(e){return this.isValid?en.fromDateTimes(this,e):this},e.hasSame=function(e,t){if(!this.isValid)return!1;var n=e.valueOf(),e=this.setZone(e.zone,{keepLocalTime:!0});return e.startOf(t)<=n&&n<=e.endOf(t)},e.equals=function(e){return this.isValid&&e.isValid&&this.valueOf()===e.valueOf()&&this.zone.equals(e.zone)&&this.loc.equals(e.loc)},e.toRelative=function(e){if(!this.isValid)return null;var t=(e=void 0===e?{}:e).base||w.fromObject({},{zone:this.zone}),n=e.padding?thisthis.set({month:1}).offset||this.offset>this.set({month:5}).offset)}},{key:"isInLeapYear",get:function(){return re(this.year)}},{key:"daysInMonth",get:function(){return oe(this.year,this.month)}},{key:"daysInYear",get:function(){return this.isValid?ie(this.year):NaN}},{key:"weeksInWeekYear",get:function(){return this.isValid?ae(this.weekYear):NaN}}],[{key:"DATE_SHORT",get:function(){return b}},{key:"DATE_MED",get:function(){return O}},{key:"DATE_MED_WITH_WEEKDAY",get:function(){return M}},{key:"DATE_FULL",get:function(){return N}},{key:"DATE_HUGE",get:function(){return D}},{key:"TIME_SIMPLE",get:function(){return E}},{key:"TIME_WITH_SECONDS",get:function(){return V}},{key:"TIME_WITH_SHORT_OFFSET",get:function(){return I}},{key:"TIME_WITH_LONG_OFFSET",get:function(){return x}},{key:"TIME_24_SIMPLE",get:function(){return C}},{key:"TIME_24_WITH_SECONDS",get:function(){return F}},{key:"TIME_24_WITH_SHORT_OFFSET",get:function(){return L}},{key:"TIME_24_WITH_LONG_OFFSET",get:function(){return Z}},{key:"DATETIME_SHORT",get:function(){return A}},{key:"DATETIME_SHORT_WITH_SECONDS",get:function(){return z}},{key:"DATETIME_MED",get:function(){return j}},{key:"DATETIME_MED_WITH_SECONDS",get:function(){return q}},{key:"DATETIME_MED_WITH_WEEKDAY",get:function(){return _}},{key:"DATETIME_FULL",get:function(){return U}},{key:"DATETIME_FULL_WITH_SECONDS",get:function(){return R}},{key:"DATETIME_HUGE",get:function(){return H}},{key:"DATETIME_HUGE_WITH_SECONDS",get:function(){return P}}]),w}();function ir(e){if(rr.isDateTime(e))return e;if(e&&e.valueOf&&J(e.valueOf()))return rr.fromJSDate(e);if(e&&"object"==typeof e)return rr.fromObject(e);throw new p("Unknown datetime argument: "+e+", of type "+typeof e)}return e.DateTime=rr,e.Duration=Kt,e.FixedOffsetZone=_e,e.IANAZone=je,e.Info=tn,e.Interval=en,e.InvalidZone=Ue,e.Settings=$e,e.SystemZone=Le,e.VERSION="2.3.2",e.Zone=Ce,Object.defineProperty(e,"__esModule",{value:!0}),e}({}); +|] + +chartJSAdapter :: String +chartJSAdapter = [s| +/*! + * chartjs-adapter-luxon v1.1.0 + * https://www.chartjs.org + * (c) 2021 chartjs-adapter-luxon Contributors + * Released under the MIT license + */ +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(require("chart.js"),require("luxon")):"function"==typeof define&&define.amd?define(["chart.js","luxon"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Chart,e.luxon)}(this,(function(e,t){"use strict";const n={datetime:t.DateTime.DATETIME_MED_WITH_SECONDS,millisecond:"h:mm:ss.SSS a",second:t.DateTime.TIME_WITH_SECONDS,minute:t.DateTime.TIME_SIMPLE,hour:{hour:"numeric"},day:{day:"numeric",month:"short"},week:"DD",month:{month:"short",year:"numeric"},quarter:"'Q'q - yyyy",year:{year:"numeric"}};e._adapters._date.override({_id:"luxon",_create:function(e){return t.DateTime.fromMillis(e,this.options)},formats:function(){return n},parse:function(e,n){const r=this.options;if(null==e)return null;const i=typeof e;return"number"===i?e=this._create(e):"string"===i?e="string"==typeof n?t.DateTime.fromFormat(e,n,r):t.DateTime.fromISO(e,r):e instanceof Date?e=t.DateTime.fromJSDate(e,r):"object"!==i||e instanceof t.DateTime||(e=t.DateTime.fromObject(e)),e.isValid?e.valueOf():null},format:function(e,t){const n=this._create(e);return"string"==typeof t?n.toFormat(t,this.options):n.toLocaleString(t)},add:function(e,t,n){const r={};return r[n]=t,this._create(e).plus(r).valueOf()},diff:function(e,t,n){return this._create(e).diff(this._create(t)).as(n).valueOf()},startOf:function(e,t,n){if("isoWeek"===t){n=Math.trunc(Math.min(Math.max(0,n),6));const t=this._create(e);return t.minus({days:(t.weekday-n+7)%7}).startOf("day").valueOf()}return t?this._create(e).startOf(t).valueOf():e},endOf:function(e,t){return this._create(e).endOf(t).valueOf()}})})); +|] + +chartJSPluginZoom :: String +chartJSPluginZoom = [s| +/*! +* chartjs-plugin-zoom v1.2.1 +* undefined + * (c) 2016-2022 chartjs-plugin-zoom Contributors + * Released under the MIT License + */ +!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n(require("chart.js"),require("hammerjs"),require("chart.js/helpers")):"function"==typeof define&&define.amd?define(["chart.js","hammerjs","chart.js/helpers"],n):(e="undefined"!=typeof globalThis?globalThis:e||self).ChartZoom=n(e.Chart,e.Hammer,e.Chart.helpers)}(this,(function(e,n,t){"use strict";function o(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var a=o(n);const i=e=>e&&e.enabled&&e.modifierKey,c=(e,n)=>e&&n[e+"Key"],r=(e,n)=>e&&!n[e+"Key"];function l(e,n,t){return void 0===e||("string"==typeof e?-1!==e.indexOf(n):"function"==typeof e&&-1!==e({chart:t}).indexOf(n))}function s(e,n,o){const a=function({x:e,y:n},t){const o=t.scales,a=Object.keys(o);for(let t=0;t=i.top&&n<=i.bottom&&e>=i.left&&e<=i.right)return i}return null}(n,o);if(a&&l(e,a.axis,o))return[a];const i=[];return t.each(o.scales,(function(n){l(e,n.axis,o)||i.push(n)})),i}const m=new WeakMap;function u(e){let n=m.get(e);return n||(n={originalScaleLimits:{},updatedScaleLimits:{},handlers:{},panDelta:{}},m.set(e,n)),n}function d(e,n,t){const o=e.max-e.min,a=o*(n-1),i=e.isHorizontal()?t.x:t.y,c=Math.max(0,Math.min(1,(e.getValueForPixel(i)-e.min)/o||0));return{min:a*c,max:a*(1-c)}}function f(e,n,o,a,i){let c=o[a];if("original"===c){const o=e.originalScaleLimits[n.id][a];c=t.valueOrDefault(o.options,o.scale)}return t.valueOrDefault(c,i)}function h(e,{min:n,max:t},o,a=!1){const i=u(e.chart),{id:c,axis:r,options:l}=e,s=o&&(o[c]||o[r])||{},{minRange:m=0}=s,d=f(i,e,s,"min",-1/0),h=f(i,e,s,"max",1/0),p=Math.max(n,d),x=Math.min(t,h),g=a?Math.max(x-p,m):e.max-e.min;if(x-p!==g)if(d>x-g)n=p,t=p+g;else if(h0===e||isNaN(e)?0:e<0?Math.min(Math.round(e),-1):Math.max(Math.round(e),1);const x={second:500,minute:3e4,hour:18e5,day:432e5,week:3024e5,month:1296e6,quarter:5184e6,year:157248e5};function g(e,n,t,o=!1){const{min:a,max:i,options:c}=e,r=c.time&&c.time.round,l=x[r]||0,s=e.getValueForPixel(e.getPixelForValue(a+l)-n),m=e.getValueForPixel(e.getPixelForValue(i+l)-n),{min:u=-1/0,max:d=1/0}=o&&t&&t[e.axis]||{};return!!(isNaN(s)||isNaN(m)||sd)||h(e,{min:s,max:m},t,o)}function b(e,n,t){return g(e,n,t,!0)}const y={category:function(e,n,t,o){const a=d(e,n,t);return e.min===e.max&&n<1&&function(e){const n=e.getLabels().length-1;e.min>0&&(e.min-=1),e.maxr&&(a=Math.max(0,a-l),i=1===c?a:a+c,s=0===a),h(e,{min:a,max:i},t)||s},default:g,logarithmic:b,timeseries:b};function z(e,n){t.each(e,((t,o)=>{n[o]||delete e[o]}))}function M(e,n){const{scales:o}=e,{originalScaleLimits:a,updatedScaleLimits:i}=n;return t.each(o,(function(e){(function(e,n,t){const{id:o,options:{min:a,max:i}}=e;if(!n[o]||!t[o])return!0;const c=t[o];return c.min!==a||c.max!==i})(e,a,i)&&(a[e.id]={min:{scale:e.min,options:e.options.min},max:{scale:e.max,options:e.options.max}})})),z(a,o),z(i,o),a}function k(e,n,o,a){const i=y[e.type]||y.default;t.callback(i,[e,n,o,a])}function w(e){const n=e.chartArea;return{x:(n.left+n.right)/2,y:(n.top+n.bottom)/2}}function S(e,n,o="none"){const{x:a=1,y:i=1,focalPoint:c=w(e)}="number"==typeof n?{x:n,y:n}:n,r=u(e),{options:{limits:m,zoom:d}}=r,{mode:f="xy",overScaleMode:h}=d||{};M(e,r);const p=1!==a&&l(f,"x",e),x=1!==i&&l(f,"y",e),g=h&&s(h,c,e);t.each(g||e.scales,(function(e){e.isHorizontal()&&p?k(e,a,c,m):!e.isHorizontal()&&x&&k(e,i,c,m)})),e.update(o),t.callback(d.onZoom,[{chart:e}])}function P(e,n,t){const o=e.getValueForPixel(n),a=e.getValueForPixel(t);return{min:Math.min(o,a),max:Math.max(o,a)}}function C(e){const n=u(e);let o=1,a=1;return t.each(e.scales,(function(e){const i=function(e,n){const o=e.originalScaleLimits[n];if(!o)return;const{min:a,max:i}=o;return t.valueOrDefault(i.options,i.scale)-t.valueOrDefault(a.options,a.scale)}(n,e.id);if(i){const n=Math.round(i/(e.max-e.min)*100)/100;o=Math.min(o,n),a=Math.max(a,n)}})),o<1?o:a}function j(e,n,o,a){const{panDelta:i}=a,c=i[e.id]||0;t.sign(c)===t.sign(n)&&(n+=c);const r=v[e.type]||v.default;t.callback(r,[e,n,o])?i[e.id]=0:i[e.id]=n}function Z(e,n,o,a="none"){const{x:i=0,y:c=0}="number"==typeof n?{x:n,y:n}:n,r=u(e),{options:{pan:s,limits:m}}=r,{mode:d="xy",onPan:f}=s||{};M(e,r);const h=0!==i&&l(d,"x",e),p=0!==c&&l(d,"y",e);t.each(o||e.scales,(function(e){e.isHorizontal()&&h?j(e,i,m,r):!e.isHorizontal()&&p&&j(e,c,m,r)})),e.update(a),t.callback(f,[{chart:e}])}function L(e){const n=u(e),t={};for(const o of Object.keys(e.scales)){const{min:e,max:a}=n.originalScaleLimits[o]||{min:{},max:{}};t[o]={min:e.scale,max:a.scale}}return t}function R(e,n){const{handlers:t}=u(e),o=t[n];o&&o.target&&(o.target.removeEventListener(n,o),delete t[n])}function Y(e,n,t,o){const{handlers:a,options:i}=u(e),c=a[t];c&&c.target===n||(R(e,t),a[t]=n=>o(e,n,i),a[t].target=n,n.addEventListener(t,a[t]))}function O(e,n){const t=u(e);t.dragStart&&(t.dragging=!0,t.dragEnd=n,e.update("none"))}function T(e,n,o){const{onZoomStart:a,onZoomRejected:i}=o;if(a){const{left:o,top:c}=n.target.getBoundingClientRect(),r={x:n.clientX-o,y:n.clientY-c};if(!1===t.callback(a,[{chart:e,event:n,point:r}]))return t.callback(i,[{chart:e,event:n}]),!1}}function X(e,n){const o=u(e),{pan:a,zoom:l={}}=o.options;if(c(i(a),n)||r(i(l.drag),n))return t.callback(l.onZoomRejected,[{chart:e,event:n}]);!1!==T(e,n,l)&&(o.dragStart=n,Y(e,e.canvas,"mousemove",O))}function D(e,n,t,o){const{left:a,top:i}=t.target.getBoundingClientRect(),c=l(n,"x",e),r=l(n,"y",e);let{top:s,left:m,right:u,bottom:d,width:f,height:h}=e.chartArea;c&&(m=Math.min(t.clientX,o.clientX)-a,u=Math.max(t.clientX,o.clientX)-a),r&&(s=Math.min(t.clientY,o.clientY)-i,d=Math.max(t.clientY,o.clientY)-i);const p=u-m,x=d-s;return{left:m,top:s,right:u,bottom:d,width:p,height:x,zoomX:c&&p?1+(f-p)/f:1,zoomY:r&&x?1+(h-x)/h:1}}function E(e,n){const o=u(e);if(!o.dragStart)return;R(e,"mousemove");const{mode:a,onZoomComplete:i,drag:{threshold:c=0}}=o.options.zoom,r=D(e,a,o.dragStart,n),s=l(a,"x",e)?r.width:0,m=l(a,"y",e)?r.height:0,d=Math.sqrt(s*s+m*m);if(o.dragStart=o.dragEnd=null,d<=c)return o.dragging=!1,void e.update("none");!function(e,n,o,a="none"){const i=u(e),{options:{limits:c,zoom:r}}=i,{mode:s="xy"}=r;M(e,i);const m=l(s,"x",e),d=l(s,"y",e);t.each(e.scales,(function(e){e.isHorizontal()&&m?h(e,P(e,n.x,o.x),c,!0):!e.isHorizontal()&&d&&h(e,P(e,n.y,o.y),c,!0)})),e.update(a),t.callback(r.onZoom,[{chart:e}])}(e,{x:r.left,y:r.top},{x:r.right,y:r.bottom},"zoom"),setTimeout((()=>o.dragging=!1),500),t.callback(i,[{chart:e}])}function F(e,n){const{handlers:{onZoomComplete:o},options:{zoom:a}}=u(e);if(!function(e,n,o){if(r(i(o.wheel),n))t.callback(o.onZoomRejected,[{chart:e,event:n}]);else if(!1!==T(e,n,o)&&(n.cancelable&&n.preventDefault(),void 0!==n.deltaY))return!0}(e,n,a))return;const c=n.target.getBoundingClientRect(),l=1+(n.deltaY>=0?-a.wheel.speed:a.wheel.speed);S(e,{x:l,y:l,focalPoint:{x:n.clientX-c.left,y:n.clientY-c.top}}),o&&o()}function H(e,n,o,a){o&&(u(e).handlers[n]=function(e,n){let t;return function(){return clearTimeout(t),t=setTimeout(e,n),n}}((()=>t.callback(o,[{chart:e}])),a))}function V(e,n){return function(o,a){const{pan:l,zoom:s={}}=n.options;if(!l||!l.enabled)return!1;const m=a&&a.srcEvent;return!m||(!(!n.panning&&"mouse"===a.pointerType&&(r(i(l),m)||c(i(s.drag),m)))||(t.callback(l.onPanRejected,[{chart:e,event:a}]),!1))}}function B(e,n,t){if(n.scale){const{center:o,pointers:a}=t,i=1/n.scale*t.scale,c=t.target.getBoundingClientRect(),r=function(e,n){const t=Math.abs(e.clientX-n.clientX),o=Math.abs(e.clientY-n.clientY),a=t/o;let i,c;return a>.3&&a<1.7?i=c=!0:t>o?i=!0:c=!0,{x:i,y:c}}(a[0],a[1]),s=n.options.zoom.mode;S(e,{x:r.x&&l(s,"x",e)?i:1,y:r.y&&l(s,"y",e)?i:1,focalPoint:{x:o.x-c.left,y:o.y-c.top}}),n.scale=t.scale}}function K(e,n,t){const o=n.delta;o&&(n.panning=!0,Z(e,{x:t.deltaX-o.x,y:t.deltaY-o.y},n.panScales),n.delta={x:t.deltaX,y:t.deltaY})}const N=new WeakMap;function q(e,n){const o=u(e),i=e.canvas,{pan:c,zoom:r}=n,l=new a.default.Manager(i);r&&r.pinch.enabled&&(l.add(new a.default.Pinch),l.on("pinchstart",(()=>function(e,n){n.options.zoom.pinch.enabled&&(n.scale=1)}(0,o))),l.on("pinch",(n=>B(e,o,n))),l.on("pinchend",(n=>function(e,n,o){n.scale&&(B(e,n,o),n.scale=null,t.callback(n.options.zoom.onZoomComplete,[{chart:e}]))}(e,o,n)))),c&&c.enabled&&(l.add(new a.default.Pan({threshold:c.threshold,enable:V(e,o)})),l.on("panstart",(n=>function(e,n,o){const{enabled:a,overScaleMode:i,onPanStart:c,onPanRejected:r}=n.options.pan;if(!a)return;const l=o.target.getBoundingClientRect(),m={x:o.center.x-l.left,y:o.center.y-l.top};if(!1===t.callback(c,[{chart:e,event:o,point:m}]))return t.callback(r,[{chart:e,event:o}]);n.panScales=i&&s(i,m,e),n.delta={x:0,y:0},clearTimeout(n.panEndTimeout),K(e,n,o)}(e,o,n))),l.on("panmove",(n=>K(e,o,n))),l.on("panend",(()=>function(e,n){n.delta=null,n.panning&&(n.panEndTimeout=setTimeout((()=>n.panning=!1),500),t.callback(n.options.pan.onPanComplete,[{chart:e}]))}(e,o)))),N.set(e,l)}var W={id:"zoom",version:"1.2.1",defaults:{pan:{enabled:!1,mode:"xy",threshold:10,modifierKey:null},zoom:{wheel:{enabled:!1,speed:.1,modifierKey:null},drag:{enabled:!1,modifierKey:null},pinch:{enabled:!1},mode:"xy"}},start:function(e,n,o){u(e).options=o,Object.prototype.hasOwnProperty.call(o.zoom,"enabled")&&console.warn("The option `zoom.enabled` is no longer supported. Please use `zoom.wheel.enabled`, `zoom.drag.enabled`, or `zoom.pinch.enabled`."),a.default&&q(e,o),e.pan=(n,t,o)=>Z(e,n,t,o),e.zoom=(n,t)=>S(e,n,t),e.zoomScale=(n,t,o)=>function(e,n,t,o="none"){M(e,u(e)),h(e.scales[n],t,void 0,!0),e.update(o)}(e,n,t,o),e.resetZoom=n=>function(e,n="default"){const o=u(e),a=M(e,o);t.each(e.scales,(function(e){const n=e.options;a[e.id]?(n.min=a[e.id].min.options,n.max=a[e.id].max.options):(delete n.min,delete n.max)})),e.update(n),t.callback(o.options.zoom.onZoomComplete,[{chart:e}])}(e,n),e.getZoomLevel=()=>C(e),e.getInitialScaleBounds=()=>L(e),e.isZoomedOrPanned=()=>function(e){const n=L(e);for(const t of Object.keys(e.scales)){const{min:o,max:a}=n[t];if(void 0!==o&&e.scales[t].min!==o)return!0;if(void 0!==a&&e.scales[t].max!==a)return!0}return!1}(e)},beforeEvent(e){const n=u(e);if(n.panning||n.dragging)return!1},beforeUpdate:function(e,n,t){u(e).options=t,function(e,n){const t=e.canvas,{wheel:o,drag:a,onZoomComplete:i}=n.zoom;o.enabled?(Y(e,t,"wheel",F),H(e,"onZoomComplete",i,250)):R(e,"wheel"),a.enabled?(Y(e,t,"mousedown",X),Y(e,t.ownerDocument,"mouseup",E)):(R(e,"mousedown"),R(e,"mousemove"),R(e,"mouseup"))}(e,t)},beforeDatasetsDraw:function(e,n,t){const{dragStart:o,dragEnd:a}=u(e);if(a){const{left:n,top:i,width:c,height:r}=D(e,t.zoom.mode,o,a),l=t.zoom.drag,s=e.ctx;s.save(),s.beginPath(),s.fillStyle=l.backgroundColor||"rgba(225,225,225,0.3)",s.fillRect(n,i,c,r),l.borderWidth>0&&(s.lineWidth=l.borderWidth,s.strokeStyle=l.borderColor||"rgba(225,225,225)",s.strokeRect(n,i,c,r)),s.restore()}},stop:function(e){!function(e){R(e,"mousedown"),R(e,"mousemove"),R(e,"mouseup"),R(e,"wheel"),R(e,"click")}(e),a.default&&function(e){const n=N.get(e);n&&(n.remove("pinchstart"),n.remove("pinch"),n.remove("pinchend"),n.remove("panstart"),n.remove("pan"),n.remove("panend"),n.destroy(),N.delete(e))}(e),function(e){m.delete(e)}(e)},panFunctions:v,zoomFunctions:y};return e.Chart.register(W),W})); +|] diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/JS/Charts.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/JS/Charts.hs new file mode 100644 index 00000000000..ab0d115a1cb --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/JS/Charts.hs @@ -0,0 +1,234 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE MultiWayIf #-} +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE ScopedTypeVariables #-} + +module Cardano.Tracer.Handlers.RTView.UI.JS.Charts + ( prepareChartsJS + , addDatasetChartJS + , addAllPointsChartJS + , getDatasetsLengthChartJS + , newTimeChartJS + , resetZoomChartJS + , changeColorsChartJS + , setTimeRange + ) where + +import Data.List (intercalate) +import Data.String.QQ +import Data.Text (Text) +import Data.Time.Clock.System +import Data.Word (Word16) +import qualified Graphics.UI.Threepenny as UI +import Graphics.UI.Threepenny.Core + +import Cardano.Tracer.Handlers.RTView.State.Historical +import Cardano.Tracer.Handlers.RTView.UI.Types +import Cardano.Tracer.Handlers.RTView.Update.Utils + +prepareChartsJS :: UI () +prepareChartsJS = + UI.runFunction $ UI.ffi "window.charts = new Map();" + +newTimeChartJS + :: ChartId + -> String + -> UI () +newTimeChartJS chartId yValuesLabel = + UI.runFunction $ UI.ffi newTimeChartJS' (show chartId) yValuesLabel + +newTimeChartJS' :: String +newTimeChartJS' = [s| +var ctx = document.getElementById(%1).getContext('2d'); +var chart = new Chart(ctx, { + type: 'line', + data: { + datasets: [] + }, + options: { + parsing: false, + animation: false, + normalized: true, + showLine: true, + spanGaps: false, + interaction: { + intersect: false, + mode: 'index', + axis: 'xy' + }, + elements: { + point: { + radius: 0 + }, + line: { + borderWidth: 2 + } + }, + responsive: true, + plugins: { + zoom: { + zoom: { + drag: { + enabled: false + }, + mode: 'x' + } + } + }, + transitions: { + zoom: { + animation: { + duration: 0 + } + } + }, + scales: { + x: { + type: 'time', + title: { + display: true, + text: 'Time in UTC' + }, + time: { + displayFormats: { + second: 'HH:mm:ss', + minute: 'HH:mm', + hour: 'hh a', + day: 'MMM D YYYY' + }, + unit: 'minute' + }, + adapters: { + date: { + zone: 'UTC' + } + }, + ticks: { + maxRotation: 0 + } + }, + y: { + title: { + display: true, + text: %2 + } + } + } + } +}); +window.charts.set(%1, chart); +|] + +addDatasetChartJS + :: ChartId + -> Text + -> Color + -> UI () +addDatasetChartJS chartId nodeName (Color color) = + UI.runFunction $ UI.ffi addDatasetChartJS' (show chartId) nodeName color + +addDatasetChartJS' :: String +addDatasetChartJS' = [s| +const newDataset = { + label: %2, + backgroundColor: %3, + borderColor: %3, + data: [], + fill: false +}; +var chart = window.charts.get(%1); +chart.data.datasets.push(newDataset); +chart.update(); +|] + +getDatasetsLengthChartJS :: ChartId -> UI Word16 +getDatasetsLengthChartJS chartId = do + (l :: Int) <- UI.callFunction $ UI.ffi "window.charts.get(%1).data.datasets.length;" (show chartId) + return $ fromIntegral l + +addAllPointsChartJS + :: ChartId + -> [(Index, [HistoricalPoint])] + -> UI () +addAllPointsChartJS chartId datasetIxsWithPoints = + UI.runFunction $ UI.ffi pushToAllDatasets + where + pushToAllDatasets = + "var chart = window.charts.get('" <> show chartId <> "');" + <> concatMap pushToDataset datasetIxsWithPoints + <> "chart.update();" + + pushToDataset (Index ix, points) = + "chart.data.datasets[" <> show ix <> "].data.push(" <> prepareArrayToPush points <> ");" + + prepareArrayToPush [] = "" + prepareArrayToPush points = intercalate ", " $ map mkArray points + where + mkArray (ts, valueH) = + let !tsInMs = ts * 1000 -- ChartJS uses milliseconds since epoch as internal format. + in "{x: " <> show tsInMs <> ", y: " <> show valueH <> "}" + +resetZoomChartJS :: ChartId -> UI () +resetZoomChartJS chartId = + UI.runFunction $ UI.ffi "window.charts.get(%1).resetZoom();" (show chartId) + +changeColorsChartJS + :: ChartId + -> Color + -> Color + -> UI () +changeColorsChartJS chartId (Color textColor) (Color gridColor) = + UI.runFunction $ UI.ffi changeColorsChartJS' (show chartId) textColor gridColor + +changeColorsChartJS' :: String +changeColorsChartJS' = [s| +var chart = window.charts.get(%1); +chart.options.scales.x.ticks.color = %2; +chart.options.scales.x.title.color = %2; +chart.options.scales.x.grid.color = %3; +chart.options.scales.y.ticks.color = %2; +chart.options.scales.y.title.color = %2; +chart.options.scales.y.grid.color = %3; +chart.options.plugins.title.color = %2; +chart.options.plugins.legend.title.color = %2; +chart.update(); +|] + +setTimeRange + :: ChartId + -> Int + -> UI () +setTimeRange chartId rangeInSec = do + now <- liftIO $ systemToUTCTime <$> getSystemTime + let !rangeInMs = rangeInSec * 1000 + !maxInMs = utc2s now * 1000 + !minInMs = maxInMs - fromIntegral rangeInMs + -- Set time units depends on selected range. + let timeUnit = if | rangeInSec == 0 -> "hour" + | rangeInSec > 0 && rangeInSec <= 300 -> "second" + | rangeInSec > 300 && rangeInSec <= 1800 -> "minute" + | otherwise -> "hour" + if rangeInSec == 0 + then + -- Show all time, no zoom, so just set time units. + UI.runFunction $ UI.ffi setUnitsChartJS (show chartId) timeUnit + else + UI.runFunction $ UI.ffi zoomScaleAndUnitsChartJS (show chartId) + (fromIntegral minInMs :: Int) + (fromIntegral maxInMs :: Int) + timeUnit + +setUnitsChartJS :: String +setUnitsChartJS = [s| +var chart = window.charts.get(%1); +chart.options.scales.x.time.unit = %2; +chart.update(); +|] + +zoomScaleAndUnitsChartJS :: String +zoomScaleAndUnitsChartJS = [s| +var chart = window.charts.get(%1); +chart.zoomScale('x', {min: %2, max: %3}); +chart.options.scales.x.time.unit = %4; +chart.update(); +|] diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/JS/Utils.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/JS/Utils.hs new file mode 100644 index 00000000000..f54f3f397de --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/JS/Utils.hs @@ -0,0 +1,61 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Cardano.Tracer.Handlers.RTView.UI.JS.Utils + ( closeModalsByEscapeButton + , copyTextToClipboard + , downloadCSVFile + , selectOption + ) where + +import Data.String.QQ +import Graphics.UI.Threepenny.Core +import qualified Graphics.UI.Threepenny as UI + +copyTextToClipboard :: String -> UI () +copyTextToClipboard textToCopy = + UI.runFunction $ UI.ffi copyTextToClipboard' textToCopy + +copyTextToClipboard' :: String +copyTextToClipboard' = [s| +const listener = function(ev) { + ev.preventDefault(); + ev.clipboardData.setData('text/plain', %1); +}; +document.addEventListener('copy', listener); +document.execCommand('copy'); +document.removeEventListener('copy', listener); +|] + +downloadCSVFile :: String +downloadCSVFile = [s| +var element = document.createElement('a'); +element.setAttribute('href', 'data:application/csv;charset=utf-8,' + %2); +element.setAttribute('download', %1); +element.style.display = 'none'; +document.body.appendChild(element); +element.click(); +document.body.removeChild(element); +|] + +selectOption + :: String + -> Int + -> UI () +selectOption selectId optionValue = + UI.runFunction $ UI.ffi "document.getElementById(%1).value = %2;" selectId optionValue + +closeModalsByEscapeButton :: UI () +closeModalsByEscapeButton = + UI.runFunction $ UI.ffi closeModalsByEscapeButton' + +closeModalsByEscapeButton' :: String +closeModalsByEscapeButton' = [s| +$(document).keydown(function(event) { + if (event.keyCode == 27) { + var modals = document.getElementsByClassName("modal is-active"); + for (var i=0; i < modals.length; i++) { + modals[i].className = "modal"; + } + } +}); +|] diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/Theme.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/Theme.hs new file mode 100644 index 00000000000..83ba8936471 --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/Theme.hs @@ -0,0 +1,78 @@ +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} + +module Cardano.Tracer.Handlers.RTView.UI.Theme + ( lightState + , darkState + , restoreTheme + , switchTheme + , isCurrentThemeDark + ) where + +import Control.Exception.Extra (ignore, try_) +import Control.Monad (void) +import qualified Data.Text as T +import qualified Data.Text.IO as TIO +import qualified Graphics.UI.Threepenny as UI +import Graphics.UI.Threepenny.Core + +import Cardano.Tracer.Handlers.RTView.System +import Cardano.Tracer.Handlers.RTView.UI.Img.Icons +import Cardano.Tracer.Handlers.RTView.UI.Charts +import Cardano.Tracer.Handlers.RTView.UI.Utils + +restoreTheme, switchTheme :: UI.Window -> UI () +restoreTheme window = readSavedTheme >>= setThemeAndSave window +switchTheme window = readSavedTheme >>= setThemeAndSave window . switch + where + switch s = if s == darkState then lightState else darkState + +isCurrentThemeDark :: UI Bool +isCurrentThemeDark = (== darkState) <$> readSavedTheme + +setThemeAndSave + :: UI.Window + -> String + -> UI () +setThemeAndSave window themeToSet = do + changeThemeIcon + changeBodyClass + changeCharts + saveTheme themeToSet + where + toBeLight = themeToSet == lightState + + changeThemeIcon = do + change "theme-icon" $ + set html (if toBeLight then rtViewThemeToDarkSVG else rtViewThemeToLightSVG) + . set dataState (if toBeLight then lightState else darkState) + . set dataTooltip ("Switch to " <> (if toBeLight then "dark" else "light") <> " theme") + + changeBodyClass = + getElementsByTagName window "body" >>= \case + [body] -> void $ element body # set UI.class_ (if toBeLight then lightState else darkState) + _ -> return () + + change elId what = findAndSet what window elId + + changeCharts = + if toBeLight + then changeChartsToLightTheme + else changeChartsToDarkTheme + +lightState, darkState :: String +lightState = "light" +darkState = "dark" + +-- | Every time when the user changed the theme, it should be saved on the file +-- for next sessions, both after web-page reload and 'cardano-tracer' restart. +saveTheme :: String -> UI () +saveTheme state = liftIO . ignore $ do + pathToThemeConfig <- getPathToThemeConfig + TIO.writeFile pathToThemeConfig $ T.pack state + +readSavedTheme :: UI String +readSavedTheme = liftIO $ + try_ (TIO.readFile =<< getPathToThemeConfig) >>= \case + Right saved -> return $ T.unpack saved + Left _ -> return darkState -- Use dark theme by default. diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/Types.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/Types.hs new file mode 100644 index 00000000000..22cdb78e4bb --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/Types.hs @@ -0,0 +1,84 @@ +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE DeriveGeneric #-} + +module Cardano.Tracer.Handlers.RTView.UI.Types + ( ChartId (..) + , ChartSelectId (..) + , ChartSettings (..) + , ChartsSettings + , Color (..) + , Colors + , DatasetsIndices + , DatasetsTimestamps + , Index (..) + ) where + +import Control.Concurrent.STM.TBQueue (TBQueue) +import Control.Concurrent.STM.TVar (TVar) +import Data.Aeson (FromJSON, ToJSON) +import Data.Map.Strict (Map) +import Data.Word (Word16) +import GHC.Generics (Generic) + +import Cardano.Tracer.Types (NodeId (..)) +import Cardano.Tracer.Handlers.RTView.State.Historical + +data ChartId + = CPUChart + | MemoryChart + | GCMajorNumChart + | GCMinorNumChart + | GCLiveMemoryChart + | CPUTimeGCChart + | CPUTimeAppChart + | ThreadsNumChart + -- Chain + | ChainDensityChart + | SlotNumChart + | BlockNumChart + | SlotInEpochChart + | EpochChart + | NodeCannotForgeChart + | ForgedSlotLastChart + | NodeIsLeaderChart + | NodeIsNotLeaderChart + | ForgedInvalidSlotLastChart + | AdoptedSlotLastChart + | NotAdoptedSlotLastChart + | AboutToLeadSlotLastChart + | CouldNotForgeSlotLastChart + -- TX + | TxsProcessedNumChart + | MempoolBytesChart + | TxsInMempoolChart + deriving (Bounded, Enum, Generic, FromJSON, ToJSON, Show) + +data ChartSelectId + = TimeRangeSelect + | UpdatePeriodSelect + deriving Show + +newtype Index = Index Word16 + deriving Show + +data ChartSettings = ChartSettings + { csTimeRangeInS :: !Int + , csUpdatePeriodInS :: !Int + } deriving (Generic, FromJSON, ToJSON) + +type ChartsSettings = [(ChartId, ChartSettings)] + +newtype Color = Color String + +type Colors = TBQueue Color + +-- | After the node is connected, we have to add a new dataset to all historical charts. +-- The metrics received from this node will be added in these datasets. +-- Since each dataset has its index, we need a map 'NodeId -> ix', +-- where 'ix' is an index of a dataset in _each_ chart. +type DatasetsIndices = TVar (Map NodeId Index) + +-- | When we add points to chart, we have to remember the timestamp of the latest point, +-- for each chart, to avoid duplicated rendering of the same points. +type LatestTimestamps = Map DataName POSIXTime +type DatasetsTimestamps = TVar (Map NodeId LatestTimestamps) diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/Utils.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/Utils.hs new file mode 100644 index 00000000000..08a1b9b25de --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/UI/Utils.hs @@ -0,0 +1,230 @@ +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE QuasiQuotes #-} + +module Cardano.Tracer.Handlers.RTView.UI.Utils + ( (##) + , dataAction + , dataParent + , dataState + , dataTooltip + , findByClassAndDo + , findAndDo + , findAndSet + , findAndSetHTML + , findAndSetText + , justCleanText + , setTextValue + , setTextValues + , setTextAndClasses + , findByClassAndSet + , findAndAdd + , findAndHide + , findAndShow + , findAndGetValue + , image + , showIt + , showInline + , showFlex + , hideIt + , pageTitle + , pageTitleNotify + , shortenName + , shortenPath + , setDisplayedValue + , delete' + ) where + +import Control.Monad (unless, void) +import Control.Monad.Extra (whenJustM) +import Data.String.QQ +import Data.Text (Text, unpack) +import qualified Data.Text as T +import qualified Foreign.JavaScript as JS +import qualified Foreign.RemotePtr as Foreign +import qualified Graphics.UI.Threepenny as UI +import Graphics.UI.Threepenny.Core + +import Cardano.Tracer.Handlers.RTView.State.Displayed +import Cardano.Tracer.Types + +(##) :: UI Element -> String -> UI Element +(##) el anId = el # set UI.id_ anId + +findAndDo + :: UI.Window + -> Text + -> (Element -> UI ()) + -> UI () +findAndDo window elId = + whenJustM (UI.getElementById window (unpack elId)) + +findByClassAndDo + :: UI.Window + -> Text + -> (Element -> UI ()) + -> UI () +findByClassAndDo window className doIt = + UI.getElementsByClassName window (unpack className) >>= mapM_ doIt + +findAndSet + :: (UI Element -> UI Element) + -> UI.Window + -> Text + -> UI () +findAndSet doIt window elId = + findAndDo window elId $ \el -> void $ element el # doIt + +findAndSetHTML + :: Text + -> UI.Window + -> Text + -> UI () +findAndSetHTML markup = findAndSet (set html $ unpack markup) + +findAndSetText + :: Text + -> UI.Window + -> Text + -> UI () +findAndSetText t = findAndSet (set text $ unpack t) + +justCleanText :: Text -> UI () +justCleanText elId = + UI.runFunction $ UI.ffi setTextValue' elId T.empty + +setTextValue :: Text -> Text -> UI () +setTextValue elId textToSet = + UI.runFunction $ UI.ffi setTextValue' elId textToSet + +setTextValues :: [(Text, Text)] -> UI () +setTextValues [] = return () +setTextValues idsWithValues = UI.runFunction $ UI.ffi setAllValues + where + setAllValues = T.unpack . T.concat $ map setValue (zip [1 :: Int ..] idsWithValues) + setValue (n, (elId, textToSet)) = + let elN = T.pack $ show n in + "var el" <> elN <> " = document.getElementById(\"" + <> elId + <> "\"); if (el" <> elN <> " !== null) el" <> elN <> ".innerHTML = \"" + <> textToSet + <> "\";" + +setTextValue' :: String +setTextValue' = [s| +var el = document.getElementById(%1); +if (el !== null) + el.innerHTML = %2; +|] + +setTextAndClasses :: Text -> Text -> Text -> UI () +setTextAndClasses elId textToSet classesToSet = + UI.runFunction $ UI.ffi setTextAndClasses' elId textToSet classesToSet + +setTextAndClasses' :: String +setTextAndClasses' = [s| +var el = document.getElementById(%1); +if (el !== null) { + el.innerHTML = %2; + el.className = %3; +} +|] + +findAndAdd + :: [UI Element] + -> UI.Window + -> Text + -> UI () +findAndAdd els window elId = + findAndDo window elId $ \el -> void $ element el #+ els + +findAndGetValue + :: UI.Window + -> Text + -> UI String +findAndGetValue window elId = + UI.getElementById window (unpack elId) >>= \case + Nothing -> return "" + Just el -> get value el + +findByClassAndSet + :: (UI Element -> UI Element) + -> UI.Window + -> Text + -> UI () +findByClassAndSet doIt window className = + UI.getElementsByClassName window (unpack className) + >>= mapM_ (\el -> void $ element el # doIt) + +findAndShow, findAndHide + :: UI.Window -> Text -> UI () +findAndShow = findAndSet showIt +findAndHide = findAndSet hideIt + +showIt + , showInline + , showFlex + , hideIt :: UI Element -> UI Element +showIt = set style [("display", "block")] +showInline = set style [("display", "inline")] +showFlex = set style [("display", "flex")] +hideIt = set style [("display", "none")] + +pageTitle, pageTitleNotify :: String +pageTitle = "Cardano RTView" +pageTitleNotify = "(!) Cardano RTView" + +dataTooltip :: WriteAttr Element String +dataTooltip = mkWriteAttr $ set' (attr "data-tooltip") + +dataAction :: Attr Element String +dataAction = dataAttr "action" + +dataParent :: Attr Element String +dataParent = dataAttr "parent" + +dataState :: Attr Element String +dataState = dataAttr "state" + +dataAttr :: String -> Attr Element String +dataAttr name = mkReadWriteAttr getData setData + where + getData el = callFunction $ ffi "$(%1).data(%2)" el name + setData v el = runFunction $ ffi "$(%1).data(%2,%3)" el name v + +image :: String -> String -> UI Element +image imgClass svg = UI.span #. imgClass # set html svg + +shortenPath :: FilePath -> FilePath +shortenPath p = + if length p > 20 + then take 20 p <> "..." + else p + +shortenName :: Text -> Text +shortenName n = + if T.length n > 20 + then T.take 20 n <> "..." + else n + +setDisplayedValue + :: NodeId + -> DisplayedElements + -> Text + -> Text + -> UI () +setDisplayedValue nodeId displayedElements elId mValue = + liftIO (getDisplayedValue displayedElements nodeId elId) >>= \case + Nothing -> setAndRemember + Just displayed -> unless (displayed == mValue) setAndRemember + where + setAndRemember = do + setTextValue elId mValue + liftIO $ saveDisplayedValue displayedElements nodeId elId mValue + +-- | The function 'delete' from 'threepenny-gui' uses 'detach' jQuery-function, +-- but if we need to remove DOM-element completely, we have to use 'remove' instead. +delete' :: Element -> UI () +delete' el = liftJSWindow $ \w -> do + JS.runFunction w $ ffi "$(%1).remove()" el + Foreign.destroy $ toJSObject el diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Chain.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Chain.hs new file mode 100644 index 00000000000..65702f5d127 --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Chain.hs @@ -0,0 +1,49 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} + +module Cardano.Tracer.Handlers.RTView.Update.Chain + ( updateBlockchainHistory + ) where + +import Data.Time.Clock +import Data.Text.Read + +import Cardano.Tracer.Handlers.Metrics.Utils +import Cardano.Tracer.Handlers.RTView.State.Historical +import Cardano.Tracer.Types + +updateBlockchainHistory + :: NodeId + -> BlockchainHistory + -> MetricName + -> MetricValue + -> UTCTime + -> IO () +updateBlockchainHistory nodeId (ChainHistory cHistory) metricName metricValue now = + case metricName of + "cardano.node.density" -> updateChainDensity + "cardano.node.slotNum" -> updateSlotNum + "cardano.node.blockNum" -> updateBlockNum + "cardano.node.slotInEpoch" -> updateSlotInEpoch + "cardano.node.epoch" -> updateEpoch + _ -> return () + where + updateChainDensity = + case double metricValue of + Left _ -> return () + Right (density, _) -> do + let !density' = 0.05 + density * 100.0 + addHistoricalData cHistory nodeId now ChainDensityData $ ValueD density' + + updateSlotNum = + readValueI metricValue $ addHistoricalData cHistory nodeId now SlotNumData + + updateBlockNum = + readValueI metricValue $ addHistoricalData cHistory nodeId now BlockNumData + + updateSlotInEpoch = + readValueI metricValue $ addHistoricalData cHistory nodeId now SlotInEpochData + + updateEpoch = + readValueI metricValue $ addHistoricalData cHistory nodeId now EpochData diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/EKG.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/EKG.hs new file mode 100644 index 00000000000..8e84cb47dbf --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/EKG.hs @@ -0,0 +1,36 @@ +{-# LANGUAGE OverloadedStrings #-} + +module Cardano.Tracer.Handlers.RTView.Update.EKG + ( updateEKGMetrics + ) where + +import Control.Concurrent.STM.TVar (readTVarIO) +import Control.Monad (forM_, unless) +import Data.List (partition, sort) +import qualified Data.Map.Strict as M +import Data.Text (intercalate, isPrefixOf) +import Graphics.UI.Threepenny.Core + +import Cardano.Tracer.Handlers.Metrics.Utils +import Cardano.Tracer.Handlers.RTView.UI.Utils +import Cardano.Tracer.Handlers.RTView.Update.Utils +import Cardano.Tracer.Types + +updateEKGMetrics :: AcceptedMetrics -> UI () +updateEKGMetrics acceptedMetrics = do + allMetrics <- liftIO $ readTVarIO acceptedMetrics + forM_ (M.toList allMetrics) $ \(NodeId anId, (ekgStore, _)) -> do + metrics <- liftIO $ getListOfMetrics ekgStore + unless (null metrics) $ do + setTextValue (anId <> "__node-ekg-metrics-num") (showT $ length metrics) + let sortedMetrics = sort metrics + (rtsGCPredefinedMetrics, otherMetrics) = partition rtsGCPredefined sortedMetrics + (mNames, mValues) = unzip otherMetrics + (mNamesPre, mValuesPre) = unzip rtsGCPredefinedMetrics + allNames = intercalate br mNames <> br <> br <> intercalate br mNamesPre + allValues = intercalate br mValues <> br <> br <> intercalate br mValuesPre + setTextValue (anId <> "__node-ekg-metrics-names") allNames + setTextValue (anId <> "__node-ekg-metrics-values") allValues + where + rtsGCPredefined (mName, _) = "rts.gc." `isPrefixOf` mName + br = "
" diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/EraSettings.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/EraSettings.hs new file mode 100644 index 00000000000..13a8aafa4ab --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/EraSettings.hs @@ -0,0 +1,51 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} + +module Cardano.Tracer.Handlers.RTView.Update.EraSettings + ( runEraSettingsUpdater + ) where + +import Control.Concurrent.STM.TVar (readTVarIO) +import Control.Monad (forever, forM_) +import Control.Monad.Extra (whenJust) +import qualified Data.Map.Strict as M +import Data.Set (Set) +import qualified Data.Text as T +import System.Time.Extra (sleep) + +import Cardano.Tracer.Handlers.RTView.State.EraSettings +import Cardano.Tracer.Handlers.RTView.State.TraceObjects +import Cardano.Tracer.Handlers.RTView.Update.Utils +import Cardano.Tracer.Types + +runEraSettingsUpdater + :: ConnectedNodes + -> ErasSettings + -> SavedTraceObjects + -> IO () +runEraSettingsUpdater connectedNodes settings savedTO = forever $ do + connected <- readTVarIO connectedNodes + updateErasSettings connected settings savedTO + sleep 1.0 + +updateErasSettings + :: Set NodeId + -> ErasSettings + -> SavedTraceObjects + -> IO () +updateErasSettings connected settings savedTO = do + savedTraceObjects <- readTVarIO savedTO + forM_ connected $ \nodeId -> + whenJust (M.lookup nodeId savedTraceObjects) $ \savedTOForNode -> + whenJust (M.lookup "Cardano.Node.Startup.ShelleyBased" savedTOForNode) $ \(trObValue, _, _) -> + -- Example: "Era Alonzo, Slot length 1s, Epoch length 432000, Slots per KESPeriod 129600" + case T.words $ T.replace "," "" trObValue of + [_, era, _, _, slotLen, _, _, epochLen, _, _, _, kesPeriod] -> + addEraSettings settings nodeId $ + EraSettings + { esEra = era + , esSlotLengthInS = readInt (T.init slotLen) 0 + , esEpochLength = readInt epochLen 0 + , esKESPeriodLength = readInt kesPeriod 0 + } + _ -> return () diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Errors.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Errors.hs new file mode 100644 index 00000000000..1e62497d972 --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Errors.hs @@ -0,0 +1,215 @@ +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} + +module Cardano.Tracer.Handlers.RTView.Update.Errors + ( runErrorsUpdater + , updateNodesErrors + , searchErrorMessages + , deleteAllErrorMessages + , exitSearchMode + , sortErrorsByTime + , sortErrorsBySeverity + ) where + +import Control.Concurrent.STM.TVar +import Control.Monad +import Control.Monad.Extra (whenJust, whenJustM) +import qualified Data.Map.Strict as M +import qualified Data.Text as T +import Data.Time.Format (defaultTimeLocale, formatTime) +import qualified Graphics.UI.Threepenny as UI +import Graphics.UI.Threepenny.Core +import System.Time.Extra (sleep) +import Text.Read (readMaybe) + +import Cardano.Logging (SeverityS (..)) + +import Cardano.Tracer.Handlers.RTView.State.Errors +import Cardano.Tracer.Handlers.RTView.State.TraceObjects +import Cardano.Tracer.Handlers.RTView.UI.Img.Icons +import Cardano.Tracer.Handlers.RTView.UI.JS.Utils +import Cardano.Tracer.Handlers.RTView.UI.Utils +import Cardano.Tracer.Handlers.RTView.Update.Utils +import Cardano.Tracer.Types + +runErrorsUpdater + :: ConnectedNodes + -> Errors + -> SavedTraceObjects + -> IO () +runErrorsUpdater connectedNodes nodesErrors savedTO = forever $ do + sleep 2.0 + connected <- readTVarIO connectedNodes + savedTraceObjects <- readTVarIO savedTO + forM_ connected $ \nodeId -> + whenJust (M.lookup nodeId savedTraceObjects) $ \savedTOForNode -> + forM_ (M.toList savedTOForNode) $ \(_, trObInfo@(_, severity, _)) -> + when (itIsError severity) $ + addError nodesErrors nodeId trObInfo + where + itIsError sev = + case sev of + Error -> True + Critical -> True + Alert -> True + Emergency -> True + _ -> False + +-- | Update error messages in a corresponding modal window. +updateNodesErrors + :: UI.Window + -> ConnectedNodes + -> Errors + -> UI () +updateNodesErrors window connectedNodes nodesErrors = do + connected <- liftIO $ readTVarIO connectedNodes + forM_ connected $ \nodeId@(NodeId anId) -> do + errorsFromNode <- liftIO $ getErrors nodesErrors nodeId + unless (null errorsFromNode) $ do + -- Update errors number (as it is in the state). + setTextValue (anId <> "__node-errors-num") (showT $ length errorsFromNode) + -- Enable 'Details' button. + findAndSet (set UI.enabled True) window (anId <> "__node-errors-details-button") + -- Add errors if needed. + whenJustM (UI.getElementById window (T.unpack anId <> "__node-errors-tbody")) $ \el -> + whenJustM (readMaybe <$> get dataState el) $ \(numberOfDisplayedRows :: Int) -> do + let onlyNewErrors = drop numberOfDisplayedRows errorsFromNode + doAddErrorRows nodeId onlyNewErrors el numberOfDisplayedRows + +doAddErrorRows + :: NodeId + -> [ErrorInfo] + -> Element + -> Int + -> UI () +doAddErrorRows nodeId errorsToAdd parentEl numberOfDisplayedRows = do + errorRows <- + forM errorsToAdd $ \(errorIx, (msg, sev, ts)) -> + mkErrorRow errorIx nodeId msg sev ts + -- Add them actually and remember their new number. + let newNumberOfDisplayedRows = numberOfDisplayedRows + length errorsToAdd + void $ element parentEl # set dataState (show newNumberOfDisplayedRows) + #+ errorRows + where + mkErrorRow _errorIx (NodeId anId) msg sev ts = do + copyErrorIcon <- image "has-tooltip-multiline has-tooltip-left rt-view-copy-icon" copySVG + # set dataTooltip "Click to copy this error" + on UI.click copyErrorIcon . const $ + copyTextToClipboard $ errorToCopy ts sev msg + + return $ + UI.tr #. (T.unpack anId <> "-node-error-row") #+ + [ UI.td #+ + [ UI.span # set text (preparedTS ts) + ] + , UI.td #+ + [ UI.span #. "tag is-medium is-danger" # set text (show sev) + ] + , UI.td #+ + [ UI.p #. "control" #+ + [ UI.input #. "input rt-view-error-msg-input" + # set UI.type_ "text" + # set (UI.attr "readonly") "readonly" + # set UI.value (T.unpack msg) + ] + ] + , UI.td #+ + [ element copyErrorIcon + ] + ] + + preparedTS = formatTime defaultTimeLocale "%b %e, %Y %T" + + errorToCopy ts sev msg = "[" <> preparedTS ts <> "] [" <> show sev <> "] [" <> T.unpack msg <> "]" + +searchErrorMessages + :: UI.Window + -> Element + -> NodeId + -> Errors + -> UI.Timer + -> UI () +searchErrorMessages window searchInput nodeId@(NodeId anId) nodesErrors updateErrorsTimer = do + textToSearch <- (T.strip . T.pack) <$> get value searchInput + unless (T.null textToSearch) $ do + -- Ok, there is non-empty text we want to search. It means that now we are + -- in search/filter mode, and during this period the new messages shouldn't be added, + -- so we stop update timer temporarily. + UI.stop updateErrorsTimer + liftIO (getErrorsFilteredByText textToSearch nodesErrors nodeId) >>= \case + [] -> do + -- There is nothing found. So we have to inform the user that + -- there is no corresponding errors. + findByClassAndDo window (anId <> "-node-error-row") delete' + -- Reset number of currently displayed errors rows. + whenJustM (UI.getElementById window (T.unpack anId <> "__node-errors-tbody")) $ \el -> + void $ element el # set dataState "0" + foundErrors -> do + -- Delete displayed errors from window. + findByClassAndDo window (anId <> "-node-error-row") delete' + -- Do add found errors. + whenJustM (UI.getElementById window (T.unpack anId <> "__node-errors-tbody")) $ \el -> + doAddErrorRows nodeId foundErrors el 0 + +deleteAllErrorMessages + :: UI.Window + -> NodeId + -> Errors + -> UI () +deleteAllErrorMessages window nodeId@(NodeId anId) nodesErrors = do + -- Delete errors from window. + findByClassAndDo window (anId <> "-node-error-row") delete' + -- Delete errors from state. + liftIO $ deleteAllErrors nodesErrors nodeId + -- Reset number of errors and disable Detail button. + setTextValue (anId <> "__node-errors-num") "0" + findAndSet (set UI.enabled False) window (anId <> "__node-errors-details-button") + -- Reset number of currently displayed errors rows. + whenJustM (UI.getElementById window (T.unpack anId <> "__node-errors-tbody")) $ \el -> + void $ element el # set dataState "0" + +exitSearchMode + :: UI.Window + -> NodeId + -> UI.Timer + -> UI () +exitSearchMode window (NodeId anId) updateErrorsTimer = do + -- Delete errors (last search result) from window. + findByClassAndDo window (anId <> "-node-error-row") delete' + -- Reset number of currently displayed errors rows. + whenJustM (UI.getElementById window (T.unpack anId <> "__node-errors-tbody")) $ \el -> + void $ element el # set dataState "0" + -- Start update errors timer again. + UI.start updateErrorsTimer + +sortErrorsByTime, sortErrorsBySeverity + :: UI.Window + -> NodeId + -> Element + -> Errors + -> UI () +sortErrorsByTime = sortErrors timeAsc timeDesc +sortErrorsBySeverity = sortErrors severityAsc severityDesc + +sortErrors + :: (ErrorInfo -> ErrorInfo -> Ordering) + -> (ErrorInfo -> ErrorInfo -> Ordering) + -> UI.Window + -> NodeId + -> Element + -> Errors + -> UI () +sortErrors orderingAsc orderingDesc window nodeId@(NodeId anId) sortIcon nodesErrors = do + -- Delete errors from window. + findByClassAndDo window (anId <> "-node-error-row") delete' + get dataState sortIcon >>= \case + "desc" -> doSortErrors orderingAsc "asc" + _ -> doSortErrors orderingDesc "desc" + where + doSortErrors ordering orderState = do + sortedErrors <- liftIO $ getErrorsSortedBy ordering nodesErrors nodeId + -- Do add sorted errors. + whenJustM (UI.getElementById window (T.unpack anId <> "__node-errors-tbody")) $ \el -> + doAddErrorRows nodeId sortedErrors el 0 + void $ element sortIcon # set dataState orderState diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Historical.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Historical.hs new file mode 100644 index 00000000000..b2892ccb32f --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Historical.hs @@ -0,0 +1,53 @@ +module Cardano.Tracer.Handlers.RTView.Update.Historical + ( runHistoricalUpdater + ) where + +import Control.Concurrent.STM.TVar (readTVarIO) +import Control.Monad (forever, forM_) +import qualified Data.Map.Strict as M +import Data.Time.Clock.System +import System.Time.Extra (sleep) + +import Cardano.Tracer.Handlers.Metrics.Utils +import Cardano.Tracer.Handlers.RTView.State.Historical +import Cardano.Tracer.Handlers.RTView.State.Last +import Cardano.Tracer.Handlers.RTView.State.TraceObjects +import Cardano.Tracer.Handlers.RTView.Update.Chain +import Cardano.Tracer.Handlers.RTView.Update.Leadership +import Cardano.Tracer.Handlers.RTView.Update.Resources +import Cardano.Tracer.Handlers.RTView.Update.Transactions +import Cardano.Tracer.Types + +-- | A lot of information received from the node is useful as historical data. +-- It means that such an information should be displayed on time charts, +-- where X axis is a time in UTC. An example: resource metrics, chain information, +-- tx information, etc. +-- +-- This information is extracted both from 'TraceObject's and 'EKG.Metrics' and then +-- it will be saved as chart coords '[(ts, v)]', where 'ts' is a timestamp +-- and 'v' is a value. Later, when the user will open RTView web-page, this +-- saved data will be used to render historical charts. +-- +-- It allows to collect historical data even when RTView web-page is closed. +-- +runHistoricalUpdater + :: SavedTraceObjects + -> AcceptedMetrics + -> ResourcesHistory + -> LastResources + -> BlockchainHistory + -> TransactionsHistory + -> IO () +runHistoricalUpdater _savedTO acceptedMetrics resourcesHistory + lastResources chainHistory txHistory = forever $ do + sleep 1.0 -- TODO: should it be configured? + + now <- systemToUTCTime <$> getSystemTime + allMetrics <- readTVarIO acceptedMetrics + forM_ (M.toList allMetrics) $ \(nodeId, (ekgStore, _)) -> do + metrics <- getListOfMetrics ekgStore + forM_ metrics $ \(metricName, metricValue) -> do + updateTransactionsHistory nodeId txHistory metricName metricValue now + updateResourcesHistory nodeId resourcesHistory lastResources metricName metricValue now + updateBlockchainHistory nodeId chainHistory metricName metricValue now + updateLeadershipHistory nodeId chainHistory metricName metricValue now diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/KES.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/KES.hs new file mode 100644 index 00000000000..b0cd329bbf5 --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/KES.hs @@ -0,0 +1,58 @@ +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} + +module Cardano.Tracer.Handlers.RTView.Update.KES + ( updateKESInfo + ) where + +import Control.Concurrent.STM.TVar (readTVarIO) +import Control.Monad (forM_) +import Control.Monad.Extra (whenJust) +import qualified Data.Map.Strict as M +import Data.Text (pack) +import Data.Text.Read +import qualified Graphics.UI.Threepenny as UI +import Graphics.UI.Threepenny.Core +import Text.Printf + +import Cardano.Tracer.Handlers.Metrics.Utils +import Cardano.Tracer.Handlers.RTView.State.EraSettings +import Cardano.Tracer.Handlers.RTView.State.Displayed +import Cardano.Tracer.Handlers.RTView.UI.Utils +import Cardano.Tracer.Types + +updateKESInfo + :: UI.Window + -> AcceptedMetrics + -> ErasSettings + -> DisplayedElements + -> UI () +updateKESInfo _window acceptedMetrics settings displayed = do + allMetrics <- liftIO $ readTVarIO acceptedMetrics + forM_ (M.toList allMetrics) $ \(nodeId@(NodeId anId), (ekgStore, _)) -> do + metrics <- liftIO $ getListOfMetrics ekgStore + forM_ metrics $ \(metricName, metricValue) -> + case metricName of + "cardano.node.currentKESPeriod" -> + setDisplayedValue nodeId displayed (anId <> "__node-current-kes-period") metricValue + "cardano.node.operationalCertificateExpiryKESPeriod" -> + setDisplayedValue nodeId displayed (anId <> "__node-op-cert-expiry-kes-period") metricValue + "cardano.node.operationalCertificateStartKESPeriod" -> + setDisplayedValue nodeId displayed (anId <> "__node-op-cert-start-kes-period") metricValue + "cardano.node.remainingKESPeriods" -> do + setDisplayedValue nodeId displayed (anId <> "__node-remaining-kes-periods") metricValue + allSettings <- liftIO $ readTVarIO settings + whenJust (M.lookup nodeId allSettings) $ + setDaysUntilRenew nodeId metricValue + _ -> return () + where + setDaysUntilRenew nodeId@(NodeId anId) metricValue EraSettings{esKESPeriodLength, esSlotLengthInS} = do + case decimal metricValue of + Left _ -> return () + Right (remainingKesPeriods :: Int, _) -> do + let secondsUntilRenew = remainingKesPeriods * esKESPeriodLength * esSlotLengthInS + daysUntilRenew :: Double + daysUntilRenew = fromIntegral secondsUntilRenew / 3600 / 24 + setDisplayedValue nodeId displayed (anId <> "__node-days-until-op-cert-renew") $ + pack $ printf "%.1f" daysUntilRenew diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Leadership.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Leadership.hs new file mode 100644 index 00000000000..4b274506a6d --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Leadership.hs @@ -0,0 +1,68 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} + +module Cardano.Tracer.Handlers.RTView.Update.Leadership + ( updateLeadershipHistory + ) where + +import Data.Time.Clock + +import Cardano.Tracer.Handlers.Metrics.Utils +import Cardano.Tracer.Handlers.RTView.State.Historical +import Cardano.Tracer.Types + +updateLeadershipHistory + :: NodeId + -> BlockchainHistory + -> MetricName + -> MetricValue + -> UTCTime + -> IO () +updateLeadershipHistory nodeId (ChainHistory cHistory) metricName metricValue now = + case metricName of + -- Slot when the node was a leader, but couldn't forge the block. + "cardano.node.nodeCannotForge" -> updateNodeCannotForge + -- Slot when this node forged last block. + "cardano.node.forgedSlotLast" -> updateForgedSlotLast + -- Slot when this node is leader. + "cardano.node.nodeIsLeader" -> updateNodeIsLeader + -- Slot when this node made leadership check and concludes it's not leader. + "cardano.node.nodeNotLeader" -> updateNodeIsNotLeader + -- Slot when invalid block was forged. + "cardano.node.forgedInvalidSlotLast" -> updateForgedInvalidSlotLast + -- Slot when the node adopted the block it forged. + "cardano.node.adoptedSlotLast" -> updateAdoptedSlotLast + -- Slot when the node didn't adopted the block it forged, but the block was valid. + "cardano.node.notAdoptedSlotLast" -> updateNotAdoptedSlotLast + -- Slot when the leadership check is started. + "cardano.node.aboutToLeadSlotLast" -> updateAboutToLeadSlotLast + -- Slot when the leadership check is failed. + "cardano.node.couldNotForgeSlotLast" -> updateCouldNotForgeSlotLast + _ -> return () + where + updateNodeCannotForge = + readValueI metricValue $ addHistoricalData cHistory nodeId now NodeCannotForgeData + + updateForgedSlotLast = + readValueI metricValue $ addHistoricalData cHistory nodeId now ForgedSlotLastData + + updateNodeIsLeader = + readValueI metricValue $ addHistoricalData cHistory nodeId now NodeIsLeaderData + + updateNodeIsNotLeader = + readValueI metricValue $ addHistoricalData cHistory nodeId now NodeIsNotLeaderData + + updateForgedInvalidSlotLast = + readValueI metricValue $ addHistoricalData cHistory nodeId now ForgedInvalidSlotLastData + + updateAdoptedSlotLast = + readValueI metricValue $ addHistoricalData cHistory nodeId now AdoptedSlotLastData + + updateNotAdoptedSlotLast = + readValueI metricValue $ addHistoricalData cHistory nodeId now NotAdoptedSlotLastData + + updateAboutToLeadSlotLast = + readValueI metricValue $ addHistoricalData cHistory nodeId now AboutToLeadSlotLastData + + updateCouldNotForgeSlotLast = + readValueI metricValue $ addHistoricalData cHistory nodeId now CouldNotForgeSlotLastData diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/NodeInfo.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/NodeInfo.hs new file mode 100644 index 00000000000..e2aff327a42 --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/NodeInfo.hs @@ -0,0 +1,73 @@ +{-# LANGUAGE OverloadedStrings #-} + +module Cardano.Tracer.Handlers.RTView.Update.NodeInfo + ( askNSetNodeInfo + ) where + +import Control.Monad (forM_, unless) +import Control.Monad.Extra (whenJustM) +import Data.Set (Set) +import qualified Data.Set as S +import qualified Data.Text as T +import Data.Time.Format (defaultTimeLocale, formatTime) +import qualified Graphics.UI.Threepenny as UI +import Graphics.UI.Threepenny.Core + +import Cardano.Node.Startup (NodeInfo (..)) + +import Cardano.Tracer.Handlers.RTView.State.Displayed +import Cardano.Tracer.Handlers.RTView.UI.Utils +import Cardano.Tracer.Handlers.RTView.Update.Utils +import Cardano.Tracer.Types + +askNSetNodeInfo + :: UI.Window + -> DataPointRequestors + -> Set NodeId + -> DisplayedElements + -> UI () +askNSetNodeInfo window dpRequestors newlyConnected displayedElements = + unless (S.null newlyConnected) $ + forM_ newlyConnected $ \nodeId@(NodeId anId) -> + whenJustM (liftIO $ askDataPoint dpRequestors nodeId "NodeInfo") $ \ni -> do + let nodeNameElId = anId <> "__node-name" + shortName = shortenName $ niName ni + + setTextValues + [ (nodeNameElId, shortName) + , (anId <> "__node-version", niVersion ni) + , (anId <> "__node-commit", T.take 7 $ niCommit ni) + , (anId <> "__node-name-for-peers", shortName) + , (anId <> "__node-name-for-ekg-metrics", shortName) + , (anId <> "__node-name-for-errors", shortName) + ] + + findAndSet (set UI.href $ nodeLink (niCommit ni)) window (anId <> "__node-commit") + + setProtocol (niProtocol ni) (anId <> "__node-protocol") + + let nodeStartElId = anId <> "__node-start-time" + setTime (niStartTime ni) nodeStartElId + setTime (niSystemStartTime ni) (anId <> "__node-system-start-time") + + liftIO $ saveDisplayedValue displayedElements nodeId nodeStartElId (T.pack . show $ niStartTime ni) + liftIO $ saveDisplayedValue displayedElements nodeId nodeNameElId (niName ni) + where + nodeLink commit = T.unpack $ "https://github.com/input-output-hk/cardano-node/commit/" <> T.take 7 commit + + setProtocol p id' = do + justCleanText id' + let byronTag = UI.span #. "tag is-warning is-rounded is-medium" # set text "Byron" + shelleyTag = UI.span #. "tag is-info is-rounded is-medium ml-3" # set text "Shelley" + case p of + "Byron" -> findAndAdd [byronTag] window id' + "Shelley" -> findAndAdd [shelleyTag] window id' + _ -> findAndAdd [byronTag, shelleyTag] window id' + + setTime ts id' = do + justCleanText id' + let time = formatTime defaultTimeLocale "%b %e, %Y %T" ts + tz = formatTime defaultTimeLocale "%Z" ts + findAndAdd [ string time + , UI.span #. "has-text-weight-normal is-size-6 ml-2" # set text tz + ] window id' diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/NodeState.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/NodeState.hs new file mode 100644 index 00000000000..df299781c01 --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/NodeState.hs @@ -0,0 +1,49 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} + +module Cardano.Tracer.Handlers.RTView.Update.NodeState + ( askNSetNodeState + ) where + +import Control.Concurrent.STM.TVar +import Control.Monad (forM_) +import Control.Monad.Extra (whenJustM) +import Data.Text (pack) +import qualified Graphics.UI.Threepenny as UI +import Graphics.UI.Threepenny.Core +import Text.Printf + +import Cardano.Node.Tracing.StateRep + +import Cardano.Tracer.Handlers.RTView.State.Displayed +import Cardano.Tracer.Handlers.RTView.UI.Utils +import Cardano.Tracer.Handlers.RTView.Update.Utils +import Cardano.Tracer.Types + +-- | There is 'NodeState' datapoint, it contains different information +-- about the current state of the node. For example, its sync progress. +askNSetNodeState + :: UI.Window + -> ConnectedNodes + -> DataPointRequestors + -> DisplayedElements + -> UI () +askNSetNodeState _window connectedNodes dpRequestors displayed = do + connected <- liftIO $ readTVarIO connectedNodes + forM_ connected $ \nodeId@(NodeId _anId) -> + whenJustM (liftIO $ askDataPoint dpRequestors nodeId "NodeState") $ \(ns :: NodeState) -> do + case ns of + NodeTracingOnlineConfiguring -> return () + NodeOpeningDbs _ -> return () + NodeReplays _ -> return () + NodeInitChainSelection _ -> return () + NodeAddBlock (AddedToCurrentChain _ _ syncPct) -> setSyncProgress nodeId syncPct + NodeStartup _ -> return () + NodeShutdown _ -> return () + where + setSyncProgress nodeId@(NodeId anId) syncPct = do + let nodeSyncProgressElId = anId <> "__node-sync-progress" + if syncPct < 100.0 + then setDisplayedValue nodeId displayed nodeSyncProgressElId $ + pack (printf "%.2f" syncPct) <> " %" + else setTextAndClasses nodeSyncProgressElId "100 %" "rt-view-percent-done" diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Nodes.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Nodes.hs new file mode 100644 index 00000000000..f968e832551 --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Nodes.hs @@ -0,0 +1,320 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} + +module Cardano.Tracer.Handlers.RTView.Update.Nodes + ( addColumnsForConnected + , addDatasetsForConnected + , checkNoNodesState + , updateNodesUI + , updateNodesUptime + ) where + +import Control.Concurrent.STM (atomically) +import Control.Concurrent.STM.TVar +import Control.Monad (forM_, unless, when) +import Control.Monad.Extra (whenJust) +import Data.List.NonEmpty (NonEmpty) +import qualified Data.Map.Strict as M +import Data.Maybe (fromMaybe, catMaybes) +import Data.Set (Set, (\\)) +import qualified Data.Set as S +import qualified Data.Text as T +import Data.Text.Read +import Data.Time.Calendar +import Data.Time.Clock +import Data.Time.Clock.System +import Data.Time.Format (defaultTimeLocale, formatTime) +import Data.Word (Word64) +import qualified Graphics.UI.Threepenny as UI +import Graphics.UI.Threepenny.Core +import Text.Read (readMaybe) + +import Cardano.Tracer.Configuration +import Cardano.Tracer.Handlers.Metrics.Utils +import Cardano.Tracer.Handlers.RTView.State.EraSettings +import Cardano.Tracer.Handlers.RTView.State.Errors +import Cardano.Tracer.Handlers.RTView.State.Displayed +import Cardano.Tracer.Handlers.RTView.State.TraceObjects +import Cardano.Tracer.Handlers.RTView.UI.HTML.Node.Column +import Cardano.Tracer.Handlers.RTView.UI.Charts +import Cardano.Tracer.Handlers.RTView.UI.Types +import Cardano.Tracer.Handlers.RTView.UI.Utils +import Cardano.Tracer.Handlers.RTView.Update.NodeInfo +import Cardano.Tracer.Handlers.RTView.Update.Utils +import Cardano.Tracer.Types + +updateNodesUI + :: UI.Window + -> ConnectedNodes + -> DisplayedElements + -> AcceptedMetrics + -> SavedTraceObjects + -> ErasSettings + -> DataPointRequestors + -> NonEmpty LoggingParams + -> Colors + -> DatasetsIndices + -> Errors + -> UI.Timer + -> UI () +updateNodesUI window connectedNodes displayedElements acceptedMetrics savedTO nodesEraSettings + dpRequestors loggingConfig colors datasetIndices nodesErrors updateErrorsTimer = do + (connected, displayedEls) <- liftIO . atomically $ (,) + <$> readTVar connectedNodes + <*> readTVar displayedElements + -- Check connected/disconnected nodes since previous UI's update. + let displayed = S.fromList $ M.keys displayedEls + when (connected /= displayed) $ do + let disconnected = displayed \\ connected -- In 'displayed' but not in 'connected'. + newlyConnected = connected \\ displayed -- In 'connected' but not in 'displayed'. + deleteColumnsForDisconnected window connected disconnected + addColumnsForConnected window newlyConnected loggingConfig nodesErrors updateErrorsTimer + checkNoNodesState window connected + askNSetNodeInfo window dpRequestors newlyConnected displayedElements + addDatasetsForConnected window newlyConnected colors datasetIndices displayedElements + liftIO $ updateDisplayedElements displayedElements connected + setBlockReplayProgress connected displayedElements acceptedMetrics + setChunkValidationProgress connected savedTO + setLedgerDBProgress connected savedTO + setLeadershipStats connected displayedElements acceptedMetrics + setEraEpochInfo connected displayedElements acceptedMetrics nodesEraSettings + +addColumnsForConnected + :: UI.Window + -> Set NodeId + -> NonEmpty LoggingParams + -> Errors + -> UI.Timer + -> UI () +addColumnsForConnected window newlyConnected loggingConfig nodesErrors updateErrorsTimer = do + unless (S.null newlyConnected) $ + findAndShow window "main-table-container" + forM_ newlyConnected $ + addNodeColumn window loggingConfig nodesErrors updateErrorsTimer + +addDatasetsForConnected + :: UI.Window + -> Set NodeId + -> Colors + -> DatasetsIndices + -> DisplayedElements + -> UI () +addDatasetsForConnected window newlyConnected colors datasetIndices displayedElements = do + unless (S.null newlyConnected) $ + findAndShow window "main-charts-container" + forM_ newlyConnected $ \nodeId -> + addNodeDatasetsToCharts window nodeId colors datasetIndices displayedElements + +deleteColumnsForDisconnected + :: UI.Window + -> Set NodeId + -> Set NodeId + -> UI () +deleteColumnsForDisconnected window connected disconnected = do + forM_ disconnected $ deleteNodeColumn window + when (S.null connected) $ do + findAndHide window "main-table-container" + findAndHide window "main-charts-container" + -- Please note that we don't remove historical data from charts + -- for disconnected node. Because the user may want to see the + -- historical data even for the node that already disconnected. + +checkNoNodesState :: UI.Window -> Set NodeId -> UI () +checkNoNodesState window connected = + if S.null connected + then do + findAndShow window "no-nodes" + findAndShow window "no-nodes-info" + else do + findAndHide window "no-nodes" + findAndHide window "no-nodes-info" + +updateNodesUptime + :: ConnectedNodes + -> DisplayedElements + -> UI () +updateNodesUptime connectedNodes displayedElements = do + connected <- liftIO $ readTVarIO connectedNodes + now <- systemToUTCTime <$> liftIO getSystemTime + displayed <- liftIO $ readTVarIO displayedElements + let elsIdsWithUptimes = map (getUptimeForNode now displayed) $ S.toList connected + setTextValues $ catMaybes elsIdsWithUptimes + where + getUptimeForNode now displayed nodeId@(NodeId anId) = + let nodeStartElId = anId <> "__node-start-time" + nodeUptimeElId = anId <> "__node-uptime" + in case getDisplayedValuePure displayed nodeId nodeStartElId of + Nothing -> Nothing + Just tsRaw -> + case readMaybe (T.unpack tsRaw) of + Nothing -> Nothing + Just (startTime :: UTCTime) -> + let uptimeDiff = now `diffUTCTime` startTime + uptime = uptimeDiff `addUTCTime` nullTime + uptimeFormatted = formatTime defaultTimeLocale "%X" uptime + daysNum = utctDay uptime `diffDays` utctDay nullTime + uptimeWithDays = if daysNum > 0 + -- Show days only if 'uptime' > 23:59:59. + then show daysNum <> "d " <> uptimeFormatted + else uptimeFormatted + in Just (nodeUptimeElId, T.pack uptimeWithDays) + +setBlockReplayProgress + :: Set NodeId + -> DisplayedElements + -> AcceptedMetrics + -> UI () +setBlockReplayProgress connected _displayedElements acceptedMetrics = do + allMetrics <- liftIO $ readTVarIO acceptedMetrics + forM_ connected $ \nodeId -> + whenJust (M.lookup nodeId allMetrics) $ \(ekgStore, _) -> do + metrics <- liftIO $ getListOfMetrics ekgStore + whenJust (lookup "Block replay progress (%)" metrics) $ \metricValue -> + updateBlockReplayProgress nodeId metricValue + where + updateBlockReplayProgress (NodeId anId) mValue = + case double mValue of + Left _ -> return () + Right (progressPct, _) -> do + let nodeBlockReplayElId = anId <> "__node-block-replay" + progressPctS = T.pack $ show progressPct + if "100" `T.isInfixOf` progressPctS + then setTextAndClasses nodeBlockReplayElId "100 %" "rt-view-percent-done" + else setTextValue nodeBlockReplayElId $ progressPctS <> " %" + +setChunkValidationProgress + :: Set NodeId + -> SavedTraceObjects + -> UI () +setChunkValidationProgress connected savedTO = do + savedTraceObjects <- liftIO $ readTVarIO savedTO + forM_ connected $ \nodeId@(NodeId anId) -> + whenJust (M.lookup nodeId savedTraceObjects) $ \savedTOForNode -> do + let nodeChunkValidationElId = anId <> "__node-chunk-validation" + forM_ (M.toList savedTOForNode) $ \(namespace, (trObValue, _, _)) -> + case namespace of + "Cardano.Node.ChainDB.ImmDbEvent.ChunkValidation.ValidatedChunk" -> + -- In this case we don't need to check if the value differs from displayed one, + -- because this 'TraceObject' is forwarded only with new values, and after 100% + -- the node doesn't forward it anymore. + -- + -- Example: "Validated chunk no. 2262 out of 2423. Progress: 93.36%" + case T.words trObValue of + [_, _, _, current, _, _, from, _, progressPct] -> + setTextValue nodeChunkValidationElId $ + T.init progressPct <> " %: no. " <> current <> " from " <> T.init from + _ -> return () + "Cardano.Node.ChainDB.ImmDbEvent.ValidatedLastLocation" -> + setTextAndClasses nodeChunkValidationElId "100 %" "rt-view-percent-done" + _ -> return () + +setLedgerDBProgress + :: Set NodeId + -> SavedTraceObjects + -> UI () +setLedgerDBProgress connected savedTO = do + savedTraceObjects <- liftIO $ readTVarIO savedTO + forM_ connected $ \nodeId@(NodeId anId) -> + whenJust (M.lookup nodeId savedTraceObjects) $ \savedTOForNode -> do + let nodeLedgerDBUpdateElId = anId <> "__node-update-ledger-db" + forM_ (M.toList savedTOForNode) $ \(namespace, (trObValue, _, _)) -> + case namespace of + "Cardano.Node.ChainDB.InitChainSelEvent.UpdateLedgerDb" -> + -- In this case we don't need to check if the value differs from displayed one, + -- because this 'TraceObject' is forwarded only with new values, and after 100% + -- the node doesn't forward it anymore. + -- + -- Example: "Pushing ledger state for block b1e6...fc5a at slot 54495204. Progress: 3.66%" + case T.words trObValue of + [_, _, _, _, _, _, _, _, _, _, progressPct] -> do + if "100" `T.isInfixOf` progressPct + then setTextAndClasses nodeLedgerDBUpdateElId "100 %" "rt-view-percent-done" + else setTextValue nodeLedgerDBUpdateElId $ T.init progressPct <> " %" + _ -> return () + _ -> return () + +setLeadershipStats + :: Set NodeId + -> DisplayedElements + -> AcceptedMetrics + -> UI () +setLeadershipStats connected displayed acceptedMetrics = do + allMetrics <- liftIO $ readTVarIO acceptedMetrics + forM_ connected $ \nodeId@(NodeId anId) -> + whenJust (M.lookup nodeId allMetrics) $ \(ekgStore, _) -> do + metrics <- liftIO $ getListOfMetrics ekgStore + forM_ metrics $ \(mName, mValue) -> + case mName of + -- How many times this node was a leader. + "nodeIsLeaderNum" -> setDisplayedValue nodeId displayed (anId <> "__node-leadership") mValue + -- How many blocks were forged by this node. + "blocksForgedNum" -> setDisplayedValue nodeId displayed (anId <> "__node-forged-blocks") mValue + -- How many times this node could not forge. + "nodeCannotForgeNum" -> setDisplayedValue nodeId displayed (anId <> "__node-cannot-forge") mValue + -- How many slots were missed in this node. + "slotsMissed" -> setDisplayedValue nodeId displayed (anId <> "__node-missed-slots") mValue + _ -> return () + +setEraEpochInfo + :: Set NodeId + -> DisplayedElements + -> AcceptedMetrics + -> ErasSettings + -> UI () +setEraEpochInfo connected displayed acceptedMetrics nodesEraSettings = do + allSettings <- liftIO $ readTVarIO nodesEraSettings + allMetrics <- liftIO $ readTVarIO acceptedMetrics + forM_ connected $ \nodeId@(NodeId anId) -> + whenJust (M.lookup nodeId allSettings) $ \settings -> do + setDisplayedValue nodeId displayed (anId <> "__node-era") $ esEra settings + whenJust (M.lookup nodeId allMetrics) $ \(ekgStore, _) -> do + metrics <- liftIO $ getListOfMetrics ekgStore + let epoch = fromMaybe "" $ lookup "cardano.node.epoch" metrics + slotInEpoch = fromMaybe "" $ lookup "cardano.node.slotInEpoch" metrics + updateEpochInfo settings nodeId epoch slotInEpoch + where + updateEpochInfo settings nodeId@(NodeId anId) epochS slotInEpochS = + unless (T.null epochS || T.null slotInEpochS) $ do + let epochNum = readInt epochS 0 + _slotInEpoch = readInt slotInEpochS 0 + setDisplayedValue nodeId displayed (anId <> "__node-epoch-num") epochS + case getEndOfCurrentEpoch settings epochNum of + Nothing -> return () + Just (_start, end) -> do + setTextValue (anId <> "__node-epoch-end") $ + T.pack $ formatTime defaultTimeLocale "%D %T" end + {- + let elapsedSecondsFromEpochStart = nesSlotLengthInS settings * slotInEpoch + diffFromEndToStart = end `diffUTCTime` start + elapsed = secondsToNominalDiffTime (fromIntegral elapsedSecondsFromEpochStart) + diffFromNowToEnd = diffFromEndToStart - elapsed + timeLeft = diffFromNowToEnd `addUTCTime` nullTime + timeLeftF = T.pack $ formatTime defaultTimeLocale "%d:%H:%M:%S" timeLeft + setTextValue (anId <> "__node-epoch-end") timeLeftF + -} + + getEndOfCurrentEpoch EraSettings{esEra, esSlotLengthInS, esEpochLength} currentEpoch = + case lookup esEra epochsInfo of + Nothing -> Nothing + Just (epochStartDate, firstEpochInEra) -> + let elapsedEpochsInEra = currentEpoch - firstEpochInEra + epochLengthInS = esSlotLengthInS * esEpochLength + secondsFromEpochStartToEpoch = epochLengthInS * elapsedEpochsInEra + !dateOfEpochStart = epochStartDate + fromIntegral secondsFromEpochStartToEpoch + !dateOfEpochEnd = dateOfEpochStart + fromIntegral epochLengthInS + in Just (s2utc dateOfEpochStart, s2utc dateOfEpochEnd) + +type EraName = T.Text +type FirstEpochInEra = Int +type EraStartPOSIX = Word64 + +-- It is taken from 'cardano-ledger' wiki topic "First-Block-of-Each-Era". +epochsInfo :: [(EraName, (EraStartPOSIX, FirstEpochInEra))] +epochsInfo = + [ ("Shelley", (1596073491, 208)) -- 07/30/2020 1:44:51 AM GMT + , ("Allegra", (1608169491, 236)) -- 12/17/2020 1:44:51 AM GMT + , ("Mary", (1614649491, 251)) -- 03/02/2021 1:44:51 AM GMT + , ("Alonzo", (1634953491, 298)) -- 10/23/2021 1:44:51 AM GMT, start of new protocol. + ] diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Peers.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Peers.hs new file mode 100644 index 00000000000..7588a468b42 --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Peers.hs @@ -0,0 +1,146 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} + +module Cardano.Tracer.Handlers.RTView.Update.Peers + ( updateNodesPeers + ) where + +import Control.Concurrent.STM.TVar +import Control.Monad +import Control.Monad.Extra (whenJustM) +import Data.List (find) +import Data.List.Extra (notNull) +import Data.Maybe (mapMaybe) +import qualified Data.Map.Strict as M +import Data.Set ((\\)) +import qualified Data.Set as S +import Data.Text (Text, unpack) +import qualified Data.Text as T +import qualified Graphics.UI.Threepenny as UI +import Graphics.UI.Threepenny.Core + +import Cardano.Tracer.Handlers.RTView.State.Peers +import Cardano.Tracer.Handlers.RTView.State.TraceObjects +import Cardano.Tracer.Handlers.RTView.UI.HTML.Node.Peers +import Cardano.Tracer.Handlers.RTView.UI.Utils +import Cardano.Tracer.Handlers.RTView.Update.Utils +import Cardano.Tracer.Types + +updateNodesPeers + :: UI.Window + -> Peers + -> SavedTraceObjects + -> UI () +updateNodesPeers window displayedPeers savedTO = do + savedTraceObjects <- liftIO $ readTVarIO savedTO + forM_ (M.toList savedTraceObjects) $ \(nodeId, savedTOForNode) -> + forM_ (M.toList savedTOForNode) $ \(namespace, (trObValue, _, _)) -> + case namespace of + "Cardano.Node.Peers" -> + doUpdatePeers window nodeId displayedPeers trObValue + _ -> return () + +doUpdatePeers + :: UI.Window + -> NodeId + -> Peers + -> Text + -> UI () +doUpdatePeers window nodeId@(NodeId anId) displayedPeers trObValue = + if "NodeKernelPeers" `T.isInfixOf` trObValue + then return () -- It was empty 'TraceObject' (without useful info), ignore it. + else do + -- Update peers number. + setTextValue (anId <> "__node-peers-num") (showT (length peersParts)) + -- If there is at least one connected peer, we enable 'Details' button. + findAndSet (set UI.enabled $ notNull peersParts) + window $ anId <> "__node-peers-details-button" + -- Update particular info about peers. + let connectedPeers = getConnectedPeers + connectedPeersAddresses = getConnectedPeersAddresses + displayedPeersAddresses <- liftIO $ getPeersAddresses displayedPeers nodeId + if displayedPeersAddresses /= connectedPeersAddresses + then do + -- There are some changes with number of peers: some new were connected + -- and/or some displayed ones were disconnected. + let disconnectedPeers = displayedPeersAddresses \\ connectedPeersAddresses -- Not in connected + newlyConnectedPeers = connectedPeersAddresses \\ displayedPeersAddresses -- Not in displayed + deleteRowsForDisconnected disconnectedPeers + addRowsForNewlyConnected newlyConnectedPeers connectedPeers + else + -- No changes with number of peers, only their data was changed. + updateConnectedPeersData connectedPeers + where + peersParts = T.splitOn "," trObValue + + getConnectedPeers = S.fromList $ + mapMaybe + (\peerPart -> let peerData = T.words peerPart in + if length peerData == 6 then Just peerData else Nothing + ) peersParts + + getConnectedPeersAddresses = S.map head getConnectedPeers + + deleteRowsForDisconnected disconnected = + forM_ disconnected $ \peerAddr -> do + deletePeerRow window nodeId peerAddr + liftIO $ removePeer displayedPeers nodeId peerAddr + + addRowsForNewlyConnected newlyConnectedPeers connectedPeers = + forM_ newlyConnectedPeers $ \peerAddr -> do + case find (\peerDataList -> head peerDataList == peerAddr) connectedPeers of + Just [_, status, slotNo, reqsInF, blocksInF, bytesInF] -> do + let idPrefix = anId <> peerAddr + addPeerRow idPrefix peerAddr status slotNo reqsInF blocksInF bytesInF + liftIO $ addPeer displayedPeers nodeId peerAddr + _ -> return () + + addPeerRow idPrefix peerAddr status slotNo reqsInF blocksInF bytesInF = do + let idPrefix' = unpack idPrefix + whenJustM (UI.getElementById window (unpack anId <> "__node-peers-tbody")) $ \el -> + void $ element el #+ + [ UI.tr ## (idPrefix' <> "__node-peer-row") #+ + [ UI.td #+ + [ UI.span ## (idPrefix' <> "__address") + #. "is-family-monospace" + # set text (unpack peerAddr) + ] + , UI.td #+ + [ UI.span ## (idPrefix' <> "__status") + # set text (unpack status) + ] + , UI.td #+ + [ UI.span ## (idPrefix' <> "__slotNo") + # set text (unpack $ checkSlot slotNo) + ] + , UI.td #+ + [ UI.span ## (idPrefix' <> "__reqsInF") + # set text (unpack reqsInF) + ] + , UI.td #+ + [ UI.span ## (idPrefix' <> "__blocksInF") + # set text (unpack blocksInF) + ] + , UI.td #+ + [ UI.span ## (idPrefix' <> "__bytesInF") + # set text (unpack bytesInF) + ] + ] + ] + + updateConnectedPeersData connectedPeers = do + let allPeersData = concatMap collectDataToUpdate (S.toList connectedPeers) + -- Update values for all peers by one single FFI-call. + setTextValues allPeersData + + collectDataToUpdate [peerAddr, status, slotNo, reqsInF, blocksInF, bytesInF] = + let idPrefix = anId <> peerAddr + in [ (idPrefix <> "__status", status) + , (idPrefix <> "__slotNo", checkSlot slotNo) + , (idPrefix <> "__reqsInF", reqsInF) + , (idPrefix <> "__blocksInF", blocksInF) + , (idPrefix <> "__bytesInF", bytesInF) + ] + collectDataToUpdate _ = [] + + checkSlot slotNo = if slotNo == "???" then "—" else slotNo diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Reload.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Reload.hs new file mode 100644 index 00000000000..ef7ab2daf26 --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Reload.hs @@ -0,0 +1,41 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} + +module Cardano.Tracer.Handlers.RTView.Update.Reload + ( updateUIAfterReload + ) where + +import Control.Concurrent.STM.TVar +import Data.List.NonEmpty (NonEmpty) +import qualified Graphics.UI.Threepenny as UI +import Graphics.UI.Threepenny.Core + +import Cardano.Tracer.Configuration +import Cardano.Tracer.Handlers.RTView.State.Displayed +import Cardano.Tracer.Handlers.RTView.State.Errors +import Cardano.Tracer.Handlers.RTView.UI.Types +import Cardano.Tracer.Handlers.RTView.Update.NodeInfo +import Cardano.Tracer.Handlers.RTView.Update.Nodes +import Cardano.Tracer.Types + +updateUIAfterReload + :: UI.Window + -> ConnectedNodes + -> DisplayedElements + -> DataPointRequestors + -> NonEmpty LoggingParams + -> Colors + -> DatasetsIndices + -> Errors + -> UI.Timer + -> UI () +updateUIAfterReload window connectedNodes displayedElements dpRequestors + loggingConfig colors datasetIndices nodesErrors updateErrorsTimer = do + -- Ok, web-page was reload (i.e. it's the first update after DOM-rendering), + -- so displayed state should be restored immediately. + connected <- liftIO $ readTVarIO connectedNodes + addColumnsForConnected window connected loggingConfig nodesErrors updateErrorsTimer + checkNoNodesState window connected + askNSetNodeInfo window dpRequestors connected displayedElements + addDatasetsForConnected window connected colors datasetIndices displayedElements + liftIO $ updateDisplayedElements displayedElements connected diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Resources.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Resources.hs new file mode 100644 index 00000000000..97508ea4cd8 --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Resources.hs @@ -0,0 +1,100 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE NumericUnderscores #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} + +module Cardano.Tracer.Handlers.RTView.Update.Resources + ( updateResourcesHistory + ) where + +import Control.Concurrent.STM.TVar (readTVarIO) +import qualified Data.Map.Strict as M +import Data.Time.Clock +import Data.Text.Read +import Data.Word (Word64) + +import Cardano.Tracer.Handlers.Metrics.Utils +import Cardano.Tracer.Handlers.RTView.State.Historical +import Cardano.Tracer.Handlers.RTView.State.Last +import Cardano.Tracer.Handlers.RTView.Update.Utils +import Cardano.Tracer.Types + +updateResourcesHistory + :: NodeId + -> ResourcesHistory + -> LastResources + -> MetricName + -> MetricValue + -> UTCTime + -> IO () +updateResourcesHistory nodeId (ResHistory rHistory) lastResources metricName metricValue now = + case metricName of + "stat.cputicks" -> updateCPUUsage + "mem.resident" -> updateRSSMemory + "rts.gcLiveBytes" -> updateGCLiveMemory + "rts.gcMajorNum" -> updateGCMajorNum + "rts.gcMinorNum" -> updateGCMinorNum + "rts.gcticks" -> updateCPUTimeGC + "rts.mutticks" -> updateCPUTimeApp + "rts.stat.threads" -> updateThreadsNum + _ -> return () + where + updateCPUUsage = + case decimal metricValue of + Left _ -> return () + Right (cpuTicks :: Int, _) -> do + lastOnes <- readTVarIO lastResources + case M.lookup nodeId lastOnes of + Nothing -> + -- There is no last resources for this node yet. + addNullResources lastResources nodeId + Just resourcesForNode -> do + let tns = utc2ns now + tDiffInSec = max 0.1 $ fromIntegral (tns - cpuLastNS resourcesForNode) / 1000_000_000 :: Double + ticksDiff = cpuTicks - cpuLastTicks resourcesForNode + !cpuV = fromIntegral ticksDiff / fromIntegral (100 :: Int) / tDiffInSec + newCPUPct = if cpuV < 0 then 0.0 else cpuV * 100.0 + addHistoricalData rHistory nodeId now CPUData $ ValueD newCPUPct + updateLastResources lastResources nodeId $ \current -> + current { cpuLastTicks = cpuTicks + , cpuLastNS = tns + } + + updateRSSMemory = + case decimal metricValue of + Left _ -> return () + Right (bytes :: Word64, _) -> do + let !memoryInMB = fromIntegral bytes / 1024 / 1024 :: Double + addHistoricalData rHistory nodeId now MemoryData $ ValueD memoryInMB + + updateGCLiveMemory = + case decimal metricValue of + Left _ -> return () + Right (bytes :: Word64, _) -> do + let !memoryInMB = fromIntegral bytes / 1024 / 1024 :: Double + addHistoricalData rHistory nodeId now GCLiveMemoryData $ ValueD memoryInMB + + updateGCMajorNum = + readValueI metricValue $ addHistoricalData rHistory nodeId now GCMajorNumData + + updateGCMinorNum = + readValueI metricValue $ addHistoricalData rHistory nodeId now GCMinorNumData + + updateCPUTimeGC = + case decimal metricValue of + Left _ -> return () + Right (cpuTimeGCInCentiS :: Int, _) -> do + -- This is a total CPU time used by the GC, as 1/100 second. + let !cpuTimeGCInMs = cpuTimeGCInCentiS * 10 + addHistoricalData rHistory nodeId now CPUTimeGCData $ ValueI cpuTimeGCInMs + + updateCPUTimeApp = + case decimal metricValue of + Left _ -> return () + Right (cpuTimeAppInCentiS :: Int, _) -> do + -- This is a total CPU time used by the the node itself, as 1/100 second. + let !cpuTimeAppInMs = cpuTimeAppInCentiS * 10 + addHistoricalData rHistory nodeId now CPUTimeAppData $ ValueI cpuTimeAppInMs + + updateThreadsNum = + readValueI metricValue $ addHistoricalData rHistory nodeId now ThreadsNumData diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Transactions.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Transactions.hs new file mode 100644 index 00000000000..a393dc904af --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Transactions.hs @@ -0,0 +1,41 @@ +{-# LANGUAGE BangPatterns #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} + +module Cardano.Tracer.Handlers.RTView.Update.Transactions + ( updateTransactionsHistory + ) where + +import Data.Time.Clock +import Data.Text.Read + +import Cardano.Tracer.Handlers.Metrics.Utils +import Cardano.Tracer.Handlers.RTView.State.Historical +import Cardano.Tracer.Types + +updateTransactionsHistory + :: NodeId + -> TransactionsHistory + -> MetricName + -> MetricValue + -> UTCTime + -> IO () +updateTransactionsHistory nodeId (TXHistory tHistory) metricName metricValue now = + case metricName of + "cardano.node.txsProcessedNum" -> updateTxsProcessedNum + "cardano.node.mempoolBytes" -> updateMempoolBytes + "cardano.node.txsInMempool" -> updateTxsInMempool + _ -> return () + where + updateTxsProcessedNum = + readValueI metricValue $ addHistoricalData tHistory nodeId now TxsProcessedNumData + + updateTxsInMempool = + readValueI metricValue $ addHistoricalData tHistory nodeId now TxsInMempoolData + + updateMempoolBytes = + case decimal metricValue of + Left _ -> return () + Right (mempoolBytes :: Int, _) -> do + let !mempoolInMB = fromIntegral mempoolBytes / 1024 / 1024 :: Double + addHistoricalData tHistory nodeId now MempoolBytesData $ ValueD mempoolInMB diff --git a/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Utils.hs b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Utils.hs new file mode 100644 index 00000000000..f92466eb24f --- /dev/null +++ b/cardano-tracer/src/Cardano/Tracer/Handlers/RTView/Update/Utils.hs @@ -0,0 +1,73 @@ +{-# LANGUAGE NumericUnderscores #-} + +module Cardano.Tracer.Handlers.RTView.Update.Utils + ( askDataPoint + , utc2ns + , utc2s + , s2utc + , showT + , readInt + , nullTime + ) where + +import Control.Concurrent.STM.TVar (readTVarIO) +import Data.Aeson (FromJSON, decode') +import qualified Data.Map.Strict as M +import Data.Text (Text, pack) +import Data.Text.Read (decimal) +import Data.Time.Calendar +import Data.Time.Clock (UTCTime (..)) +import Data.Time.Clock.POSIX +import Data.Word (Word64) + +import Trace.Forward.Utils.DataPoint (askForDataPoints) +import Trace.Forward.Protocol.DataPoint.Type (DataPointName) + +import Cardano.Tracer.Types + +-- | There is a different information the node can provide us by explicit request. +-- This is a structured data about internal state of the node (for example, its +-- basic information like version, protocol, commit hash, start time, etc). +-- +-- Such a structured data is provided as a 'DataPoint'. When it's receved, it's +-- technically a lazy bytestring that is a result of 'ToJSON'-encoding on the +-- forwarder's side. Here we can decode it to particular Haskell type provided +-- by the node. +askDataPoint + :: FromJSON a + => DataPointRequestors + -> NodeId + -> DataPointName + -> IO (Maybe a) +askDataPoint dpRequestors nodeId dpName = do + requestors <- readTVarIO dpRequestors + case M.lookup nodeId requestors of + Nothing -> return Nothing + Just dpRequestor -> do + dp <- askForDataPoints dpRequestor [dpName] + case lookup dpName dp of + Just (Just rawValue) -> return $ decode' rawValue + _ -> return Nothing + +-- | Converts a timestamp to seconds since Unix epoch. +utc2s :: UTCTime -> Word64 +utc2s utc = fromInteger . round $ utcTimeToPOSIXSeconds utc + +-- | Converts a timestamp to nanoseconds since Unix epoch. +utc2ns :: UTCTime -> Word64 +utc2ns utc = fromInteger . round $ 1000_000_000 * utcTimeToPOSIXSeconds utc + +s2utc :: Word64 -> UTCTime +s2utc posixTime = posixSecondsToUTCTime $ fromIntegral posixTime + +showT :: Show a => a -> Text +showT = pack . show + +readInt :: Text -> Int -> Int +readInt t defInt = + case decimal t of + Left _ -> defInt + Right (i, _) -> i + +nullTime :: UTCTime +nullTime = UTCTime (ModifiedJulianDay 0) 0 diff --git a/cardano-tracer/src/Cardano/Tracer/Run.hs b/cardano-tracer/src/Cardano/Tracer/Run.hs index e3c3e07b4d5..1116ad94601 100644 --- a/cardano-tracer/src/Cardano/Tracer/Run.hs +++ b/cardano-tracer/src/Cardano/Tracer/Run.hs @@ -13,32 +13,39 @@ import Control.Monad (void) import Cardano.Tracer.Acceptors.Run (runAcceptors) import Cardano.Tracer.CLI (TracerParams (..)) import Cardano.Tracer.Configuration (TracerConfig, readTracerConfig) +import Cardano.Tracer.Handlers.CheckMode (runChecker) import Cardano.Tracer.Handlers.Logs.Rotator (runLogsRotator) import Cardano.Tracer.Handlers.Metrics.Servers (runMetricsServers) +import Cardano.Tracer.Handlers.RTView.Run (initSavedTraceObjects, runRTView) import Cardano.Tracer.Types (DataPointRequestors, ProtocolsBrake) import Cardano.Tracer.Utils (initAcceptedMetrics, initConnectedNodes, - initDataPointRequestors, initProtocolsBrake, lift3M) + initDataPointRequestors, initProtocolsBrake) -- | Top-level run function, called by 'cardano-tracer' app. runCardanoTracer :: TracerParams -> IO () -runCardanoTracer TracerParams{tracerConfig} = lift3M - doRunCardanoTracer (readTracerConfig tracerConfig) - initProtocolsBrake - initDataPointRequestors +runCardanoTracer TracerParams{tracerConfig, checkMode} = do + config <- readTracerConfig tracerConfig + brake <- initProtocolsBrake + dpRequestors <- initDataPointRequestors + doRunCardanoTracer config checkMode brake dpRequestors -- | Runs all internal services of the tracer. doRunCardanoTracer - :: TracerConfig -- ^ Tracer's configuration. - -> ProtocolsBrake -- ^ The flag we use to stop all the protocols. + :: TracerConfig -- ^ Tracer's configuration. + -> Bool -- ^ The flag of check mode. + -> ProtocolsBrake -- ^ The flag we use to stop all the protocols. -> DataPointRequestors -- ^ The DataPointRequestors to ask 'DataPoint's. -> IO () -doRunCardanoTracer config protocolsBrake dpRequestors = do +doRunCardanoTracer config itIsCheckMode protocolsBrake dpRequestors = do connectedNodes <- initConnectedNodes acceptedMetrics <- initAcceptedMetrics currentLogLock <- newLock + savedTO <- initSavedTraceObjects void . sequenceConcurrently $ [ runLogsRotator config currentLogLock , runMetricsServers config connectedNodes acceptedMetrics - , runAcceptors config connectedNodes acceptedMetrics + , runAcceptors config connectedNodes acceptedMetrics savedTO dpRequestors protocolsBrake currentLogLock + , runRTView config connectedNodes acceptedMetrics savedTO dpRequestors + , runChecker itIsCheckMode acceptedMetrics savedTO ] diff --git a/cardano-tracer/test/Cardano/Tracer/Test/Acceptor.hs b/cardano-tracer/test/Cardano/Tracer/Test/Acceptor.hs index 859f8b6f243..153dad97d82 100644 --- a/cardano-tracer/test/Cardano/Tracer/Test/Acceptor.hs +++ b/cardano-tracer/test/Cardano/Tracer/Test/Acceptor.hs @@ -18,6 +18,7 @@ import System.Time.Extra (sleep) import Cardano.Tracer.Acceptors.Run (runAcceptors) import Cardano.Tracer.Configuration +import Cardano.Tracer.Handlers.RTView.Run (initSavedTraceObjects) import Cardano.Tracer.Types (DataPointRequestors) import Cardano.Tracer.Utils (initAcceptedMetrics, initConnectedNodes, initDataPointRequestors, initProtocolsBrake) @@ -35,9 +36,10 @@ launchAcceptorsSimple mode localSock dpName = do dpRequestors <- initDataPointRequestors connectedNodes <- initConnectedNodes acceptedMetrics <- initAcceptedMetrics + savedTO <- initSavedTraceObjects currentLogLock <- newLock void . sequenceConcurrently $ - [ runAcceptors mkConfig connectedNodes acceptedMetrics + [ runAcceptors mkConfig connectedNodes acceptedMetrics savedTO dpRequestors protocolsBrake currentLogLock , runDataPointsPrinter dpName dpRequestors ] @@ -51,6 +53,7 @@ launchAcceptorsSimple mode localSock dpName = do , ekgRequestFreq = Just 1.0 , hasEKG = Nothing , hasPrometheus = Nothing + , hasRTView = Nothing , logging = NE.fromList [LoggingParams "/tmp/demo-acceptor" FileMode ForHuman] , rotation = Nothing , verbosity = Just Minimum diff --git a/cardano-tracer/test/Cardano/Tracer/Test/DataPoint/Tests.hs b/cardano-tracer/test/Cardano/Tracer/Test/DataPoint/Tests.hs index edbda9e81b5..1db66897e58 100644 --- a/cardano-tracer/test/Cardano/Tracer/Test/DataPoint/Tests.hs +++ b/cardano-tracer/test/Cardano/Tracer/Test/DataPoint/Tests.hs @@ -35,7 +35,7 @@ propDataPoint rootDir localSock = do stopProtocols <- initProtocolsBrake dpRequestors <- initDataPointRequestors savedDPValues :: TVar DataPointValues <- newTVarIO [] - withAsync (doRunCardanoTracer config stopProtocols dpRequestors) . const $ do + withAsync (doRunCardanoTracer config False stopProtocols dpRequestors) . const $ do sleep 1.0 withAsync (launchForwardersSimple Initiator localSock 1000 10000) . const $ do sleep 1.5 @@ -83,6 +83,7 @@ propDataPoint rootDir localSock = do , ekgRequestFreq = Just 1.0 , hasEKG = Nothing , hasPrometheus = Nothing + , hasRTView = Nothing , logging = NE.fromList [LoggingParams rootDir FileMode ForHuman] , rotation = Nothing , verbosity = Just Minimum diff --git a/cardano-tracer/test/Cardano/Tracer/Test/Logs/Tests.hs b/cardano-tracer/test/Cardano/Tracer/Test/Logs/Tests.hs index b98311c19d0..70ee3c178bd 100644 --- a/cardano-tracer/test/Cardano/Tracer/Test/Logs/Tests.hs +++ b/cardano-tracer/test/Cardano/Tracer/Test/Logs/Tests.hs @@ -36,7 +36,7 @@ propLogs :: LogFormat -> FilePath -> FilePath -> IO Property propLogs format rootDir localSock = do stopProtocols <- initProtocolsBrake dpRequestors <- initDataPointRequestors - withAsync (doRunCardanoTracer (config rootDir localSock) stopProtocols dpRequestors) . const $ do + withAsync (doRunCardanoTracer (config rootDir localSock) False stopProtocols dpRequestors) . const $ do sleep 1.0 withAsync (launchForwardersSimple Initiator localSock 1000 10000) . const $ do sleep 8.0 -- Wait till some rotation is done. @@ -67,6 +67,7 @@ propLogs format rootDir localSock = do , ekgRequestFreq = Just 1.0 , hasEKG = Nothing , hasPrometheus = Nothing + , hasRTView = Nothing , logging = NE.fromList [LoggingParams root FileMode format] , rotation = Just $ RotationParams { rpFrequencySecs = 3 @@ -81,7 +82,7 @@ propMultiInit :: LogFormat -> FilePath -> FilePath -> FilePath -> IO Property propMultiInit format rootDir localSock1 localSock2 = do stopProtocols <- initProtocolsBrake dpRequestors <- initDataPointRequestors - withAsync (doRunCardanoTracer (config rootDir localSock1 localSock2) stopProtocols dpRequestors) . const $ do + withAsync (doRunCardanoTracer (config rootDir localSock1 localSock2) False stopProtocols dpRequestors) . const $ do sleep 1.0 withAsync (launchForwardersSimple Responder localSock1 1000 10000) . const $ do sleep 1.0 @@ -98,6 +99,7 @@ propMultiInit format rootDir localSock1 localSock2 = do , ekgRequestFreq = Just 1.0 , hasEKG = Nothing , hasPrometheus = Nothing + , hasRTView = Nothing , logging = NE.fromList [LoggingParams root FileMode format] , rotation = Nothing , verbosity = Just Minimum @@ -107,7 +109,7 @@ propMultiResp :: LogFormat -> FilePath -> FilePath -> IO Property propMultiResp format rootDir localSock = do stopProtocols <- initProtocolsBrake dpRequestors <- initDataPointRequestors - withAsync (doRunCardanoTracer (config rootDir localSock) stopProtocols dpRequestors) . const $ do + withAsync (doRunCardanoTracer (config rootDir localSock) False stopProtocols dpRequestors) . const $ do sleep 1.0 withAsync (launchForwardersSimple Initiator localSock 1000 10000) . const $ do sleep 1.0 @@ -124,6 +126,7 @@ propMultiResp format rootDir localSock = do , ekgRequestFreq = Just 1.0 , hasEKG = Nothing , hasPrometheus = Nothing + , hasRTView = Nothing , logging = NE.fromList [LoggingParams root FileMode format] , rotation = Nothing , verbosity = Just Minimum diff --git a/cardano-tracer/test/Cardano/Tracer/Test/Restart/Tests.hs b/cardano-tracer/test/Cardano/Tracer/Test/Restart/Tests.hs index a96db2f2fbf..f0dee59c830 100644 --- a/cardano-tracer/test/Cardano/Tracer/Test/Restart/Tests.hs +++ b/cardano-tracer/test/Cardano/Tracer/Test/Restart/Tests.hs @@ -28,12 +28,14 @@ tests = localOption (QuickCheckTests 1) $ testGroup "Test.Restart" ] propNetworkForwarder :: FilePath -> FilePath -> IO Property -propNetworkForwarder rootDir localSock = +propNetworkForwarder rootDir localSock = do + let config = mkConfig rootDir localSock + itIsCheckMode = False + brake <- initProtocolsBrake + dpRequestors <- initDataPointRequestors propNetwork' rootDir ( launchForwardersSimple Initiator localSock 1000 10000 - , lift3M doRunCardanoTracer (return $ mkConfig rootDir localSock) - initProtocolsBrake - initDataPointRequestors + , doRunCardanoTracer config itIsCheckMode brake dpRequestors ) propNetwork' @@ -82,6 +84,7 @@ mkConfig root p = TracerConfig , ekgRequestFreq = Just 1.0 , hasEKG = Nothing , hasPrometheus = Nothing + , hasRTView = Nothing , logging = NE.fromList [LoggingParams root FileMode ForMachine] , rotation = Nothing , verbosity = Just Minimum