diff --git a/webgui/css/bootstrap-grid.css b/webgui/css/bootstrap-grid.css index 259a9e2..4ce4d53 100644 --- a/webgui/css/bootstrap-grid.css +++ b/webgui/css/bootstrap-grid.css @@ -353,6 +353,12 @@ html { margin-left: 91.666667%; } +@media (min-width: 350px) { + #selectSlots{ + width: 12em; + } +} + @media (min-width: 576px) { .col-sm { -ms-flex-preferred-size: 0; @@ -553,6 +559,10 @@ html { .offset-sm-11 { margin-left: 91.666667%; } + + #selectSlots{ + width: 12em; + } } @media (min-width: 768px) { @@ -614,6 +624,7 @@ html { flex: 0 0 25%; max-width: 25%; } + .col-md-4 { -ms-flex: 0 0 33.333333%; flex: 0 0 33.333333%; @@ -755,6 +766,10 @@ html { .offset-md-11 { margin-left: 91.666667%; } + + #selectSlots{ + width: 12em; + } } @media (min-width: 992px) { @@ -957,6 +972,9 @@ html { .offset-lg-11 { margin-left: 91.666667%; } + #selectSlots{ + width: 12em; + } } @media (min-width: 1200px) { diff --git a/webgui/css/bootstrap.css b/webgui/css/bootstrap.css index 8eac957..e9e0474 100644 --- a/webgui/css/bootstrap.css +++ b/webgui/css/bootstrap.css @@ -8532,6 +8532,7 @@ button.bg-dark:focus { margin-left: auto !important; } + @media (min-width: 576px) { .m-sm-0 { margin: 0 !important; diff --git a/webgui/css/custom.css b/webgui/css/custom.css index 023a773..aee007d 100644 --- a/webgui/css/custom.css +++ b/webgui/css/custom.css @@ -39,7 +39,8 @@ a[data-tab] { font-size: 1em; } -button, .button { +button, +.button { font-size: 1em; width: 100%; white-space: normal; @@ -48,18 +49,22 @@ button, .button { background-color: #ebf2f5; } -button:disabled, button:disabled div, label.disabled{ +button:disabled, +button:disabled div, +label.disabled { border-color: lightgray; color: gray; background-color: whitesmoke; } -button:active, .button:active { +button:active, +.button:active { filter: brightness(1.5); box-shadow: 0 0 10px 2px #004562 !important; } -button:disabled img, label.disabled img { +button:disabled img, +label.disabled img { filter: invert(0.5); } @@ -99,7 +104,7 @@ br { color: red; } -.text-center{ +.text-center { text-align: center; } @@ -122,8 +127,8 @@ br { } .full-height { - height: 100%; - } + height: 100%; +} .full-width { width: 100%; @@ -147,7 +152,7 @@ br { position: relative; } -.float-left{ +.float-left { float: left; } @@ -163,13 +168,14 @@ br { border-right: medium dotted darkgray; } -#tab-puff-container span, #tab-puff-container label { +#tab-puff-container span, +#tab-puff-container label { background-color: white; white-space: normal; } label { - display:inline-block; + display: inline-block; } .cursorPosWrapper { @@ -187,9 +193,32 @@ label { max-height: 350px; } +@media (prefers-color-scheme: dark) { + + /* This makes it so that within posVis it is inverted. */ + /* ASK: Whether the colour is okay, in both modes (used to be blue). 1% is the best option for both, but the back-layer is not really visible then.*/ + #posVis { + filter: invert(30%); + } +} + +@media (prefers-color-scheme: dark) { + #tab-puff-container { + filter: invert(1%); + } + + #tabVisBtnSipVis { + filter: invert(1%); + } + + #fabi-logo {} + +} + .color-lightcyan { background-color: #9be7ff; } + .color-lightred { background-color: #FF7B61; } @@ -198,8 +227,7 @@ label { background-color: #cceff9; } -.center-div -{ +.center-div { margin-right: auto; margin-left: auto; } @@ -208,7 +236,8 @@ label { display: none; } -button.selected, .custom-radio:checked ~ label { +button.selected, +.custom-radio:checked~label { border-width: 0.2em; border-color: #33C3F0; background-color: #cceff9; @@ -218,7 +247,7 @@ button.selected, .custom-radio:checked ~ label { border-width: 0.25em; border-color: #33C3F0; background-color: #0D5F77; - box-shadow: black ; + box-shadow: black; } .custom-radio { @@ -230,10 +259,10 @@ button.selected, .custom-radio:checked ~ label { .onlyscreenreader { display: block !important; position: absolute; - left:-9999px; + left: -9999px; } .btnTransparent { background-color: transparent; border: 1px solid #bbb; -} +} \ No newline at end of file diff --git a/webgui/img/fabi_lowres.png b/webgui/img/FABI_lowres.png similarity index 100% rename from webgui/img/fabi_lowres.png rename to webgui/img/FABI_lowres.png diff --git a/webgui/index.html b/webgui/index.html index aaba150..db1c14d 100644 --- a/webgui/index.html +++ b/webgui/index.html @@ -1,17 +1,60 @@  + - FLipMouse/FABI Configuration + Generic Configuration + + + + + + + + + + + + + + + + + + + + + + + + + + + - +
+
+ + + \ No newline at end of file diff --git a/webgui/js/adapter/arecomm.js b/webgui/js_new/adapter/arecomm.js similarity index 100% rename from webgui/js/adapter/arecomm.js rename to webgui/js_new/adapter/arecomm.js diff --git a/webgui/js_new/adapter/mockcomm.js b/webgui/js_new/adapter/mockcomm.js new file mode 100644 index 0000000..a4ad951 --- /dev/null +++ b/webgui/js_new/adapter/mockcomm.js @@ -0,0 +1,83 @@ +window.mock = {}; +mock.deviceSlotNr = 0; +mock.slotNames = ['mouse', 'test']; + +function MockCommunicator() { + let DEFAULT_CONFIGURATION = ['AT SC 0x00ffff', 'AT SB 2', 'AT AX 60', 'AT AY 60', 'AT DX 20', 'AT DY 20', 'AT MS 50', 'AT AC 50', 'AT TS 500', 'AT TP 525', 'AT WS 3', 'AT SP 700', 'AT SS 300', 'AT MM 1', 'AT GU 50', 'AT GD 50', 'AT GL 50', 'AT GR 50', 'AT RO 0', 'AT BT 1', 'AT BM 01', 'AT NE', 'AT BM 02', 'AT KP KEY_ESC', 'AT BM 03', 'AT NC', 'AT BM 04', 'AT KP KEY_UP', 'AT BM 05', 'AT KP KEY_DOWN', 'AT BM 06', 'AT KP KEY_LEFT', 'AT BM 07', 'AT KP KEY_RIGHT', 'AT BM 08', 'AT PL', 'AT BM 09', 'AT NC', 'AT BM 10', 'AT CR', 'AT BM 11', 'AT CA', 'AT BM 12', 'AT NC', 'AT BM 13', 'AT NC', 'AT BM 14', 'AT NC', 'AT BM 15', 'AT NC', 'AT BM 16', 'AT NC', 'AT BM 17', 'AT NC', 'AT BM 18', 'AT NC', 'AT BM 19', 'AT NC', 'AT BM 20', 'AT NC', 'AT BM 21', 'AT NC', 'AT BM 22', 'AT NC']; + var VALUE_CONSTANT = 'VALUES:'; + var _valueHandler = null; + var _invervalHandler = null; + var thiz = this; + + this.setValueHandler = function (handler) { + _valueHandler = handler; + }; + + this.sendData = function (value, timeout) { + if (!value) return; + thiz.pressure = thiz.pressure || 500; + thiz.x = thiz.x || 0; + thiz.y = thiz.y || 0; + thiz.incrementP = thiz.incrementP || 1; + thiz.incrementXY = thiz.increment || 1; + + return new Promise(function (resolve) { + if (value == 'AT') { + resolve(''); + } else if (value.indexOf('AT SR') > -1) { + _invervalHandler = setInterval(function () { + if (L.isFunction(_valueHandler)) { + thiz.pressure += thiz.incrementP; + thiz.x += thiz.incrementXY * getRandomInt(-5, 5); + thiz.y += thiz.incrementXY * (-1) * getRandomInt(-5, 5); + if (thiz.pressure > 550 || thiz.pressure < 450) { + thiz.incrementP *= -1; + } + if (thiz.y > 100 || thiz.y < -100 || thiz.x > 100 || thiz.x < -100) { + thiz.incrementXY *= -1; + } + _valueHandler(`${VALUE_CONSTANT}${thiz.pressure},${getRandomInt(500, 600)},${getRandomInt(500, 600)},${getRandomInt(500, 600)},${getRandomInt(500, 600)},${thiz.x},${thiz.y},111,${mock.deviceSlotNr},10,10`); + } + }, 200); + + } else if (value.indexOf('AT ER') > -1) { + clearInterval(_invervalHandler); + + } else if (value.indexOf('AT LA') > -1) { // LA = Load All. + let defaultCmds = DEFAULT_CONFIGURATION.join('\n'); + let cmds = 'Slot:mouse\n' + defaultCmds + '\n'; + cmds = cmds + 'Slot:test\n' + defaultCmds + '\nEND'; + resolve(cmds); + + } else if (value.indexOf('AT CA') > -1) { + thiz.x = 0; + thiz.y = 0; + + } else if (value.indexOf('AT LO') > -1) { + let slotName = value.replace('AT LO ', ''); + mock.deviceSlotNr = mock.slotNames.indexOf(slotName); + resolve('OK'); + + } else if (value.indexOf('AT IL') > -1) { // IL = Lists all stored infrared command names. + resolve('IRCommand0:play\nIRCommand1:pause\nIRCommand2:stop'); + + } else if (value.indexOf('AT ID') > -1) { + resolve('FABI v3.7, PressureSensor=DSP310, ForceSensor=InternalADC, Board=Raspberry_Pi_Pico_2W'); + + } else if (value.indexOf('AT SA') > -1) { + resolve('OK'); + } + setTimeout(function () { + resolve(); + }, timeout); + }); + }; +} + +function getRandomInt(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +function getRandomInt2(factor) { + return Math.floor((Math.random() - Math.random()) * factor); +} \ No newline at end of file diff --git a/webgui/js_new/adapter/sercomm.js b/webgui/js_new/adapter/sercomm.js new file mode 100644 index 0000000..5341ea3 --- /dev/null +++ b/webgui/js_new/adapter/sercomm.js @@ -0,0 +1,244 @@ +import {MainView} from "../ui/views/MainView.js"; + +window.logReceived = false; + +function SerialCommunicator() { + //serial port instance + let thiz = this; + var _port; + var _portWriter; + var _textEncoder = new TextEncoder(); + let _runReader = true; + let _portReader = null; + + //value handler for reported ADC/mouthpiece values + var _valueHandler; + //internal value handler function for the returned data for an AT command. + var _internalValueFunction; + let _sendingRaw = false; + + let _stringToReceive = null; + let _stringToReceiveResolve = null; + + this.setValueHandler = function (handler) { + _valueHandler = handler; + }; + + this.getSerialPort = function() { + return _port; + } + + this.init = async function () { + if (!navigator.serial) { + console.warn("Browser not supported, please use Chromium, Vivaldi, Edge or Chrome"); + return Promise.reject(C.ERROR_SERIAL_NOT_SUPPORTED); + } + + //filter for arduino/Teensy VID/PID and our own ones + const filters = C.USB_DEVICE_FILTERS; + + _port = await navigator.serial.requestPort({filters}).catch((error) => { + console.log(error); + return Promise.reject(C.ERROR_SERIAL_DENIED); + }); + + // Wait for the serial port to open. + await _port.open({baudRate: 115200}).catch((error) => { + console.log(error); + return Promise.reject(C.ERROR_SERIAL_BUSY); + }); + await listenToPort().catch(() => { + return Promise.reject(C.ERROR_SERIAL_CONNECT_FAILED); + }); + _portWriter = _port.writable.getWriter(); + + return Promise.resolve(); + }; + + this.cancel = function () { + _runReader = false; + if (_portReader) _portReader.cancel(); + if (_portWriter) _portWriter.close(); + if (_portReader) _portReader.releaseLock(); + if (_portWriter) _portWriter.releaseLock(); + } + + this.close = function () { + return new Promise(resolve => { + this.cancel(); + setTimeout(() => { + if (_port) _port.close(); + setTimeout(() => { + resolve(); + }, 200); + }, 200); + }); + } + + /** + * waits for a specific string to be received by serial port + * @param stringToReceive the string to wait for + * @param timeout timeout how long to wait + * @return {Promise} Promise is resolved if string is received, otherwise rejected after given timeout + */ + this.waitForReceiving = function (stringToReceive, timeout) { + timeout = timeout || 5000; + _stringToReceive = stringToReceive; + return new Promise((resolve, reject) => { + _stringToReceiveResolve = resolve; + setTimeout(() => { + _stringToReceive = null; + _stringToReceiveResolve = null; + reject(stringToReceive + ' not received.'); + }, timeout); + }); + } + + /** + * sends raw data to serial port + * @param arrayBuffer the binary data to send in an ArrayBuffer + * @param progressCallback optional function that is called with current percentage value of progress (0-100) + * @return {Promise} + */ + this.sendRawData = async function (arrayBuffer, progressCallback) { + if (!arrayBuffer) return; + if (!_port) { + throw 'sercomm: port not initialized. call init() before sending data.'; + } + _sendingRaw = true; + let array = new Int8Array(arrayBuffer); + let chunksize = 256; + let sent = 0; + let lastProgress = null; + for (let i = 0; i < array.length; i += chunksize) { + sent += chunksize; + await _portWriter.write(array.slice(i, i + chunksize)); + let progress = Math.floor((sent / array.length) * 100); + if (progressCallback && progress !== lastProgress) { + progressCallback(progress); + lastProgress = progress; + log.info(progress + '%'); + } + await new Promise(resolve => setTimeout(() => resolve(), 10)); + } + _sendingRaw = false; + } + + /** + * sends raw audio data to serial port + * @param arrayBuffer the binary audio data (in wav format, 22Khz, mono) to send in an ArrayBuffer + * @return {Promise} + */ + this.sendAudioData = async function (arrayBuffer) { + if (!arrayBuffer) return; + if (!_port) { + throw 'sercomm: port not initialized. call init() before sending data.'; + } + _sendingRaw = true; + try { + await _portWriter.write(_textEncoder.encode(C.AT_CMD_AUDIO_TRANSMISSION+"\n")); + await _portWriter.write(arrayBuffer); + } catch (error) { + console.error("Error sending data to serial device:", error); + } + _sendingRaw = false; + + } + + + //send data line based (for all AT commands) + this.sendData = async function (value, timeout, dontLog) { + if (!value || _sendingRaw) return; + if (!_port) { + throw 'sercomm: port not initialized. call init() before sending data.'; + } + timeout = timeout || 0; + + //send data via serial port + var output = value + "\r\n"; + await _portWriter.write(_textEncoder.encode(output)); + //add NL/CR (not needed on websockets) + //await _portWriter.write('\r\n'); + + //_portWriter.releaseLock(); + //wait for a response to this command + //(there might be a timeout for commands with no response) + + if (timeout > 0) { + return new Promise(function (resolve) { + let result = ''; + let timeoutHandler = setTimeout(function () { + if (!dontLog) { + console.log("timeout of command: " + value); + } + resolve(result); + }, timeout); + _internalValueFunction = function (data) { + clearTimeout(timeoutHandler); + result += data; + timeoutHandler = setTimeout(function () { + if (!dontLog) { + console.log("got result: " + result); + } + resolve(result); + _internalValueFunction = null; + }, 200); + }; + }); + } + return Promise.resolve(); + }; + + async function listenToPort() { + const textDecoder = new TextDecoderStream(); + _port.readable.pipeTo(textDecoder.writable); + _portReader = textDecoder.readable.getReader(); + + // Listen to data coming from the serial device. + _runReader = true; + var chunk = ""; + return new Promise(async (resolve, reject) => { + setTimeout(resolve, 200); + while (_runReader) { + try { + const {value, done} = await _portReader.read(); + if (done) { + break; + } + + if (window.logReceived) { + log.info(value); + } + value.split("").forEach((part) => { + chunk = chunk + part; + if (_stringToReceive && chunk.indexOf(_stringToReceive.trim()) > -1) { + _stringToReceiveResolve(); + _stringToReceive = null; + } + if (part === '\n') { + if (chunk.length > 2 && chunk.indexOf(C.LIVE_VALUE_CONSTANT) > -1) { + if (L.isFunction(_valueHandler)) { + _valueHandler(chunk.toString()); + } + } else if (_internalValueFunction) { + _internalValueFunction(chunk); + } + chunk = ""; + } + }); + + } catch (e) { + console.warn(e); + thiz.close(); + if (MainView.instance) { + MainView.instance.toConnectionScreen(); + } + reject(); + } + } + _portReader.releaseLock(); + }); + } +} + +export {SerialCommunicator}; diff --git a/webgui/js/adapter/wscomm.js b/webgui/js_new/adapter/wscomm.js similarity index 100% rename from webgui/js/adapter/wscomm.js rename to webgui/js_new/adapter/wscomm.js diff --git a/webgui/js/adapter/wsutil.js b/webgui/js_new/adapter/wsutil.js similarity index 100% rename from webgui/js/adapter/wsutil.js rename to webgui/js_new/adapter/wsutil.js diff --git a/webgui/js_new/communication/ATDevice.js b/webgui/js_new/communication/ATDevice.js new file mode 100644 index 0000000..02e57ef --- /dev/null +++ b/webgui/js_new/communication/ATDevice.js @@ -0,0 +1,1042 @@ +/** + * ATDevice implements all functionality that is generic for FLipMouse, FABI and FlipPad devices. + */ +import {SerialCommunicator} from "../adapter/sercomm.js"; +import {localStorageService} from "../localStorageService.js"; + +let ATDevice = {}; +ATDevice.parseLiveData = true; + +// TBD: seperate array for global settings (not slot specific) ? +let _slots = []; +let _slotsBackup = []; +let _currentSlot = null; +let _slotChangeHandler = null; +let _lastSlotChangeTime = 0; +let _SLOT_CONSTANT = 'Slot'; +let _valueHandler = null; +let _liveValueLastUpdate = 0; +let _sensorInfo = {}; + +let _communicator; +let _isInitialized = false; +let _inRawMode = false; +let _busyMethods = {}; + +let _atCmdQueue = []; +let _sendingAtCmds = false; +let _timestampLastAtCmd = new Date().getTime(); + +let _connectionTestIntervalHandler = null; +let _connectionTestCallbacks = []; +let _connected = true; + +let _AT_CMD_BUSY_RESPONSE = 'BUSY'; +let _AT_CMD_IR_TIMEOUT_RESPONSE = 'IR_TIMEOUT'; +let _liveData = {}; +let _liveValueHandler = null; +let _lastLiveValueParse = 0; + +let _autoSaveTimeout = 750; +let _dontGetLiveValues = false; + +let _lastVersionResult = null +let _lastVersionRawString = null + + +const TEST_MODE_OPTIONS = "TEST_MODE_OPTIONS"; +let _testModeOptions = localStorageService.get(TEST_MODE_OPTIONS) || { + enabled: false, + countdownSeconds: 10, + testSeconds: 90 +}; + +localStorageService.save(TEST_MODE_OPTIONS, _testModeOptions); +let _currentlyTestingSlot = ''; +let _currentDeviceSlot = ''; +let _slotBeforeTest = ''; + + +/** + * initializes the instance of the device + * @param dontGetLiveValues if true, live values are not requested by default + * @return {Promise<* | void>} promise resolving with config of the current slot + */ +ATDevice.init = function (dontGetLiveValues) { + _dontGetLiveValues = dontGetLiveValues; + return Promise.resolve().then(() => { + if (C.GUI_IS_MOCKED_VERSION) { + _communicator = new MockCommunicator(); + return Promise.resolve(); + } else if (C.GUI_IS_HOSTED) { + _communicator = new SerialCommunicator(); + return _communicator.init(); + } else if (C.GUI_IS_ON_DEVICE) { + return ws.initWebsocket(C.FLIP_WEBSOCKET_URL).then(function (socket) { + _communicator = new WsCommunicator(C.FLIP_WEBSOCKET_URL, socket); + return Promise.resolve(); + }); + } + }).then(function () { + _isInitialized = true; + return ATDevice.sendAtCmdWithResultForce(C.AT_CMD_VERSION); + }).then((versionString) => { + _lastVersionRawString = versionString; + _lastVersionResult = L.parseVersion(versionString); + console.log("VersionString: " + versionString); + if (versionString.toLowerCase().includes("fabi")) { + C.CURRENT_DEVICE = C.AT_DEVICE_FABI; + C.DEVICE_IS_FLIPPAD = false; + C.DEVICE_IS_FM = false; + C.DEVICE_IS_FABI=true; + C.PYHSICAL_BUTTON_COUNT = 5; // FABI has 5 physical buttons + } + else if (versionString.toLowerCase().includes("flipmouse")) { + C.CURRENT_DEVICE = C.AT_DEVICE_FLIPMOUSE; + C.DEVICE_IS_FLIPPAD = false; + C.DEVICE_IS_FM = true; + C.DEVICE_IS_FABI=false; + C.PYHSICAL_BUTTON_COUNT = 3; // FlipMouse has 3 physical buttons + } + else if (versionString.toLowerCase().includes("flippad")) { + C.CURRENT_DEVICE = C.AT_DEVICE_FLIPPAD; + C.DEVICE_IS_FLIPPAD = true; + C.DEVICE_IS_FM = false; + C.DEVICE_IS_FABI=false; + C.PYHSICAL_BUTTON_COUNT = 3; // FlipPad has 3 physical buttons + } + else { + if (_communicator.close) _communicator.close(); + return Promise.reject(C.ERROR_WRONG_DEVICE); + } + if (!L.isVersionNewer(C.UNIFIED_GUI_MIN_FIRMWARE_VERSION, versionString) && !L.isVersionEqual(C.UNIFIED_GUI_MIN_FIRMWARE_VERSION, versionString)) { + if (_communicator.close) _communicator.close(); + // older than MIN_FW_VERSION + alert(L.translate("You connected a device using a firmware older than version " + C.UNIFIED_GUI_MIN_FIRMWARE_VERSION + ". For switching to the legacy WebGUI, please press Connect again. // Sie haben ein Gerät mit einer Firmware verbunden, die älter ist als Version " + C.UNIFIED_GUI_MIN_FIRMWARE_VERSION + ". Um zur Legacy-WebGUI zu wechseln, bitte erneut Verbinden drücken.")); + return Promise.reject(C.ERROR_LEGACY_FIRMWARE); + } + if (versionString.indexOf(C.PRESSURE_SENSOR_TYPE_NONE)>0) { + _sensorInfo[C.PRESSURE_SENSOR]=false; + console.log("No Pressure Sensor available"); + } else { + _sensorInfo[C.PRESSURE_SENSOR]=true; + console.log("Pressure Sensor found"); + } + _sensorInfo[C.FORCE_SENSOR]=false; + if ((versionString.indexOf(C.FORCE_SENSOR_TYPE_NAU7802)>0) || + (versionString.indexOf(C.FORCE_SENSOR_TYPE_ADC)>0)) { + _sensorInfo[C.FORCE_SENSOR]=true; + console.log("Force Sensor found"); + } else { + console.log("No Force Sensor available"); + } + return ATDevice.refreshConfig(); + }).then(() => { + if (!_dontGetLiveValues) { + ATDevice.sendAtCmdForce(C.AT_CMD_START_REPORTING_LIVE); + _communicator.setValueHandler((data) => { + _liveValueLastUpdate = new Date().getTime(); + if (_valueHandler) { + _valueHandler(data); + } + }); + } + startTestingConnection(); + return Promise.resolve(); + }).catch((error) => { + console.warn(error); + return Promise.reject(error); + }); +} + +ATDevice.isInitialized = function () { + return _isInitialized; +} + +ATDevice.getVersion = function () { + return ATDevice.sendAtCmdWithResultForce(C.AT_CMD_VERSION).then(result => { + _lastVersionRawString = result; + _lastVersionResult = L.parseVersion(result); + return Promise.resolve(L.formatVersion(result)); + }); +} + +ATDevice.getVersionSuffix = function () { + if (!_lastVersionRawString) { + return; + } + let parts = _lastVersionRawString.split(', '); + if (parts.length > 1) { + parts.shift(); + return parts.join(', '); + } + return ""; +} + +ATDevice.isMajorVersion = function (numValue) { + let currentVersion = _lastVersionResult || {}; + return currentVersion.major === numValue; +} + +ATDevice.getMajorVersion = function () { + let currentVersion = _lastVersionResult || {}; + return currentVersion.major; +} +ATDevice.getSensorInfo = function () { + let currentSensorInfo = _sensorInfo || {}; + return currentSensorInfo; +} + +ATDevice.getBTVersion = function () { + ATDevice.sendAtCmdForce(C.AT_CMD_STOP_REPORTING_LIVE); + return ATDevice.sendAtCmdWithResultForce(C.AT_CMD_ADDON_COMMAND, '$ID').then(result => { + result = result || ''; + return Promise.resolve(result.trim() ? L.formatVersion(result) : ''); + }).finally(() => { + if (!_dontGetLiveValues) ATDevice.sendAtCmdForce(C.AT_CMD_START_REPORTING_LIVE); + }); +} + +/** + * Sends the given AT command to the device. If sending of the last command is not completed yet, the given AT command + * is added to a queue and will be sent later. + * The order of sending the commands is always equal to the order of calls to this function. + * + * @param atCmd the AT command to send + * @param param an optional parameter that is appended to the AT command + * @param options.timeout maximum time after the returned promise resolves, regardless if data was received or not. Default 0ms. + * @param options.onlyIfNotBusy if set to true, the command is sent only if no other AT command is currently waiting for a response + * @param options.dontLog if set to true, there are no logs to console for this command + * @param options.forceSend if set to true, AT command is send also in safe mode + * @param options.close if set to true, the communicator is closed after sending the command + * @return {Promise} which resolves to the result of the command or '' if no result was received. + */ +ATDevice.sendATCmd = function (atCmd, param, options) { + options = options || {}; + if (_testModeOptions.enabled && !options.forceSend) { + log.info(`not sending command ${atCmd} command because of safe mode.`); + return Promise.resolve(); + } + if (!ATDevice.isInitialized()) { + return Promise.reject('cannot send AT command if not initialized.'); + } + if (_inRawMode) { + log.warn('not sending AT command because in raw mode.'); + return Promise.reject(); + } + if ((options.onlyIfNotBusy && _atCmdQueue.length > 0)) { + if (!options.dontLog) console.log('did not send cmd: "' + atCmd + "' because another command is executing."); + return Promise.resolve(_AT_CMD_BUSY_RESPONSE); + } + if (_atCmdQueue.length > 0) { + if (!options.dontLog) log.debug("adding cmd to queue: " + atCmd); + } + let queueElem = null; + let cmd = param !== undefined ? atCmd + ' ' + param : atCmd; + let promise = new Promise(function (resolve, reject) { + queueElem = { + timeout: options.timeout || 0, + dontLog: options.dontLog, + cmd: cmd.trim(), + resolveFn: resolve, + rejectFn: reject, + options: options + }; + }); + queueElem.promise = promise; + _atCmdQueue.push(queueElem); + + if (!_sendingAtCmds) { + sendNext(); + } + + function sendNext() { + _sendingAtCmds = true; + if (_atCmdQueue.length === 0) { + _sendingAtCmds = false; + return; + } + let nextQueueElem = _atCmdQueue.shift(); + let waitTimeMs = C.GUI_IS_HOSTED ? 25 : 20; + let timeoutSend = Math.max(0, waitTimeMs - (new Date().getTime() - _timestampLastAtCmd)); + setTimeout(function () { + if (!nextQueueElem.dontLog) console.log("sending to device: " + nextQueueElem.cmd); + _timestampLastAtCmd = new Date().getTime(); + _communicator.sendData(nextQueueElem.cmd, nextQueueElem.timeout, nextQueueElem.dontLog).then(nextQueueElem.resolveFn, nextQueueElem.rejectFn); + nextQueueElem.promise.finally(() => { + if (nextQueueElem.options.close) { + let communicator = ATDevice.getCommunicator(); + if (communicator.close) { + log.info("closing communicator..."); + communicator.close(); + } + } else { + sendNext(); + } + }); + }, timeoutSend); + } + + return promise; +}; + +ATDevice.sendAtCmdForce = function (atCmd, param, options) { + options = options || {}; + options.forceSend = true; + return ATDevice.sendATCmd(atCmd, param, options); +} + +window.sendATCmd = (cmd) => { + ATDevice.sendAtCmdWithResultForce(cmd); +} + +window.sendATCmdNoResult = (cmd) => { + ATDevice.sendAtCmdForce(cmd); +} + +/** + * Sends the given AT command to the device and waits for a response, details @see sendATCmd() + * + * @param atCmd + * @param param + * @param options.timeout the timeout to wait for a response, default: 3000ms + * @return {Promise} + */ +ATDevice.sendAtCmdWithResult = function (atCmd, param, options) { + options = options || {}; + options.timeout = options.timeout || 3000; + let promise = ATDevice.sendATCmd(atCmd, param, options); + return promise; +} + +ATDevice.sendAtCmdWithResultForce = function (atCmd, param, options) { + options = options || {}; + options.forceSend = true; + return ATDevice.sendAtCmdWithResult(atCmd, param, options); +} + +ATDevice.upgradeBTAddon = async function (firmwareArrayBuffer, progressCallback) { + if (!_communicator.sendRawData) { + log.warn('upgrade not supported by communicator!') + return; + } + stopTestingConnection(); + ATDevice.sendAtCmdForce(C.AT_CMD_STOP_REPORTING_LIVE); + ATDevice.sendAtCmdForce(C.AT_CMD_UPGRADE_ADDON); + _inRawMode = true; + return _communicator.waitForReceiving('OTA:ready', 15000).then(() => { + log.info('starting sending raw data'); + return _communicator.sendRawData(firmwareArrayBuffer, progressCallback); + }).then(() => { + return _communicator.waitForReceiving('OTA:$FIN', 20000); + }).then(() => { + log.info('bluetooth upgrade successful!'); + return Promise.resolve(); + }).catch((error) => { + log.warn('BT addon update failed because: ' + error); + return Promise.reject(); + }).finally(() => { + _inRawMode = false; + if (!_dontGetLiveValues) ATDevice.sendAtCmdForce(C.AT_CMD_START_REPORTING_LIVE); + startTestingConnection(); + }); +} + +ATDevice.addConnectionTestHandler = function (fn) { + _connectionTestCallbacks.push(fn); + _connectionTestCallbacks.forEach(fn => fn(_connected)); +} + +ATDevice.setSlotChangeHandler = function (fn) { + _slotChangeHandler = fn; +} + +ATDevice.setLiveValueHandler = function (handler) { + _valueHandler = handler; +}; + + +ATDevice.getConfig = function (constant, slotName) { + let slotConfig = ATDevice.getSlotConfig(slotName || _currentSlot); + if (slotConfig[constant] !== undefined) { + let value = slotConfig[constant] + ''; + let intValue = parseInt(value); + return intValue + '' === value.trim() ? intValue : value; + } + return ''; +}; + +ATDevice.setConfig = async function (atCmd, value, debounceTimeout, slot) { + value = value + ''; + setConfigInternal(atCmd, value, [slot]); + return new Promise(resolve => { + debounceTimeout = debounceTimeout === undefined ? 300 : debounceTimeout; + L.debounce(async function () { + if (_busyMethods['setConfig']) { + log.warn("not doing command because device is busy."); + return; + } + _busyMethods['setConfig'] = true; + if (slot !== _currentSlot) { + await ATDevice.setSlot(slot); + } + ATDevice.sendATCmd(atCmd, value); + _busyMethods['setConfig'] = false; + resolve(); + ATDevice.planSaving(); + }, debounceTimeout, atCmd); + }); +}; + +ATDevice.setConfigForSlot = async function(atCmd, value, slot, debounceTimeout) { + debounceTimeout = debounceTimeout || 0; + return ATDevice.setConfig(atCmd, value, debounceTimeout, slot); +} + +/** + * copies config values from one slot to all other slots + * @param configConstants an array of config constants which values should be copied to all slots + * @param sourceSlot (optional) the source slot to copy the values from, default: current slot + * @param skipInitialSave if true the current slot is not saved (because already saved) + */ +ATDevice.copyConfigToAllSlots = async function (configConstants, sourceSlot, skipInitialSave) { + if (!configConstants || configConstants.length === 0) { + return; + } + let originalSlot = ATDevice.getCurrentSlot(); + sourceSlot = sourceSlot || ATDevice.getCurrentSlot(); + let sourceSlotObject = _slots.filter(slotObject => slotObject.name === sourceSlot)[0]; + if (!skipInitialSave) { + ATDevice.save(); + } + ATDevice.parseLiveData = false; + for (let slotObject of _slots) { + if (slotObject.name !== sourceSlot) { + await ATDevice.sendAtCmdWithResult(C.AT_CMD_LOAD_SLOT, slotObject.name); + let slotChanged = false; + for (let constant of configConstants) { + if (slotObject.config[constant] !== sourceSlotObject.config[constant]) { + slotChanged = true; + slotObject.config[constant] = sourceSlotObject.config[constant]; + if (constant.indexOf(C.AT_CMD_BTN_MODE) !== -1) { + ATDevice.sendATCmd(constant); + ATDevice.sendATCmd(sourceSlotObject.config[constant]); + } else { + ATDevice.sendATCmd(constant, sourceSlotObject.config[constant]); + } + } + } + if (slotChanged) { + emitConfigChange(); + await ATDevice.save(slotObject.name); + } + } + } + await ATDevice.sendAtCmdWithResult(C.AT_CMD_LOAD_SLOT, originalSlot); + ATDevice.parseLiveData = true; + return Promise.resolve(); +} + +ATDevice.refreshConfig = function () { + return new Promise(function (resolve, reject) { + ATDevice.sendAtCmdForce(C.AT_CMD_STOP_REPORTING_LIVE); + ATDevice.sendAtCmdWithResultForce(C.AT_CMD_LOAD_ALL).then(function (response) { + _slots = ATDevice.parseConfig(response); + _slotsBackup = JSON.parse(JSON.stringify(_slots)); + _currentSlot = _currentSlot || _slots[0].name; + _currentDeviceSlot = _currentSlot; + emitConfigChange(); + resolve(); + }, function () { + console.log("could not get config!"); + reject(); + }).finally(() => { + if (!_dontGetLiveValues) ATDevice.sendAtCmdForce(C.AT_CMD_START_REPORTING_LIVE); + }); + }); +}; + +ATDevice.getButtonAction = function (buttonModeIndex, slot) { + buttonModeIndex = parseInt(buttonModeIndex); + return ATDevice.getConfig(C.AT_CMD_BTN_MODE + " " + buttonModeIndex, slot); +} + +ATDevice.setButtonAction = function (buttonModeIndex, atCmd) { + if (buttonModeIndex === undefined || !atCmd) { + return; + } + buttonModeIndex = parseInt(buttonModeIndex); + setConfigInternal(C.AT_CMD_BTN_MODE + " " + buttonModeIndex, atCmd); + ATDevice.sendATCmd(C.AT_CMD_BTN_MODE, buttonModeIndex); + ATDevice.sendATCmd(atCmd); + ATDevice.save(); +}; + +ATDevice.getButtonActionATCmd = function (index, slot) { + let action = ATDevice.getButtonAction(index, slot); + return action ? action.substring(0, C.LENGTH_AT_CMD_PREFIX).trim() : null; +} + +ATDevice.getButtonActionATCmdSuffix = function (index, slot) { + let action = ATDevice.getButtonAction(index, slot); + return action ? action.substring(C.LENGTH_AT_CMD_PREFIX).trim() : null; +} + +ATDevice.save = async function (slot, force) { + slot = slot || _currentSlot; + if (!slot) { + return; + } + ATDevice.abortAutoSaving(); + return ATDevice.sendAtCmdWithResult(C.AT_CMD_SAVE_SLOT, slot, { + forceSend: force + }); +}; + +ATDevice.planSaving = function () { + L.debounce(() => { + ATDevice.save(); + }, _autoSaveTimeout, 'ATDEVICE_SAVE'); +} + +ATDevice.abortAutoSaving = function () { + L.clearDebounce('ATDEVICE_SAVE'); +} + +ATDevice.getSlots = function () { + return _slots.map(slotObject => slotObject.name); +}; + +ATDevice.getAllSlotObjects = function () { + return JSON.parse(JSON.stringify(_slots)); +} + +ATDevice.getAllSlotBackupObjects = function () { + return JSON.parse(JSON.stringify(_slotsBackup)); +} + +ATDevice.getSlotConfig = function (slotName) { + let object = _slots.filter(slot => slot.name === slotName)[0]; + return object && object.config ? object.config : {}; +} + +ATDevice.getSlotName = function (id) { + id = id || 0; + return _slots[id] ? _slots[id].name : ''; +} + +ATDevice.getCurrentSlot = function () { + return _currentSlot; +}; + +ATDevice.getSlotConfigText = function (slotName) { + let config = ATDevice.getSlotConfig(slotName); + let ret = "Slot:" + slotName + "\n"; + + Object.keys(config).forEach(function (key) { + if (key.indexOf(C.AT_CMD_BTN_MODE) > -1) { + ret = ret + key + '\n' + config[key] + "\n"; + } else { + ret = ret + key + ' ' + config[key] + "\n"; + } + }); + + return ret; +} + +ATDevice.handleSlotChangeFromDevice = function (deviceSlot) { + if (!deviceSlot) { + return; + } + if (ATDevice.isSlotTestMode() && ATDevice.isTesting()) { + if (deviceSlot === _currentlyTestingSlot && _currentDeviceSlot !== deviceSlot) { // switched back to current testing slot on device + applySlotChangesToDevice(); + } + } + _currentDeviceSlot = deviceSlot; + if (!ATDevice.isSlotTestMode()) { + ATDevice.setSlot(deviceSlot, true); + } +} + +ATDevice.setSlot = async function (slot, dontSendToDevice) { + let promise = Promise.resolve(); + if (slot === _currentSlot) { + return Promise.resolve(); + } + if (_busyMethods['setSlot']) { + log.warn("not doing command because device is busy."); + return; + } + _busyMethods['setSlot'] = true; + if (ATDevice.getSlots().includes(slot)) { + if (!dontSendToDevice) { + ATDevice.parseLiveData = false; //prevent to parse old slot from live values before new slot applied on device + await ATDevice.save(); + promise = ATDevice.sendAtCmdWithResult(C.AT_CMD_LOAD_SLOT, slot); + ATDevice.sendATCmd(C.AT_CMD_CALIBRATION); + } + _currentSlot = slot; + } + promise.finally(() => { + ATDevice.parseLiveData = true; + _busyMethods['setSlot'] = false; + }); + emitSlotChange(); + return promise; +}; + +ATDevice.createSlot = function (slotName) { + if (!slotName || ATDevice.getSlots().includes(slotName)) { + console.warn('slot not saved because no slot name or slot already existing!'); + } + ATDevice.save(); + let slotConfig = ATDevice.getSlotConfig(_currentSlot); + _slots.push({ + name: slotName, + config: L.deepCopy(slotConfig) + }); + ATDevice.save(slotName, true); //create new slot also in save mode + ATDevice.sendATCmd(C.AT_CMD_LOAD_SLOT, slotName); + emitSlotChange(); + return Promise.resolve(); +}; + +ATDevice.deleteSlot = function (slotName) { + if (!slotName || !ATDevice.getSlots().includes(slotName)) { + console.warn('slot not deleted because no slot name or slot not existing!'); + } + _slots = _slots.filter(slotObject => slotObject.name !== slotName); + ATDevice.sendATCmd(C.AT_CMD_DELETE_SLOT, slotName); + if (slotName === _currentSlot) { + _currentSlot = ATDevice.getSlots()[0]; + if (_currentSlot) { + ATDevice.sendATCmd(C.AT_CMD_LOAD_SLOT, _currentSlot); + } + } + emitSlotChange(); + return Promise.resolve(); +}; + +ATDevice.deleteAllSlots = function () { + ATDevice.sendATCmd(C.AT_CMD_DELETE_SLOT); + _slots = []; + _currentSlot = ''; + emitSlotChange(); +} + +ATDevice.sendAudio = async function(wavBuffer) { + + let serialCommunicator = ATDevice.getCommunicator(); + let failed = false; + if (!serialCommunicator.sendRawData) { + log.warn('audio upload not supported by communicator!') + return; + } + + try { + console.log("Sending Wav File to FABI"); + await serialCommunicator.sendAudioData(wavBuffer); + // console.log("Sent to serial:", wavBuffer); + console.log("Done"); + } catch (error) { + console.error("Error sending data to serial device:", error); + } +} + + + +ATDevice.uploadSlots = async function (slotObjects, progressHandler) { + ATDevice.save(); + let slotObject = null; + progressHandler = progressHandler || (() => {}); + progressHandler(1); + for (let i = 0; i < slotObjects.length; i++) { + slotObject = slotObjects[i]; + Object.keys(slotObject.config).forEach(function (key) { + if (key.indexOf(C.AT_CMD_BTN_MODE) > -1) { + ATDevice.sendATCmd(key); + ATDevice.sendATCmd(slotObject.config[key]); + } else { + ATDevice.sendATCmd(key, slotObject.config[key]); + } + }); + await ATDevice.save(slotObject.name, true); + progressHandler(Math.round((i+1) / slotObjects.length * 100)); + _slots.push(slotObject); + } + progressHandler(100); + if (slotObjects[0]) { + ATDevice.sendATCmd(C.AT_CMD_LOAD_SLOT, slotObjects[0].name); + _currentSlot = slotObjects[0].name; + emitSlotChange(); + } +} + +ATDevice.restoreDefaultConfiguration = function () { + ATDevice.sendAtCmdForce(C.AT_CMD_RESET_DEVICE); + _currentSlot = null; + _slots = []; + let promise = ATDevice.refreshConfig(); + promise.then(() => { + emitSlotChange(); + ATDevice.calibrate(); + }) + return promise; +}; + +ATDevice.parseConfig = function(atCmdsString) { + atCmdsString = atCmdsString.replace(/\n\s*\n/g, '\n'); //replace doubled linebreaks with single one + let elements = atCmdsString.split('\n'); + let parsedSlots = []; + let currentParsedSlot = null; + for (let i = 0; i < elements.length; i++) { + let currentElement = elements[i]; + let nextElement = elements[i + 1] || ''; + if (currentElement.indexOf(_SLOT_CONSTANT) > -1) { + let slotName = currentElement.substring(currentElement.indexOf(':') + 1).trim(); + currentParsedSlot = { + name: slotName, + config: {} + }; + parsedSlots.push(currentParsedSlot); + } else { + let currentAtCmd = currentElement.substring(0, C.LENGTH_AT_CMD_PREFIX - 1).trim(); + if (currentAtCmd.indexOf(C.AT_CMD_BTN_MODE) > -1) { + let buttonModeIndex = parseInt(currentElement.substring(C.LENGTH_AT_CMD_PREFIX - 1)); + currentParsedSlot.config[C.AT_CMD_BTN_MODE + ' ' + buttonModeIndex] = nextElement.trim(); + } else if (C.AT_CMDS_SETTINGS.indexOf(currentAtCmd) > -1) { + currentParsedSlot.config[currentAtCmd] = currentElement.substring(C.LENGTH_AT_CMD_PREFIX - 1).trim(); + } + } + } + return parsedSlots; +} + +ATDevice.getCommunicator = function () { + return _communicator; +} + +ATDevice.isSlotTestMode = function () { + return _testModeOptions.enabled; +} + +ATDevice.setSlotTestModeOptions = function (options) { + options = options || {}; + if (!_testModeOptions.enabled && options.enabled) { + _slotsBackup = JSON.parse(JSON.stringify(_slots)); + } + _testModeOptions = Object.assign(_testModeOptions, options); + localStorageService.save(TEST_MODE_OPTIONS, _testModeOptions); + if (_slotChangeHandler) { // repaint MainView + _slotChangeHandler(); + } +} + +ATDevice.getSlotTestModeOptions = function () { + return JSON.parse(JSON.stringify(_testModeOptions)); +} + +ATDevice.revertCurrentSlot = function () { + if (!_testModeOptions.enabled) { + return; + } + ATDevice.stopTestingCurrentSlot(); + let backupSlotNames = _slotsBackup.map(slot => slot.name); + let deviceSlot = _slotsBackup.filter(slot => slot.name === _currentSlot)[0]; + let guiSlot = _slots.filter(slot => slot.name === _currentSlot)[0]; + if (backupSlotNames.includes(_currentSlot)) { + _slots[_slots.indexOf((guiSlot))] = JSON.parse(JSON.stringify(deviceSlot)); + emitSlotChange(); + } + window.dispatchEvent(new CustomEvent(C.EVENT_REFRESH_MAIN)); +} + +ATDevice.testCurrentSlot = function () { + _slotBeforeTest = _currentDeviceSlot; + _currentlyTestingSlot = _currentSlot; + if (_currentSlot !== _currentDeviceSlot) { + ATDevice.sendAtCmdForce(C.AT_CMD_LOAD_SLOT, _currentSlot); + } + applySlotChangesToDevice(); + window.dispatchEvent(new CustomEvent(C.EVENT_REFRESH_MAIN)); +} + +ATDevice.isTesting = function () { + return !!_currentlyTestingSlot; +} + +ATDevice.stopTestingCurrentSlot = function () { + if (_currentlyTestingSlot) { + _currentlyTestingSlot = ''; + ATDevice.sendAtCmdForce(C.AT_CMD_LOAD_SLOT, _slotBeforeTest); + window.dispatchEvent(new CustomEvent(C.EVENT_REFRESH_MAIN)); + } +} + +ATDevice.approveCurrentSlot = function () { + let originalSlot = _currentDeviceSlot; + if (_currentSlot !== _currentDeviceSlot) { + ATDevice.sendAtCmdForce(C.AT_CMD_LOAD_SLOT, _currentSlot); + } + applySlotChangesToDevice(); + ATDevice.sendAtCmdForce(C.AT_CMD_SAVE_SLOT, _currentSlot); + ATDevice.sendAtCmdForce(C.AT_CMD_LOAD_SLOT, originalSlot); + let backupSlotNames = _slotsBackup.map(slot => slot.name); + let deviceSlot = _slotsBackup.filter(slot => slot.name === _currentSlot)[0]; + let guiSlot = _slots.filter(slot => slot.name === _currentSlot)[0]; + if (backupSlotNames.includes(_currentSlot)) { + _slotsBackup[_slotsBackup.indexOf((deviceSlot))] = JSON.parse(JSON.stringify(guiSlot)); + } else { + _slotsBackup.push(JSON.parse(JSON.stringify(guiSlot))); + } + window.dispatchEvent(new CustomEvent(C.EVENT_REFRESH_MAIN)); +} + +ATDevice.hasUnsavedChanges = function () { + let guiSlotConfig = _slots.filter(slot => slot.name === _currentSlot)[0].config; + let deviceSlot = _slotsBackup.filter(slot => slot.name === _currentSlot)[0]; + let deviceSlotConfig = deviceSlot ? deviceSlot.config : {}; + return JSON.stringify(guiSlotConfig) !== JSON.stringify(deviceSlotConfig) || _currentDeviceSlot !== _currentSlot; +} + +function applySlotChangesToDevice() { + let guiSlotConfig = _slots.filter(slot => slot.name === _currentSlot)[0].config; + let deviceSlot = _slotsBackup.filter(slot => slot.name === _currentSlot)[0]; + let deviceSlotConfig = deviceSlot ? deviceSlot.config : {}; + let cmd = ''; + Object.keys(guiSlotConfig).forEach(key => { + if (deviceSlotConfig[key] !== guiSlotConfig[key]) { + if (key.indexOf(C.AT_CMD_BTN_MODE) > -1) { + cmd += key + '\n'; + cmd += guiSlotConfig[key] + '\n'; + } else { + cmd += key + " " + guiSlotConfig[key] + '\n'; + } + } + }); + ATDevice.sendAtCmdForce(cmd); +} + +function emitSlotChange() { + emitConfigChange(); + if (_slotChangeHandler && new Date().getTime() - _lastSlotChangeTime > 200) { + _lastSlotChangeTime = new Date().getTime(); + _slotChangeHandler(); + } +} + +function emitConfigChange() { + window.dispatchEvent(new CustomEvent(C.EVENT_CONFIG_CHANGED)); +} + +function setConfigInternal(constant, value, slots) { + slots = slots && slots[0] ? slots : [_currentSlot]; + slots.forEach(slot => { + let slotConfig = ATDevice.getSlotConfig(slot); + if (slotConfig) { + slotConfig[constant] = value; + } + }); + emitConfigChange(); +} + +ATDevice.getIRCommands = function () { + return ATDevice.sendAtCmdWithResult(C.AT_CMD_IR_LIST).then(result => { + return Promise.resolve(result.split('\n').map(elem => elem.split(':')[1]).filter(elem => !!elem).map(e => e.trim())); + }); +}; + +ATDevice.recordIrCommand = function (name) { + return ATDevice.sendAtCmdWithResult(C.AT_CMD_IR_RECORD, name, {timeout: 11000}).then(result => { + let success = result && result.indexOf(_AT_CMD_IR_TIMEOUT_RESPONSE) === -1; + return Promise.resolve(success); + }); +}; + +ATDevice.rotate = function () { + let currentOrientation = ATDevice.getConfig(C.AT_CMD_ORIENTATION_ANGLE); + ATDevice.setConfig(C.AT_CMD_ORIENTATION_ANGLE, (currentOrientation + 90) % 360, 0); + ATDevice.sendATCmd('AT CA'); + ATDevice.planSaving(); +}; + +ATDevice.calibrate = function () { + ATDevice.sendAtCmdForce('AT CA'); +}; + +ATDevice.setStickMode = function (index) { + index = parseInt(index); + if (!C.STICK_MODES.map(mode => mode.value).includes(index)) { + return; + } + ATDevice.planSaving(); + return ATDevice.setConfig(C.AT_CMD_STICK_MODE, index, 0); +}; + +ATDevice.setAutoDwellTime = function (dwelltime) { + let vol = parseInt(dwelltime); + // console.log("setAutoDwellTime", dwelltime); + ATDevice.planSaving(); + return ATDevice.setConfig(C.AT_CMD_THRESHOLD_AUTODWELL, dwelltime, 0); +}; + +ATDevice.setAudioVolume = function (volume) { + let vol = parseInt(volume); + // console.log("setAudioVolume", volume); + ATDevice.planSaving(); + return ATDevice.setConfig(C.AT_CMD_AUDIO_VOLUME, vol, 0); +}; + +ATDevice.startLiveValueListener = function (handler) { + _liveValueHandler = handler; +}; + +ATDevice.stopLiveValueListener = function () { + _liveValueHandler = null; +}; + +ATDevice.getLiveData = function (constant) { + if (constant) { + return _liveData[constant]; + } + return _liveData; +}; + +ATDevice.resetMinMaxLiveValues = function () { + _liveData[C.LIVE_PRESSURE_MIN] = 1024; + _liveData[C.LIVE_MOV_X_MIN] = 1024; + _liveData[C.LIVE_MOV_Y_MIN] = 1024; + _liveData[C.LIVE_PRESSURE_MAX] = -1; + _liveData[C.LIVE_MOV_X_MAX] = -1; + _liveData[C.LIVE_MOV_Y_MAX] = -1; +}; + +ATDevice.updateFirmware = async function (url, progressHandler, dontReset) { + localStorageService.setFirmwareDownloadUrl(url); + let serialCommunicator = ATDevice.getCommunicator(); + let failed = false; + + /* + if (!dontReset) { + await serialCommunicator.close(); + await TeensyFirmwareUpdater.resetDevice(serialCommunicator.getSerialPort()); + } + await TeensyFirmwareUpdater.uploadFirmware(url, progressHandler).catch(() => { + failed = true; + window.location.reload(); + }); + if (!failed) { + localStorageService.setFirmwareDownloadUrl(''); + + if (!window.location.href.includes(C.SUCCESS_FIRMWAREUPDATE)) { + window.location.replace(window.location.href = window.location.href + '?' + C.SUCCESS_FIRMWAREUPDATE); + } + setTimeout(() => { + window.location.reload(); + }, 100); + } + */ +} + + +ATDevice.resetDevice = async function (existingPort, filters) { + //open & close + // Wait for the serial port to open. + let port = existingPort; + filters = filters || []; + if (!port) { + port = await navigator.serial.requestPort({filters}); + } + await port.open({baudRate: 1200}); + await firmwareUtil.wait(500); + await port.close(); + log.info('reset done!'); +} + + +ATDevice.enterFwDownloadMode = async function () { + let serialCommunicator = ATDevice.getCommunicator(); + await serialCommunicator.close(); + await ATDevice.resetDevice(serialCommunicator.getSerialPort()); +} + +function parseLiveData(data) { + if (!ATDevice.parseLiveData) { + return; + } + if (Object.keys(_liveData).length === 0) { + ATDevice.resetMinMaxLiveValues() + } + if (!data) { + return; + } + + let interval = _liveValueHandler ? 0 : 300; + if (new Date().getTime() - _lastLiveValueParse > interval) { + _lastLiveValueParse = new Date().getTime(); + let valArray = data.split(':')[1].split(','); + _liveData[C.LIVE_PRESSURE] = parseInt(valArray[0]); + _liveData[C.LIVE_DOWN] = parseInt(valArray[1]); + _liveData[C.LIVE_UP] = parseInt(valArray[2]); + _liveData[C.LIVE_RIGHT] = parseInt(valArray[3]); + _liveData[C.LIVE_LEFT] = parseInt(valArray[4]); + _liveData[C.LIVE_MOV_X] = parseInt(valArray[5]); + _liveData[C.LIVE_MOV_Y] = parseInt(valArray[6]); + if (valArray[7]) { + _liveData[C.LIVE_BUTTONS] = valArray[7].split('').map(v => v === "1"); + } + if (valArray[8]) { + let slot = ATDevice.getSlotName(parseInt(valArray[8])); + ATDevice.handleSlotChangeFromDevice(slot); + } + _liveData[C.LIVE_PRESSURE_MIN] = L.robustMin(_liveData[C.LIVE_PRESSURE_MIN], _liveData[C.LIVE_PRESSURE]); + _liveData[C.LIVE_MOV_X_MIN] = L.robustMin(_liveData[C.LIVE_MOV_X_MIN], _liveData[C.LIVE_MOV_X]); + _liveData[C.LIVE_MOV_Y_MIN] = L.robustMin(_liveData[C.LIVE_MOV_Y_MIN], _liveData[C.LIVE_MOV_Y]); + _liveData[C.LIVE_PRESSURE_MAX] = L.robustMax(_liveData[C.LIVE_PRESSURE_MAX], _liveData[C.LIVE_PRESSURE]); + _liveData[C.LIVE_MOV_X_MAX] = L.robustMax(_liveData[C.LIVE_MOV_X_MAX], _liveData[C.LIVE_MOV_X]); + _liveData[C.LIVE_MOV_Y_MAX] = L.robustMax(_liveData[C.LIVE_MOV_Y_MAX], _liveData[C.LIVE_MOV_Y]); + + if (_liveValueHandler) { + _liveValueHandler(_liveData); + } + } +} + + +function startTestingConnection() { + if (_connectionTestIntervalHandler) { + return; + } + + function doTest() { + _connected = !_liveValueLastUpdate || new Date().getTime() - _liveValueLastUpdate < 1000; + _connectionTestCallbacks.forEach(fn => fn(_connected)); + } + + doTest(); + _connectionTestIntervalHandler = setInterval(doTest, 500); +} + +function stopTestingConnection() { + clearInterval(_connectionTestIntervalHandler); +} + +window.addEventListener('beforeunload', () => { + if (ATDevice.isInitialized()) { + log.info('saving config before closing...'); + //sending in one command because two are not possible in beforeunload + let cmd = C.AT_CMD_SAVE_SLOT + ' ' + _currentSlot + '\n' + C.AT_CMD_STOP_REPORTING_LIVE; + if (_testModeOptions.enabled) { + cmd = C.AT_CMD_LOAD_SLOT + ' ' + _currentDeviceSlot + '\n' + C.AT_CMD_STOP_REPORTING_LIVE; + } + ATDevice.sendAtCmdForce(cmd, "", { + close: true + }); + } +}); + + +ATDevice.setLiveValueHandler(parseLiveData); + +export {ATDevice}; diff --git a/webgui/js_new/constantsATCommands.js b/webgui/js_new/constantsATCommands.js new file mode 100644 index 0000000..c97cb38 --- /dev/null +++ b/webgui/js_new/constantsATCommands.js @@ -0,0 +1,387 @@ +import { ATDevice } from "./communication/ATDevice.js"; + +// AT commands - general +C.AT_CMD_VERSION = 'AT ID'; +C.AT_CMD_BTN_MODE = 'AT BM'; + +// AT commands - USB HID +C.AT_CMD_CLICK_MOUSE_L = 'AT CL'; +C.AT_CMD_CLICK_MOUSE_R = 'AT CR'; +C.AT_CMD_CLICK_MOUSE_M = 'AT CM'; +C.AT_CMD_DOUBLECLICK_MOUSE_L = 'AT CD'; + +C.AT_CMD_HOLD_MOUSE_L = 'AT HL'; +C.AT_CMD_HOLD_MOUSE_R = 'AT HR'; +C.AT_CMD_HOLD_MOUSE_M = 'AT HM'; + +C.AT_CMD_RELEASE_MOUSE_L = 'AT RL'; +C.AT_CMD_RELEASE_MOUSE_R = 'AT RR'; +C.AT_CMD_RELEASE_MOUSE_M = 'AT RM'; + +C.AT_CMD_MOUSE_TOGGLE_L = 'AT TL'; +C.AT_CMD_MOUSE_TOGGLE_R = 'AT TR'; +C.AT_CMD_MOUSE_TOGGLE_M = 'AT TM'; + +C.AT_CMD_MOUSEWHEEL_UP = 'AT WU'; +C.AT_CMD_MOUSEWHEEL_DOWN = 'AT WD'; + +C.AT_CMD_MOUSE_MOVEX = 'AT MX'; +C.AT_CMD_MOUSE_MOVEY = 'AT MY'; +C.AT_CMD_ORIENTATION_ANGLE = 'AT RO'; + +C.AT_CMD_JOYSTICK_AXIS0 = 'AT J0'; +C.AT_CMD_JOYSTICK_AXIS1 = 'AT J1'; +C.AT_CMD_JOYSTICK_AXIS2 = 'AT J2'; +C.AT_CMD_JOYSTICK_AXIS3 = 'AT J3'; +C.AT_CMD_JOYSTICK_AXIS4 = 'AT J4'; +C.AT_CMD_JOYSTICK_AXIS5 = 'AT J5'; +C.AT_CMD_JOYSTICK_BUTTON_PRESS = 'AT JP'; +C.AT_CMD_JOYSTICK_BUTTON_RELEASE = 'AT JR'; +C.AT_CMD_JOYSTICK_HAT_POS = 'AT JH'; + +C.AT_CMD_WRITEWORD = 'AT KW'; +C.AT_CMD_KEYPRESS = 'AT KP'; +C.AT_CMD_KEYHOLD = 'AT KH'; +C.AT_CMD_KEYTOGGLE = 'AT KT'; +C.AT_CMD_KEYRELEASE = 'AT KR'; +C.AT_CMD_KEYRELEASEALL = 'AT RA'; + +// AT commands - Macro specific +C.AT_CMD_MACRO = 'AT MA'; +C.AT_CMD_WAIT = 'AT WA'; + +// AT commands - Housekeeping +C.AT_CMD_SAVE_SLOT = 'AT SA'; +C.AT_CMD_LOAD_SLOT = 'AT LO'; +C.AT_CMD_LOAD_ALL = 'AT LA'; +C.AT_CMD_NEXT_SLOT = 'AT NE'; +C.AT_CMD_DELETE_SLOT = 'AT DE'; +C.AT_CMD_RESET_DEVICE = 'AT RS'; +C.AT_CMD_NO_CMD = 'AT NC'; +C.AT_CMD_DEVICE_MODE = 'AT BT'; +C.AT_CMD_BUZZER_MODE = 'AT AB'; +C.AT_CMD_SET_COLOR = 'AT SC'; +C.AT_CMD_KEYBOARD_LAYOUT = 'AT KL'; + +C.DEVICE_MODE_USB = 1; +C.DEVICE_MODE_BT = 2; +C.DEVICE_MODE_USB_BT = 3; + +C.AT_CMD_STICK_MODE = 'AT MM'; +C.AT_CMD_START_REPORTING_LIVE = 'AT SR'; +C.AT_CMD_STOP_REPORTING_LIVE = 'AT ER'; +C.AT_CMD_CALIBRATION = 'AT CA'; +C.AT_CMD_SENSITIVITY_X = 'AT AX'; +C.AT_CMD_SENSITIVITY_Y = 'AT AY'; +C.AT_CMD_DEADZONE_X = 'AT DX'; +C.AT_CMD_DEADZONE_Y = 'AT DY'; +C.AT_CMD_MAX_SPEED = 'AT MS'; +C.AT_CMD_ACCELERATION = 'AT AC'; +C.AT_CMD_SENSORBOARD = 'AT SB'; + +C.AT_CMD_SIP_THRESHOLD = 'AT TS'; +C.AT_CMD_PUFF_THRESHOLD = 'AT TP'; +C.AT_CMD_PUFF_STRONG_THRESHOLD = 'AT SP'; +C.AT_CMD_SIP_STRONG_THRESHOLD = 'AT SS'; + +// AT commands - Infrared specific +C.AT_CMD_IR_RECORD = 'AT IR'; +C.AT_CMD_IR_PLAY = 'AT IP'; +C.AT_CMD_IR_HOLD = 'AT IH'; +C.AT_CMD_IR_STOP = 'AT IS'; +C.AT_CMD_IR_DELETE = 'AT IC'; +C.AT_CMD_IR_WIPE = 'AT IW'; +C.AT_CMD_IR_LIST = 'AT IL'; +C.AT_CMD_IR_TIMEOUT = 'AT IT'; + +// AT commands - Macro specific +C.AT_CMD_MACRO = 'AT MA'; +C.AT_CMD_WAIT = 'AT WA'; + +// AT commands - Audio specific +C.AT_CMD_AUDIO_TRANSMISSION = 'AT AT'; +C.AT_CMD_AUDIO_VOLUME = 'AT AV'; +C.AT_CMD_AUDIO_REMOVE = 'AT AR'; +C.AT_CMD_AUDIO_PLAY = 'AT AP'; + +// AT commands - Timing specific +C.AT_CMD_THRESHOLD_AUTODWELL = 'AT AD'; +C.AT_CMD_THRESHOLD_LONGPRESS = 'AT LP'; +C.AT_CMD_THRESHOLD_MULTIPRESS = 'AT MP'; +// TBD: handle long press / multi press etc, decide if/how to integrate anti-tremor timings ... +// C.AT_CMD_ANTITREMOR_PRESS = 'AT AP'; +// C.AT_CMD_ANTITREMOR_RELEASE = 'AT AR'; +// C.AT_CMD_ANTITREMOR_IDLE = 'AT AI'; + + +// AT commands ESP32 BT-Addon specific (only supported by FlipMouse) +C.AT_CMD_ADDON_COMMAND = 'AT BC'; +C.AT_CMD_UPGRADE_ADDON = 'AT UG'; + + +// Constants for live values (sent from the device) +C.LIVE_VALUE_CONSTANT = 'VALUES:'; + + +// AT command selection for slot settings (TBD: enable global settings in specific data structure, not only per slot-settings) +C.AT_CMDS_SETTINGS = [C.AT_CMD_BTN_MODE, C.AT_CMD_DEVICE_MODE, C.AT_CMD_SENSITIVITY_X, C.AT_CMD_SENSITIVITY_Y, C.AT_CMD_DEADZONE_X, C.AT_CMD_DEADZONE_Y, +C.AT_CMD_MAX_SPEED, C.AT_CMD_ACCELERATION, C.AT_CMD_STICK_MODE, C.AT_CMD_SIP_THRESHOLD, C.AT_CMD_PUFF_THRESHOLD, C.AT_CMD_PUFF_STRONG_THRESHOLD, +C.AT_CMD_SIP_STRONG_THRESHOLD, C.AT_CMD_ORIENTATION_ANGLE, C.AT_CMD_THRESHOLD_AUTODWELL, C.AT_CMD_THRESHOLD_LONGPRESS, C.AT_CMD_THRESHOLD_MULTIPRESS, +C.AT_CMD_SET_COLOR, C.AT_CMD_SENSORBOARD, C.AT_CMD_KEYBOARD_LAYOUT, C.AT_CMD_AUDIO_VOLUME, C.AT_CMD_BUZZER_MODE]; + + +// AT command categories +C.AT_CMD_CAT_KEYBOARD = 'AT_CMD_CAT_KEYBOARD'; +C.AT_CMD_CAT_MOUSE = 'AT_CMD_CAT_MOUSE'; +C.AT_CMD_CAT_JOYSTICK = 'AT_CMD_CAT_JOYSTICK'; +C.AT_CMD_CAT_DEVICE = 'AT_CMD_CAT_DEVICE'; +C.AT_CMD_CAT_IR = 'AT_CMD_CAT_IR'; +C.AT_CMD_CAT_MACRO = 'AT_CMD_CAT_MACRO'; + +C.INPUTFIELD_TYPE_KEYBOARD = 'INPUTFIELD_TYPE_KEYBOARD'; +C.INPUTFIELD_TYPE_TEXT = 'INPUTFIELD_TYPE_TEXT'; +C.INPUTFIELD_TYPE_NUMBER = 'INPUTFIELD_TYPE_NUMBER'; +C.INPUTFIELD_TYPE_SELECT = 'INPUTFIELD_TYPE_SELECT'; +C.INPUTFIELD_TYPE_MACRO = 'INPUTFIELD_TYPE_MACRO'; + +C.AT_CMD_CATEGORIES = [{ + constant: C.AT_CMD_CAT_KEYBOARD, + label: 'Keyboard // Tastatur' +}, { + constant: C.AT_CMD_CAT_MOUSE, + label: 'Mouse // Maus' +}, { + constant: C.AT_CMD_CAT_DEVICE, + label: 'Device // Gerät' +}, { + constant: C.AT_CMD_CAT_IR, + label: 'Infrared // Infrarot' +}, { + constant: C.AT_CMD_CAT_MACRO, + label: 'Macro // Makro' +}, { + constant: C.AT_CMD_CAT_JOYSTICK, + label: 'Joystick' +}]; + + +// AT command selection for button actions and their description +C.AT_CMDS_ACTIONS = [{ + cmd: C.AT_CMD_NO_CMD, + label: 'No command (empty) // Keine Funktion (leer)', + shortLabel: '(empty) // (leer)', + category: C.AT_CMD_CAT_DEVICE +}, { + cmd: C.AT_CMD_HOLD_MOUSE_L, + label: 'Hold left mouse button (as long as input action) // Linke Maustaste halten (für Dauer der Eingabe-Aktion)', + shortLabel: 'Hold left mouse button // Linke Maustaste halten', + macroLabel: 'Hold left mouse button // Linke Maustaste halten', + category: C.AT_CMD_CAT_MOUSE +}, { + cmd: C.AT_CMD_HOLD_MOUSE_R, + label: 'Hold right mouse button (as long as input action) // Rechte Maustaste halten (für Dauer der Eingabe-Aktion)', + shortLabel: 'Hold right mouse button // Rechte Maustaste halten', + macroLabel: 'Hold right mouse button // Rechte Maustaste halten', + category: C.AT_CMD_CAT_MOUSE +}, { + cmd: C.AT_CMD_HOLD_MOUSE_M, + label: 'Hold middle mouse button (as long as input action) // Mittlere Maustaste halten (für Dauer der Eingabe-Aktion)', + shortLabel: 'Hold middle mouse button // Mittlere Maustaste halten', + macroLabel: 'Hold middle mouse button // Mittlere Maustaste halten', + category: C.AT_CMD_CAT_MOUSE +}, { + cmd: C.AT_CMD_CLICK_MOUSE_L, + label: 'Click left mouse button // Klick linke Maustaste', + category: C.AT_CMD_CAT_MOUSE +}, { + cmd: C.AT_CMD_CLICK_MOUSE_R, + label: 'Click right mouse button // Klick rechte Maustaste', + category: C.AT_CMD_CAT_MOUSE +}, { + cmd: C.AT_CMD_CLICK_MOUSE_M, + label: 'Click middle mouse button (wheel) // Klick mittlere Maustaste (Mausrad)', + shortLabel: 'Click middle mouse button // Klick mittlere Maustaste', + category: C.AT_CMD_CAT_MOUSE +}, { + cmd: C.AT_CMD_DOUBLECLICK_MOUSE_L, + label: 'Double click left mouse button // Doppelklick linke Maustaste', + shortLabel: 'Double click mouse left // Doppelklick linke Maustaste', + category: C.AT_CMD_CAT_MOUSE +}, { + cmd: C.AT_CMD_MOUSE_TOGGLE_L, + label: 'Press or release left mouse button (toggle) // Drücken oder Loslassen linke Maustaste (umschalten)', + shortLabel: 'Toggle left mouse button // Umschalten linke Maustaste', + category: C.AT_CMD_CAT_MOUSE +}, { + cmd: C.AT_CMD_MOUSE_TOGGLE_R, + label: 'Press or release right mouse button (toggle) // Drücken oder Loslassen rechte Maustaste (umschalten)', + shortLabel: 'Toggle right mouse button // Umschalten rechte Maustaste', + category: C.AT_CMD_CAT_MOUSE +}, { + cmd: C.AT_CMD_MOUSE_TOGGLE_M, + label: 'Press or release middle mouse button (toggle) // Drücken oder Loslassen mittlere Maustaste (umschalten)', + shortLabel: 'Toggle middle mouse button // Umschalten mittlere Maustaste', + category: C.AT_CMD_CAT_MOUSE +}, { + cmd: C.AT_CMD_MOUSEWHEEL_UP, + label: 'Scroll down // Nach unten scrollen', + category: C.AT_CMD_CAT_MOUSE +}, { + cmd: C.AT_CMD_MOUSEWHEEL_DOWN, + label: 'Scroll up // Nach oben scrollen', + category: C.AT_CMD_CAT_MOUSE +}, { + cmd: C.AT_CMD_MOUSE_MOVEX, + label: 'Move mouse horizontally (x-axis) // Maus horizontal bewegen (x-Achse)', + shortLabel: 'Move mouse x-axis // Mausbewegung x-Achse', + category: C.AT_CMD_CAT_MOUSE, + input: C.INPUTFIELD_TYPE_NUMBER +}, { + cmd: C.AT_CMD_MOUSE_MOVEY, + label: 'Move mouse vertically (y-axis) // Maus vertikal bewegen (y-Achse)', + shortLabel: 'Move mouse y-axis // Mausbewegung y-Achse', + category: C.AT_CMD_CAT_MOUSE, + input: C.INPUTFIELD_TYPE_NUMBER +}, { + cmd: C.AT_CMD_KEYHOLD, + label: 'Hold key(s) (as long as input action) // Taste(n) halten (für Dauer der Eingabe-Aktion)', + shortLabel: 'Hold key(s) // Taste(n) halten', + macroLabel: 'Hold key(s) // Taste(n) halten', + category: C.AT_CMD_CAT_KEYBOARD, + input: C.INPUTFIELD_TYPE_KEYBOARD +}, { + cmd: C.AT_CMD_KEYPRESS, + label: 'Press key(s) + release automatically // Taste(n) drücken + wieder loslassen', + shortLabel: 'Press key(s) // Taste(n) drücken', + category: C.AT_CMD_CAT_KEYBOARD, + input: C.INPUTFIELD_TYPE_KEYBOARD +}, { + cmd: C.AT_CMD_KEYTOGGLE, + label: 'Press or release key(s) (toggle) // Taste(n) drücken oder auslassen (umschalten)', + shortLabel: 'Press/release key(s) // Taste(n) drücken/auslassen', + category: C.AT_CMD_CAT_KEYBOARD, + input: C.INPUTFIELD_TYPE_KEYBOARD +}, { + cmd: C.AT_CMD_WRITEWORD, + label: 'Write word // Schreibe Wort', + category: C.AT_CMD_CAT_KEYBOARD, + input: C.INPUTFIELD_TYPE_TEXT +}, { + cmd: C.AT_CMD_NEXT_SLOT, + label: 'Load next slot // Nächsten Slot laden', + category: C.AT_CMD_CAT_DEVICE +}, { + cmd: C.AT_CMD_LOAD_SLOT, + label: 'Load slot by name // Slot per Name laden', + category: C.AT_CMD_CAT_DEVICE, + input: C.INPUTFIELD_TYPE_SELECT, + optionsFn: ATDevice.getSlots +}, { + cmd: C.AT_CMD_CALIBRATION, + label: 'Calibrate stick middle position // Stick-Mittelposition kalibrieren', + shortLabel: 'Calibrate stick // Stick kalibrieren', + category: C.AT_CMD_CAT_DEVICE +}, { + cmd: C.AT_CMD_IR_PLAY, + label: 'Play infrared command // Infrarot-Kommando abspielen', + category: C.AT_CMD_CAT_IR, + input: C.INPUTFIELD_TYPE_SELECT, + optionsFn: ATDevice.getIRCommands +}, { + cmd: C.AT_CMD_IR_HOLD, + label: 'Hold infrared command (as long as input action) // Infrarot-Kommando halten (für Dauer der Eingabe-Aktion)', + shortLabel: 'Hold IR command // IR-Kommando halten', + category: C.AT_CMD_CAT_IR, + input: C.INPUTFIELD_TYPE_SELECT, + optionsFn: ATDevice.getIRCommands +}, { + cmd: C.AT_CMD_JOYSTICK_AXIS0, + label: 'Joystick 1 set x-axis // Joystick 1 x-Achse setzen', + category: C.AT_CMD_CAT_JOYSTICK, + input: C.INPUTFIELD_TYPE_NUMBER +}, { + cmd: C.AT_CMD_JOYSTICK_AXIS1, + label: 'Joystick 1 set y-axis // Joystick 1 y-Achse setzen', + category: C.AT_CMD_CAT_JOYSTICK, + input: C.INPUTFIELD_TYPE_NUMBER +}, { + cmd: C.AT_CMD_JOYSTICK_AXIS2, + label: 'Joystick 2 set x-axis // Joystick 2 x-Achse setzen', + category: C.AT_CMD_CAT_JOYSTICK, + input: C.INPUTFIELD_TYPE_NUMBER +}, { + cmd: C.AT_CMD_JOYSTICK_AXIS3, + label: 'Joystick 2 set y-axis // Joystick 2 y-Achse setzen', + category: C.AT_CMD_CAT_JOYSTICK, + input: C.INPUTFIELD_TYPE_NUMBER +}, { + cmd: C.AT_CMD_JOYSTICK_AXIS4, + label: 'Joystick 3 set x-axis // Joystick 3 x-Achse setzen', + category: C.AT_CMD_CAT_JOYSTICK, + input: C.INPUTFIELD_TYPE_NUMBER +}, { + cmd: C.AT_CMD_JOYSTICK_AXIS5, + label: 'Joystick 3 set y-axis // Joystick 3 y-Achse setzen', + category: C.AT_CMD_CAT_JOYSTICK, + input: C.INPUTFIELD_TYPE_NUMBER +}, { + cmd: C.AT_CMD_JOYSTICK_BUTTON_PRESS, + label: 'Hold joystick button (as long as input action) // Joystick-Button halten (für Dauer der Eingabe-Aktion)', + shortLabel: 'Hold joystick button // Joystick-Button halten', + category: C.AT_CMD_CAT_JOYSTICK, + input: C.INPUTFIELD_TYPE_NUMBER, + minValue: 1, + maxValue: 32 +}, { + cmd: C.AT_CMD_JOYSTICK_HAT_POS, + label: 'Set joystick hat position // Joystick Hat-Position setzen', + category: C.AT_CMD_CAT_JOYSTICK, + input: C.INPUTFIELD_TYPE_NUMBER +}, { + cmd: C.AT_CMD_MACRO, + label: 'Custom macro // Benutzerdefiniertes Makro', + category: C.AT_CMD_CAT_MACRO, + input: C.INPUTFIELD_TYPE_MACRO +}]; + +// AT command selection for macro actions (additional to the AT commands for button actions) +C.AT_CMDS_MACRO = C.AT_CMDS_MACRO || []; +C.AT_CMDS_MACRO = [{ + cmd: C.AT_CMD_WAIT, + label: 'Wait time in milliseconds // Warten (Millisekunden)', + input: C.INPUTFIELD_TYPE_NUMBER +}, { + cmd: C.AT_CMD_KEYRELEASE, + label: 'Release specific key(s) // Spezifische Taste(n) auslassen', + shortLabel: 'Release key(s) // Taste(n) auslassen', + category: C.AT_CMD_CAT_KEYBOARD, + input: C.INPUTFIELD_TYPE_KEYBOARD +}, { + cmd: C.AT_CMD_KEYRELEASEALL, + label: 'Release all key(s) // Alle Taste(n) auslassen', + shortLabel: 'Release key(s) // Taste(n) auslassen', + category: C.AT_CMD_CAT_KEYBOARD +}, { + cmd: C.AT_CMD_RELEASE_MOUSE_L, + label: 'Release left mouse button // Linke Maustaste loslassen', + category: C.AT_CMD_CAT_MOUSE +}, { + cmd: C.AT_CMD_RELEASE_MOUSE_R, + label: 'Release right mouse button // Rechte Maustaste loslassen', + category: C.AT_CMD_CAT_MOUSE +}, { + cmd: C.AT_CMD_RELEASE_MOUSE_M, + label: 'Release middle mouse button // Mittlere Maustaste loslassen', + category: C.AT_CMD_CAT_MOUSE +}, { + cmd: C.AT_CMD_JOYSTICK_BUTTON_RELEASE, + label: 'Release joystick button // Joystick-Button auslassen', + category: C.AT_CMD_CAT_JOYSTICK, + input: C.INPUTFIELD_TYPE_NUMBER, + minValue: 1, + maxValue: 32 +}, { + cmd: C.AT_CMD_IR_STOP, + label: 'Stop infrared command // Infrarot-Kommando stoppen', + category: C.AT_CMD_CAT_IR +} ]; diff --git a/webgui/js_new/constantsGeneric.js b/webgui/js_new/constantsGeneric.js new file mode 100644 index 0000000..8078476 --- /dev/null +++ b/webgui/js_new/constantsGeneric.js @@ -0,0 +1,423 @@ +import { TabStick } from "./ui/views/TabStick.js"; +import { TabSlots } from "./ui/views/TabSlots.js"; +import { TabActions } from "./ui/views/TabActions.js"; +import { TabGeneral } from "./ui/views/TabGeneral.js"; +import { TabTimings } from "./ui/views/TabTimings.js"; +import { TabSipPuff } from "./ui/views/TabSipPuff.js"; +import { TabVisualization } from "./ui/views/TabVisualization.js"; + +window.C = window.C || {}; + +C.CURRENT_DEVICE = C.CURRENT_DEVICE || null; // is set in index.html based on the hostname and in ATDevice.js based on the device type + +C.UNIFIED_GUI_MIN_FIRMWARE_VERSION = '3.7.0'; +C.MAX_NUMBER_SLOTS = 10; +C.MAX_LENGTH_SLOTNAME = 11; + +// Device VID / PID filters for connection via USB (defines visible devices in connection dialog) +C.USB_DEVICE_FILTERS = [ + { usbVendorId: 0x2341, usbProductId: 0x8037 }, // Arduino Pro Micro for legacy FABI + { usbVendorId: 0x16c0 }, // Teensy for legacy FlipMouse + { usbVendorId: 0x2e8a }, // Arduino Nano 2040 Connect (RP2040) + { usbVendorId: 0x2341 }, // Arduino Nano 2040 Connect (from 2023 on) + { usbVendorId: 0x2E8A, usbProductId: 0xF10A }, // RaspberryPi PicoW + { usbVendorId: 0x239A, usbProductId: 0xCAFE }, // RaspberryPi PicoW - Adafruit TinyUSB Stack + { usbVendorId: 0x2E8A, usbProductId: 0xF10F } // RaspberryPi Pico2W +]; + +C.AT_DEVICE_FLIPMOUSE = 'FLipMouse'; +C.AT_DEVICE_FLIPPAD = 'FLipPad'; +C.AT_DEVICE_FABI = 'FABI'; + +// HW specific constants, available sensors and their types +C.PHYSICAL_BUTTON_COUNT = 5; // default value for fabi, can be overwritten by the device + +C.PRESSURE_SENSOR = 'PRESSURE_SENSOR'; +C.PRESSURE_SENSOR_TYPE_NONE = 'PressureSensor=None'; +C.PRESSURE_SENSOR_TYPE_DPS310 = 'PressureSensor=DSP310'; +C.PRESSURE_SENSOR_TYPE_MPRLS = 'PressureSensor=MPRLS'; +C.PRESSURE_SENSOR_TYPE_ADC = 'PressureSensor=InternalADC'; + +C.FORCE_SENSOR = 'FORCE_SENSOR'; +C.FORECE_SENSOR_TYPE_NONE = 'ForceSensor=None' +C.FORCE_SENSOR_TYPE_NAU7802 = 'ForceSensor=NAU7802'; +C.FORCE_SENSOR_TYPE_ADC = 'ForceSensor=InternalADC'; + +// Constants for web URLs and WebSocket connections +// TBD: add dynamic links and labels for Flipmouse and Flippad +C.GUI_IS_HOSTED = window.location.href.indexOf('localhost') > -1 || window.location.href.indexOf('asterics.github.io') > -1 || window.location.href.indexOf('file://') > -1 || window.location.hostname.indexOf('asterics') > -1; +C.GUI_IS_ON_DEVICE = !C.GUI_IS_HOSTED; +C.GUI_IS_MOCKED_VERSION = window.location.href.indexOf('mock') > -1; +C.IS_TOUCH_DEVICE = 'ontouchstart' in document.documentElement; + +C.ARE_WEBSOCKET_URL = 'ws://' + window.location.hostname + ':8092/ws/astericsData'; +C.FLIP_WEBSOCKET_URL = 'ws://' + window.location.hostname + ':1804/'; + +// Additional links for the footer of the app +C.ADDITIONAL_LINKS = [{ + label: 'More information about FABI // Mehr Infos zu FABI', + url: 'https://www.asterics-foundation.org/projects/fabi/ // https://www.asterics-foundation.org/projekte-2/fabi/' +}, { + label: 'User manual // Benutzerhandbuch', + url: 'https://github.com/asterics/FABI/blob/master/Documentation/UserManual/Markdown/Fabi%20User%20Manual.md // https://github.com/asterics/FABI/blob/master/Documentation/UserManual/Markdown/Fabi%20Anwendungsanleitung.md', + // url: 'https://github.com/asterics/FLipMouse/blob/master/Documentation/UserManual/Markdown/FLipMouseUserManual.md // https://github.com/asterics/FLipMouse/blob/master/Documentation/UserManual/Markdown/FLipMouseAnwendungsanleitung.md'; + +}, { + label: 'Licensing // Lizenzbestimmungen', + url: 'https://github.com/asterics/FABI/blob/master/LICENSE' +}, { + label: 'Legal Notice // Impressum', + url: 'https://www.asterics-foundation.org/legal-notice/ // https://www.asterics-foundation.org/impressum/' +}] + + +C.MAX_LENGTH_ATCMD = 400; +C.LENGTH_AT_CMD_PREFIX = 6; //with space (e.g. "AT KW ") +C.MAX_LENGTH_VOICEMESSAGE = 100; // max length of voice message for slot change (in characters) + + +// List of views in the main view, with their labels, hashes and visibility functions +C.VIEWS = [{ + object: TabStick, + hash: '#tabStick', + label: 'Stick-Config', + helpHash: '#stick-configuration-tab-stick-config // #stick-konfiguration-tab-stick-config', + visibleFn: (ATDevice) => ATDevice.getSensorInfo()[C.FORCE_SENSOR] +}, { + object: TabSipPuff, + hash: '#tabPuff', + label: 'Sip and Puff // Saug-Puste-Steuerung', + helpHash: '#sip-and-puff-tab-using-a-pressure-sensor // #verwendung-eines-drucksensors-sip-puff---reiter-saug-puste-steuerung', + visibleFn: (ATDevice) => ATDevice.getSensorInfo()[C.PRESSURE_SENSOR] +}, { + object: TabActions, + hash: '#tabActions', + label: 'Actions // Aktionen', + helpHash: '#actions-tab-assigning-button-functions // #zuweisung-der-taster-funktionen-reiter-aktionen' +}, { + object: TabSlots, + hash: '#tabSlots', + label: 'Slots // Slots', + helpHash: '#slots-tab-using-configuration-slots // #verwendung-der-speicherplätze-reiter-slots' +}, { + object: TabTimings, + hash: '#tabTimings', + label: 'Timings', + helpHash: '#timings-tab-antitremor-and-special-functions // #einstellmöglichkeiten-im-reiter-timings', + visibleFn: (ATDevice) => false // TODO: enable this tab, when the timing functionality is implemented +}, { + object: TabGeneral, + hash: '#tabGeneral', + label: 'General // Allgemein', + helpHash: '#general-tab-bluetooth-and-firmware-options // #einstellmöglichkeiten-im-reiter-allgemein' +}, { + object: TabVisualization, + hash: '#tabVis', + label: 'Visualization // Visualisierung', +}]; + +// Default view to show when the app starts +C.VIEW_START_HASH = '#tabActions'; + +// Categories for button actions +C.BTN_CAT_BTN = 'BTN_CAT_BTN'; +C.BTN_CAT_BTN_LONGPRESS = 'BTN_CAT_BTN_LONGPRESS'; +C.BTN_CAT_SIPPUFF = 'BTN_CAT_SIPPUFF'; +C.BTN_CAT_STRONG_SIPPUFF = "BTN_CAT_STRONG_SIPPUFF" +C.BTN_CAT_STICK = 'BTN_CAT_STICK'; +C.BTN_CAT_BTN_STRONG_SIPPUFF = 'BTN_CAT_BTN_STRONG_SIPPUFF'; + +C.BTN_CATEGORIES = [{ + constant: C.BTN_CAT_BTN, + label: 'Buttons' +}, { + constant: C.BTN_CAT_BTN_LONGPRESS, + label: 'Buttons long press // Buttons lange drücken' +}, { + constant: C.BTN_CAT_SIPPUFF, + label: 'Sip/Puff // Ansaugen/Pusten' +}, { + constant: C.BTN_CAT_STRONG_SIPPUFF, + label: 'Strong Sip/Puff // Starkes Ansaugen/Pusten' +}, { + constant: C.BTN_CAT_STICK, + label: 'Stick actions // Stick-Aktionen' +}, { + constant: C.BTN_CAT_BTN_STRONG_SIPPUFF, + label: 'Combine button and strong Sip/Puff actions // Kombiniere button und starke Ansaugen/Pusten Aktionen' +}] + + +// List of button modes and their actions (depending on device type) +export function getBtnModesActionList() { + let currentIndex = 1; + let list = []; + // console.log("C.CURRENT_DEVICE=", C.CURRENT_DEVICE, "C.DEVICE_IS_FABI=", C.DEVICE_IS_FABI, "C.DEVICE_IS_FM=", C.DEVICE_IS_FM, "C.PYHSICAL_BUTTON_COUNT=", C.PYHSICAL_BUTTON_COUNT); + // Add buttons depending on device + if (C.DEVICE_IS_FABI === true) { + // FABI: 5 buttons + for (let i = 1; i <= 5; i++) { + list.push({ + index: currentIndex++, + label: `Button ${i}`, + category: C.BTN_CAT_BTN + }); + } + } else if (C.DEVICE_IS_FM === true) { + // FLIPMOUSE: 3 buttons + for (let i = 1; i <= 3; i++) { + list.push({ + index: currentIndex++, + label: `Button ${i}`, + category: C.BTN_CAT_BTN + }); + } + } + + // Add the rest of the actions (stick, sip/puff, etc.) as before + list = list.concat([ + { + index: currentIndex++, + label: 'Stick Up // Stick rauf', + category: C.BTN_CAT_STICK, + visibleBtnFn: (ATDevice) => ATDevice.getSensorInfo()[C.FORCE_SENSOR] + }, { + index: currentIndex++, + label: 'Stick Down // Stick runter', + category: C.BTN_CAT_STICK, + visibleBtnFn: (ATDevice) => ATDevice.getSensorInfo()[C.FORCE_SENSOR] + }, { + index: currentIndex++, + label: 'Stick Left // Stick links', + category: C.BTN_CAT_STICK, + visibleBtnFn: (ATDevice) => ATDevice.getSensorInfo()[C.FORCE_SENSOR] + }, { + index: currentIndex++, + label: 'Stick Right // Stick rechts', + category: C.BTN_CAT_STICK, + visibleBtnFn: (ATDevice) => ATDevice.getSensorInfo()[C.FORCE_SENSOR] + }, { + index: currentIndex++, + label: 'Sip // Ansaugen', + category: C.BTN_CAT_SIPPUFF, + visibleBtnFn: (ATDevice) => ATDevice.getSensorInfo()[C.PRESSURE_SENSOR] + }, { + index: currentIndex++, + label: 'Puff // Pusten', + category: C.BTN_CAT_SIPPUFF, + visibleBtnFn: (ATDevice) => ATDevice.getSensorInfo()[C.PRESSURE_SENSOR] + }, { + index: currentIndex++, + label: 'Strong Sip // Starkes Ansaugen', + category: C.BTN_CAT_STRONG_SIPPUFF, + visibleBtnFn: (ATDevice) => ATDevice.getSensorInfo()[C.PRESSURE_SENSOR] + }, { + index: currentIndex++, + label: 'Strong Puff // Starks Pusten', + category: C.BTN_CAT_STRONG_SIPPUFF, + visibleBtnFn: (ATDevice) => ATDevice.getSensorInfo()[C.PRESSURE_SENSOR] + }, { + index: currentIndex++, + label: 'Strong Sip + Up // Stark ansaugen + nach oben', + category: C.BTN_CAT_BTN_STRONG_SIPPUFF, + visibleBtnFn: (ATDevice) => ATDevice.getSensorInfo()[C.PRESSURE_SENSOR] && ATDevice.getSensorInfo()[C.FORCE_SENSOR] + }, { + index: currentIndex++, + label: 'Strong Sip + Down // Stark ansaugen + nach unten', + category: C.BTN_CAT_BTN_STRONG_SIPPUFF, + visibleBtnFn: (ATDevice) => ATDevice.getSensorInfo()[C.PRESSURE_SENSOR] && ATDevice.getSensorInfo()[C.FORCE_SENSOR] + }, { + index: currentIndex++, + label: 'Strong Sip + Left // Stark ansaugen + nach links', + category: C.BTN_CAT_BTN_STRONG_SIPPUFF, + visibleBtnFn: (ATDevice) => ATDevice.getSensorInfo()[C.PRESSURE_SENSOR] && ATDevice.getSensorInfo()[C.FORCE_SENSOR] + }, { + index: currentIndex++, + label: 'Strong Sip + Right // Stark ansaugen + nach rechts', + category: C.BTN_CAT_BTN_STRONG_SIPPUFF, + visibleBtnFn: (ATDevice) => ATDevice.getSensorInfo()[C.PRESSURE_SENSOR] && ATDevice.getSensorInfo()[C.FORCE_SENSOR] + }, { + index: currentIndex++, + label: 'Strong Puff + Up // Stark pusten + nach oben', + category: C.BTN_CAT_BTN_STRONG_SIPPUFF, + visibleBtnFn: (ATDevice) => ATDevice.getSensorInfo()[C.PRESSURE_SENSOR] && ATDevice.getSensorInfo()[C.FORCE_SENSOR] + }, { + index: currentIndex++, + label: 'Strong Puff + Down // Stark pusten + nach unten', + category: C.BTN_CAT_BTN_STRONG_SIPPUFF, + visibleBtnFn: (ATDevice) => ATDevice.getSensorInfo()[C.PRESSURE_SENSOR] && ATDevice.getSensorInfo()[C.FORCE_SENSOR] + }, { + index: currentIndex++, + label: 'Strong Puff + Left // Stark pusten + nach links', + category: C.BTN_CAT_BTN_STRONG_SIPPUFF, + visibleBtnFn: (ATDevice) => ATDevice.getSensorInfo()[C.PRESSURE_SENSOR] && ATDevice.getSensorInfo()[C.FORCE_SENSOR] + }, { + index: currentIndex++, + label: 'Strong Puff + Right // Stark pusten + nach rechts', + category: C.BTN_CAT_BTN_STRONG_SIPPUFF, + visibleBtnFn: (ATDevice) => ATDevice.getSensorInfo()[C.PRESSURE_SENSOR] && ATDevice.getSensorInfo()[C.FORCE_SENSOR] + } ]); + + return list; +} +// C.BTN_MODES_ACTIONLIST = getBtnModesActionList(); + + + +/* TDB: handle long press / multiple press actions +}, { + index: currentIndex++, + label: 'Button 1 long press // Button 1 lange drücken', + category: C.BTN_CAT_BTN_LONGPRESS + (...) +*/ + +// Modes for the stick input and their descriptions +C.STICK_MODE_ALT = { + value: 0, + label: 'Alternative actions // Alternative Aktionen', +}; +C.STICK_MODE_MOUSE = { + value: 1, + label: 'Mouse movement // Mausbewegung' +}; +C.STICK_MODE_JOYSTICK_1 = { + value: 2, + label: 'Joystick 1 // Joystick 1', +}; +C.STICK_MODE_JOYSTICK_2 = { + value: 3, + label: 'Joystick 2 // Joystick 2' +}; +C.STICK_MODE_JOYSTICK_3 = { + value: 4, + label: 'Joystick 3 // Joystick 3' +}; +C.STICK_MODE_PAD_JOYSTICK = { + value: 5, + label: 'Mouse (joystick mode) // Maus (Joystick-Modus)' +}; +C.STICK_MODE_PAD_TOUCHPAD = { + value: 6, + label: 'Mouse (touchpad mode) // Maus (Touchpad-Modus)' +}; + +C.FLIPPAD_MODE_STICK_ALTERNATIVE = { + value: 0, + label: 'Alternative actions (stick mode) // Alternative Aktionen (Stick-Modus)' +}; +C.FLIPPAD_MODE_PAD_ALTERNATIVE = { + value: 6, + label: 'Alternative actions (pad mode) // Alternative Aktionen (Pad-Modus)' +}; + +C.STICK_MODES = [C.STICK_MODE_MOUSE, C.STICK_MODE_ALT, C.STICK_MODE_JOYSTICK_1, C.STICK_MODE_JOYSTICK_2, C.STICK_MODE_JOYSTICK_3]; + + +// Events for refreshing views / configuration changes +C.EVENT_CONFIG_CHANGED = "EVENT_CONFIG_CHANGED"; +C.EVENT_REFRESH_MAIN = "EVENT_REFRESH_MAIN"; + +// keycode stuff +C.KEYCODE_MAPPING = []; +C.PRINTABLE_KEYCODES = []; +C.KEYCODE_PREFIX = 'KEY_'; +C.JS_KEYCODE_SHIFT = 16; +C.JS_KEYCODE_CTRL = 17; +C.JS_KEYCODE_ALT = 18; +C.JS_KEYCODE_RIGHTALT = 225; +C.JS_KEYCODE_BACKSPACE = 8; +C.JS_KEYCODE_SPACE = 32; +C.JS_KEYCODE_TAB = 9; +C.JS_KEYCODE_GUI = 91; //Windows / Mac key +C.JS_KEYCODE_F5 = 116; + +// A-Z +for (var code = 65; code <= 90; code++) { + C.KEYCODE_MAPPING[code] = C.KEYCODE_PREFIX + String.fromCharCode(code); + C.PRINTABLE_KEYCODES.push(code); +} + +// 0-9 +for (var code = 48; code <= 57; code++) { + C.KEYCODE_MAPPING[code] = C.KEYCODE_PREFIX + String.fromCharCode(code); + C.PRINTABLE_KEYCODES.push(code); +} + +// F1-F24 +for (var code = 112; code <= 135; code++) { + C.KEYCODE_MAPPING[code] = C.KEYCODE_PREFIX + 'F' + (code - 111); +} + +C.KEYCODE_MAPPING[37] = 'KEY_LEFT'; +C.KEYCODE_MAPPING[38] = 'KEY_UP'; +C.KEYCODE_MAPPING[39] = 'KEY_RIGHT'; +C.KEYCODE_MAPPING[40] = 'KEY_DOWN'; +C.KEYCODE_MAPPING[13] = 'KEY_ENTER'; +C.KEYCODE_MAPPING[27] = 'KEY_ESC'; +C.KEYCODE_MAPPING[C.JS_KEYCODE_BACKSPACE] = 'KEY_BACKSPACE'; +C.KEYCODE_MAPPING[C.JS_KEYCODE_TAB] = 'KEY_TAB'; +C.KEYCODE_MAPPING[36] = 'KEY_HOME'; //pos1 +C.KEYCODE_MAPPING[33] = 'KEY_PAGE_UP'; +C.KEYCODE_MAPPING[34] = 'KEY_PAGE_DOWN'; +C.KEYCODE_MAPPING[46] = 'KEY_DELETE'; +C.KEYCODE_MAPPING[45] = 'KEY_INSERT'; +C.KEYCODE_MAPPING[35] = 'KEY_END'; +C.KEYCODE_MAPPING[144] = 'KEY_NUM_LOCK'; +C.KEYCODE_MAPPING[145] = 'KEY_SCROLL_LOCK'; +C.KEYCODE_MAPPING[C.JS_KEYCODE_SPACE] = 'KEY_SPACE'; +C.KEYCODE_MAPPING[20] = 'KEY_CAPS_LOCK'; +C.KEYCODE_MAPPING[19] = 'KEY_PAUSE'; +C.KEYCODE_MAPPING[C.JS_KEYCODE_SHIFT] = 'KEY_SHIFT'; +C.KEYCODE_MAPPING[C.JS_KEYCODE_CTRL] = 'KEY_CTRL'; +C.KEYCODE_MAPPING[C.JS_KEYCODE_ALT] = 'KEY_ALT'; +C.KEYCODE_MAPPING[C.JS_KEYCODE_GUI] = 'KEY_GUI'; //Windows +C.KEYCODE_MAPPING[C.JS_KEYCODE_RIGHTALT] = 'KEY_RIGHT_ALT'; //Windows +C.KEYCODE_MAPPING[92] = 'KEY_RIGHT_GUI'; +C.KEYCODE_MAPPING[187] = 'KEY_PLUS'; +C.KEYCODE_MAPPING[189] = 'KEY_MINUS'; +C.KEYCODE_MAPPING[300] = 'KEY_ASTERISK'; +C.KEYCODE_MAPPING[301] = 'KEY_SLASH'; +C.KEYCODE_MAPPING[190] = 'KEY_DOT'; +C.KEYCODE_MAPPING[302] = 'KEY_COLON'; +C.KEYCODE_MAPPING[303] = 'KEY_SEMICOLON'; +C.KEYCODE_MAPPING[191] = 'KEY_HASH'; +C.KEYCODE_MAPPING[107] = 'KEY_KP_PLUS'; +C.KEYCODE_MAPPING[109] = 'KEY_KP_MINUS'; +C.KEYCODE_MAPPING[106] = 'KEY_KP_ASTERISK'; +C.KEYCODE_MAPPING[111] = 'KEY_KP_SLASH'; + +C.SUPPORTED_KEYCODES = []; +for (var i = 0; i < 400; i++) { + if (C.KEYCODE_MAPPING[i]) { + C.SUPPORTED_KEYCODES.push(i); + } +} + +C.ERROR_FIRMWARE_OUTDATED = 'ERROR_FIRMWARE_OUTDATED'; +C.ERROR_LEGACY_FIRMWARE = 'ERROR_LEGACY_FIRMWARE'; +C.ERROR_WRONG_DEVICE = 'ERROR_WRONG_DEVICE'; +C.ERROR_SERIAL_DENIED = 'ERROR_SERIAL_DENIED'; +C.ERROR_SERIAL_BUSY = 'ERROR_SERIAL_BUSY'; +C.ERROR_CONNECTION_LOST = 'ERROR_CONNECTION_LOST'; +C.ERROR_SERIAL_CONNECT_FAILED = 'ERROR_SERIAL_CONNECT_FAILED'; +C.ERROR_SERIAL_NOT_SUPPORTED = 'ERROR_SERIAL_NOT_SUPPORTED'; + +C.SUCCESS_FIRMWAREUPDATE = 'fwupdatesuccess'; + +// constants for live value reporting +C.LIVE_PRESSURE = 'LIVE_PRESSURE'; +C.LIVE_PRESSURE_MIN = 'LIVE_PRESSURE_MIN'; +C.LIVE_PRESSURE_MAX = 'LIVE_PRESSURE_MAX'; + +C.LIVE_UP = 'LIVE_UP'; +C.LIVE_DOWN = 'LIVE_DOWN'; +C.LIVE_LEFT = 'LIVE_LEFT'; +C.LIVE_RIGHT = 'LIVE_RIGHT'; +C.LIVE_MOV_X = 'LIVE_MOV_X'; +C.LIVE_MOV_Y = 'LIVE_MOV_Y'; +C.LIVE_MOV_X_MIN = 'LIVE_MOV_X_MIN'; +C.LIVE_MOV_X_MAX = 'LIVE_MOV_X_MAX'; +C.LIVE_MOV_Y_MIN = 'LIVE_MOV_Y_MIN'; +C.LIVE_MOV_Y_MAX = 'LIVE_MOV_Y_MAX'; +C.LIVE_BUTTONS = 'LIVE_BUTTONS'; diff --git a/webgui/js_new/localStorageService.js b/webgui/js_new/localStorageService.js new file mode 100644 index 0000000..d787028 --- /dev/null +++ b/webgui/js_new/localStorageService.js @@ -0,0 +1,50 @@ +let localStorageService = {}; +let KEY_FIRMWARE_DOWNLOAD_URL = 'WEBGUI_KEY_FIRMWARE_DOWNLOAD_URL'; +let storage = null; + +if (typeof (Storage) !== "undefined") { + try { + storage = window.localStorage; + } catch (e) { + log.error('could not access local storage, maybe disabled by user? Error: ' + e) + } + +} + +localStorageService.save = function (key, value) { + if (storage) { + try { + return storage.setItem(C.CURRENT_DEVICE + key, JSON.stringify(value)); + } catch (e) { + log.error(e) + } + } +}; + +localStorageService.get = function (key) { + if (storage) { + try { + let value = storage.getItem(C.CURRENT_DEVICE + key); + if (value === 'undefined') { + return undefined; + } + return value ? JSON.parse(value) : value; + } catch (e) { + log.error(e) + } + } +}; + +localStorageService.hasKey = function (key) { + return storage.getItem(C.CURRENT_DEVICE + key) !== null; +} + +localStorageService.getFirmwareDownloadUrl = function () { + return localStorageService.get(KEY_FIRMWARE_DOWNLOAD_URL) || ""; +}; + +localStorageService.setFirmwareDownloadUrl = function (downloadUrl) { + return localStorageService.save(KEY_FIRMWARE_DOWNLOAD_URL, downloadUrl); +}; + +export {localStorageService}; \ No newline at end of file diff --git a/webgui/js_new/lquery.js b/webgui/js_new/lquery.js new file mode 100644 index 0000000..474cd5e --- /dev/null +++ b/webgui/js_new/lquery.js @@ -0,0 +1,468 @@ +//very lightweight replacement for jquery, +//see https://blog.garstasio.com/you-dont-need-jquery/selectors/#multiple-selectors +import {localStorageService} from "./localStorageService.js"; + +let L = function (selector) { + if (selector instanceof Node) { + return selector; + } + var selectorType = 'querySelectorAll'; + + if (selector.indexOf('#') === 0 && selector.indexOf(' ') === -1) { + selectorType = 'getElementById'; + selector = selector.substr(1, selector.length); + } + + return document[selectorType](selector); +}; + +L.toggle = function () { + var args = Array.prototype.slice.call(arguments); + args.unshift("block"); + toggleInternal(args); +}; + +L.toggleInline = function () { + var args = Array.prototype.slice.call(arguments); + args.unshift("inline"); + toggleInternal(args); +}; + +function toggleInternal(args) { + var displayModeShown = args[0]; + if (!args || args.length < 2) { + return; + } + for (var i = 1; i < args.length; i++) { + var selector = args[i]; + var elems = L.selectAsList(selector); + elems.forEach(function (x) { + if (x.style && x.style.display === "none") { + x.style.display = displayModeShown; + } else { + x.style.display = "none"; + } + }); + } +} + +L.isVisible = function (selector) { + var x = L(selector); + return !(x.style && x.style.display === "none"); +}; + +L.setVisible = function (selector, visible, visibleClass) { + var elems = L.selectAsList(selector); + elems.forEach(function (x) { + if (visible == false) { + x.style.display = "none"; + } else { + x.style.display = visibleClass ? visibleClass : "block"; + } + }); +}; + +L.selectAsList = function (selector) { + var result = L(selector); + if (result && result.length > 0) { + return result; + } + return result && !(result instanceof NodeList) ? [result] : []; +}; + +L.addClass = function (selector, className) { + L.toggleClass(selector, className, false, true); +}; + +L.removeClass = function (selector, className) { + L.toggleClass(selector, className, true, false); +}; + +L.toggleClass = function (selector, className, dontAdd, dontRemove) { + let list = L.selectAsList(selector); + list.forEach(function (elem) { + let classes = elem.className.split(' '); + if (classes.indexOf(className) === -1) { + if (!dontAdd) { + elem.className += ' ' + className; + elem.className = elem.className.trim(); + } + } else if (!dontRemove) { + classes = classes.filter(c => c.trim() !== className); + elem.className = classes.join(' '); + } + }); +} + +L.setSelected = function (selector, selected) { + if (selected == undefined) selected = true; + var list = L.selectAsList(selector); + list.forEach(function (elem) { + if (selected) { + L.addClass(elem, 'selected'); + } else { + L.removeClass(elem, 'selected'); + } + elem.setAttribute('aria-selected', selected); + }); +}; + +L.setValue = function (selector, value) { + var list = L.selectAsList(selector); + list.forEach(function (elem) { + if (elem.value) { + elem.value = value; + } + }); +}; + +L.hasFocus = function (selector) { + return L(selector) == document.activeElement; +}; + +L.val2key = function (val, array) { + for (var key in array) { + if (array[key] == val) { + return key; + } + } + return false; +}; + +L.isFunction = function (functionToCheck) { + var getType = {}; + return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]'; +}; + +L.getIDSelector = function (id) { + return '#' + id; +}; + +L.getPercentage = function (value, minRange, maxRange) { + return (Math.round(((value - minRange) / (maxRange - minRange) * 100) * 1000) / 1000) +}; + +L.limitValue = function (value, min, max) { + return Math.max(Math.min(value, max), min); +} + +L.getMs = function () { + return new Date().getTime(); +}; + +L.deepCopy = function (object) { + return JSON.parse(JSON.stringify(object)); +}; + +L.removeAllChildren = function (selector) { + var elm = L(selector); + elm = elm instanceof NodeList ? elm : [elm]; + elm.forEach(function (elem) { + while (elem.firstChild) { + elem.removeChild(elem.firstChild); + } + }); +}; + +L.createElement = function (tagName, className, inner, style) { + var e = document.createElement(tagName); + e.className = className; + e.style.cssText = style || ''; + if (inner) { + inner = inner instanceof Array ? inner : [inner]; + inner.forEach(function (innerElem) { + if (typeof innerElem === 'string') { + e.innerHTML += innerElem; + } else { + e.appendChild(innerElem); + } + }); + } + + return e; +}; + +/** + * creates a list of `)} + + +
+ +
+ + + +
+ +
+
+
+ +
+
+ +
+
+
+
+
+ ${L.translate('Input help // Eingabehilfe')} +
+
    + ${state.commandList.map((atCmd, index) => { + let atCmdObject = this.getAtCmdObject('AT ' + atCmd.trim()); + let title = L.translate(atCmdObject ? atCmdObject.label : ''); + return html`
  1. + \u24D8 + "${atCmd.trim()}" + - ${L.translate('Command OK // Befehl OK')} + - ${L.translate(state.errorList[index] || '')} +
  2. ` + })} +
+
+
+ `; + } +} + +export {InputMacro}; \ No newline at end of file diff --git a/webgui/js_new/ui/components/ManageIR.js b/webgui/js_new/ui/components/ManageIR.js new file mode 100644 index 0000000..034942a --- /dev/null +++ b/webgui/js_new/ui/components/ManageIR.js @@ -0,0 +1,120 @@ +import { h, Component } from '../../../lib/preact.min.js'; +import htm from '../../../lib/htm.min.js'; +import {ATDevice} from "../../communication/ATDevice.js"; + +const html = htm.bind(h); + +class ManageIR extends Component { + + constructor(props) { + super(); + + this.props = props; + this.state = { + deleteIrName: null, + irCmdName: '', + isRecording: false, + showAdvancedOptions: false + }; + } + + recordIrCmd() { + this.setState({isRecording: true}); + ATDevice.recordIrCommand(this.state.irCmdName).then(success => { + if (success) { + this.props.onchange(this.state.irCmdName); + this.setState({irCmdName: '', deleteIrName: this.state.irCmdName}); + } + this.setState({isRecording: false}); + }) + } + + deleteIrCmd() { + let deleteName = this.state.deleteIrName || this.props.irCmds[0]; + if (!confirm(L.translate('Do you really want to delete IR command "{?}"? // Möchten Sie das IR Kommando "{?}" wirklich löschen?', deleteName))) { + return; + } + ATDevice.sendATCmd(C.AT_CMD_IR_DELETE, deleteName); + let remainingCmds = this.props.irCmds.filter(cmd => cmd !== deleteName); + this.setState({ + deleteIrName: remainingCmds.length > 0 ? remainingCmds[0] : '' + }) + this.props.onchange(); + } + + deleteAllIrCmds() { + if (!confirm(L.translate('Do you really want to delete all IR commands? // Möchten Sie wirklich alle IR Kommandos löschen?'))) { + return; + } + ATDevice.sendATCmd(C.AT_CMD_IR_WIPE); + this.props.onchange(); + } + + render(props) { + let state = this.state; + props.irCmds = props.irCmds || []; + + return html` +

${L.translate('Manage IR Commands // IR Kommandos verwalten')}

+
+ +
+
+
+ +
+
+ +
+
+
+
+ +
+
+ + +
+
+
+ +
+
+
+ +
+
+ +
+
+
+
+
+
+ +
+
+ `; + } +} + +ManageIR.irTimeout = 10; + +export {ManageIR}; \ No newline at end of file diff --git a/webgui/js_new/ui/components/MouseAndKeyboardVisualization.js b/webgui/js_new/ui/components/MouseAndKeyboardVisualization.js new file mode 100644 index 0000000..e5039bd --- /dev/null +++ b/webgui/js_new/ui/components/MouseAndKeyboardVisualization.js @@ -0,0 +1,119 @@ +import { h, Component, render } from '../../../lib/preact.min.js'; +import htm from '../../../lib/htm.min.js'; +import { ATDevice } from '../../communication/ATDevice.js'; + +const html = htm.bind(h); + +class MouseAndKeyboardVisualization extends Component { + + constructor() { + super(); + + MouseAndKeyboardVisualization.instance = this; + this.stateListener = null; + this.state = { + pressedMouseButtons: [], + pressedKeys: [], + pressedKeyCodes: [] + }; + + document.addEventListener('mousedown', this.handleMousePress); + document.addEventListener('mouseup', this.handleMouseRelease); + document.addEventListener('keydown', this.handleKeyPress); + document.addEventListener('keyup', this.handleKeyRelease); + } + + handleKeyPress(event) { + let thiz = MouseAndKeyboardVisualization.instance; + thiz.setState({ + pressedKeys: [...new Set(thiz.state.pressedKeys.concat([event.code]))], // The newly created array gets converted into a Set. Set is a special data structure. Set makes sure that double entries are removed. + pressedKeyCodes: [...new Set(thiz.state.pressedKeyCodes.concat([event.which]))] // which contains the numeric code for a particular key pressed. + }); + } + + handleKeyRelease(event) { + let thiz = MouseAndKeyboardVisualization.instance; + thiz.setState({ + pressedKeys: thiz.state.pressedKeys.filter(e => e !== event.code), + pressedKeyCodes: thiz.state.pressedKeyCodes.filter(e => e !== event.which) + }); + } + + handleMousePress(event) { + let thiz = MouseAndKeyboardVisualization.instance; + thiz.setState({ + pressedMouseButtons: thiz.state.pressedMouseButtons.concat([event.which]) + }); + } + + handleMouseRelease(event) { + let thiz = MouseAndKeyboardVisualization.instance; + if (event.which !== 1) { + event.preventDefault(); + } + thiz.setState({ + pressedMouseButtons: thiz.state.pressedMouseButtons.filter(e => e !== event.which) + }); + } + + componentWillUnmount() { + if (MouseAndKeyboardVisualization.instance === this) { + MouseAndKeyboardVisualization.instance = null; + } + document.removeEventListener('mousedown', this.handleMousePress); + document.removeEventListener('mouseup', this.handleMouseRelease); + document.removeEventListener('keydown', this.handleKeyPress); + document.removeEventListener('keyup', this.handleKeyRelease); + } + + updateState(options) { + this.setState(options); + } + + render() { + let state = this.state; + + return html` +
+
+ ${L.translate('Mouse buttons: // Maustasten:')} + ${L.translate("Left // Links")} + ${L.translate("Middle // Mitte")} + ${L.translate("Right // Rechts")} +
+ +
+ ${L.translate('Keyboard keys: // Tasten auf Tastatur:')} + ${L.translate("(none) // (keine)")} + ${state.pressedKeys.map((keyCode, index) => { + let atKeycode = C.KEYCODE_MAPPING[state.pressedKeyCodes[index]]; + return html` + ${keyCode} + ${atKeycode} + ` + })} +
+
+ `; + } +} + +export { MouseAndKeyboardVisualization }; \ No newline at end of file diff --git a/webgui/js_new/ui/components/PositionVisualization.js b/webgui/js_new/ui/components/PositionVisualization.js new file mode 100644 index 0000000..7946849 --- /dev/null +++ b/webgui/js_new/ui/components/PositionVisualization.js @@ -0,0 +1,180 @@ +import { h, Component, createRef } from '../../../lib/preact.min.js'; +import htm from '../../../lib/htm.min.js'; +import {styleUtil} from '../../util/styleUtil.js'; +import {ATDevice} from "../../communication/ATDevice.js"; +import {FaIcon} from "../components/FaIcon.js"; +import {localStorageService} from "../../localStorageService.js"; + +const html = htm.bind(h); + +const KEY_POS_VIS_MAX_POS_ZOOM = 'KEY_POS_VIS_MAX_POS_ZOOM'; +class PositionVisualization extends Component { + + canvasRef = createRef(); + posVisRef = createRef(); + + constructor(props) { + super(); + + /* + possible props (default values): + showAnalogBars (false), showAnalogValues (false), showOrientation (false), showDeadzone (false), showMaxPos (false), circleRadius (20), maxPosManual (undefined), showZoom (false) + */ + + this.props = props; + PositionVisualization.instance = this; + this.stateListener = null; + this.state = { + liveData: {}, + pX: 50, + pY: 50, + pDzX: 0, + pDzY: 0, + maxPos: 50, + maxPosZoom: localStorageService.hasKey(KEY_POS_VIS_MAX_POS_ZOOM) ? localStorageService.get(KEY_POS_VIS_MAX_POS_ZOOM) : 50 + }; + } + + getValue(value, defaultValue) { + return value !== undefined ? value : defaultValue; + } + + componentWillUnmount() { + this.stateListener = null; + if (PositionVisualization.instance === this) { + PositionVisualization.instance = null; + } + } + + updateState(options) { + this.setState(options); + } + + setStateListener(fn) { + this.stateListener = fn; + } + + getMaxPosManual() { + if (this.props.showZoom) { + return this.state.maxPosZoom; + } + return this.props.maxPosManual; + } + + updateData(data) { + let x = data[C.LIVE_MOV_X]; + let y = data[C.LIVE_MOV_Y]; + let maxX = data[C.LIVE_MOV_X_MAX]; + let maxY = data[C.LIVE_MOV_Y_MAX]; + let minX = data[C.LIVE_MOV_X_MIN]; + let minY = data[C.LIVE_MOV_Y_MIN]; + let deadX = ATDevice.getConfig(C.AT_CMD_DEADZONE_X); + let deadY = ATDevice.getConfig(C.AT_CMD_DEADZONE_Y); + let pDzX = (L.getPercentage(ATDevice.getConfig(C.AT_CMD_DEADZONE_X), 0, this.state.maxPos)); + let pDzY = (L.getPercentage(ATDevice.getConfig(C.AT_CMD_DEADZONE_Y), 0, this.state.maxPos)); + this.state.maxPos = this.getMaxPosManual() !== undefined ? this.getMaxPosManual() : Math.max(maxX, maxY, Math.abs(minX), Math.abs(minY), Math.round(deadX * 1.1), Math.round(deadY * 1.1), this.state.maxPos); + let percentageX = L.limitValue(L.getPercentage(x, -this.state.maxPos, this.state.maxPos), 0, 100); + let percentageY = L.limitValue(L.getPercentage(y, -this.state.maxPos, this.state.maxPos), 0, 100); + let eX = percentageX - 50; + let eY = percentageY - 50; + let inDeadzone = (Math.pow(eX, 2) / Math.pow(pDzX/2, 2) + Math.pow(eY, 2) / Math.pow(pDzY/2, 2)) < 1; + + this.setState({ + liveData: data, + pX: percentageX, + pY: percentageY, + pDzX: pDzX, + pDzY: pDzY, + inDeadzone: inDeadzone + }); + } + + getPercentLength(constant) { + return Math.min(50, (this.state.liveData[constant] / 1024 * 100) / 2); + } + + setMaxPosZoom(value) { + this.setState({ + maxPosZoom: value + }); + localStorageService.save(KEY_POS_VIS_MAX_POS_ZOOM, value); + } + + componentDidUpdate() { + let posVisSize = this.posVisRef.current.getBoundingClientRect().width; + let canvas = this.canvasRef.current; + canvas.width = posVisSize; + canvas.height = posVisSize; + let ctx = canvas.getContext('2d'); + ctx.beginPath(); + let rectW = ctx.canvas.width * this.state.pDzX / 100; + let rectH = ctx.canvas.height * this.state.pDzY / 100; + if (C.DEVICE_IS_FM && ATDevice.getConfig(C.AT_CMD_STICK_MODE) === C.STICK_MODE_ALT.value) { + ctx.rect(ctx.canvas.width / 2 - rectW / 2, ctx.canvas.height / 2 - rectH / 2, rectW, rectH); + } else { + ctx.ellipse(ctx.canvas.width / 2, ctx.canvas.height / 2, rectW / 2, rectH / 2, 0, 0, 2 * Math.PI); + } + ctx.fillStyle = this.state.inDeadzone ? '#9be7ff' : '#cceff9'; + ctx.fill(); + } + + render(props) { + if (this.stateListener) { + this.stateListener(this.state); + } + this.props = props; + let state = this.state; + let data = this.state.liveData; + return html` + `; + } +} + +export {PositionVisualization}; \ No newline at end of file diff --git a/webgui/js/ui/components/RadioFieldset.js b/webgui/js_new/ui/components/RadioFieldset.js similarity index 99% rename from webgui/js/ui/components/RadioFieldset.js rename to webgui/js_new/ui/components/RadioFieldset.js index 902d5d8..b70c362 100644 --- a/webgui/js/ui/components/RadioFieldset.js +++ b/webgui/js_new/ui/components/RadioFieldset.js @@ -9,6 +9,7 @@ class RadioFieldset extends Component { props.elements = props.elements || []; // objects with value and label property props.value = props.value !== undefined ? props.value : null; props.name = props.name || props.elements[0].value + props.elements[1].value; + return html`
${L.translate(props.legend)} diff --git a/webgui/js/ui/components/Slider.js b/webgui/js_new/ui/components/Slider.js similarity index 88% rename from webgui/js/ui/components/Slider.js rename to webgui/js_new/ui/components/Slider.js index 04ca95c..c565512 100644 --- a/webgui/js/ui/components/Slider.js +++ b/webgui/js_new/ui/components/Slider.js @@ -6,19 +6,20 @@ class Slider extends Component { render(props) { props.label = props.label || ''; props.lang = props.lang || ''; - props.oninput = props.oninput || (() => {}); + props.oninput = props.oninput || (() => { }); props.value = props.value !== undefined ? props.value : null; props.min = props.min !== undefined ? props.min : 0; props.max = props.max !== undefined ? props.max : 255; props.step = props.step !== undefined ? props.step : ''; props.updateConstants = props.updateConstants || []; - props.toggleFn = props.toggleFn || (() => {}); + props.toggleFn = props.toggleFn || (() => { }); props.toggleFnLabel = props.toggleFnLabel || ''; props.viewFactor = props.viewFactor || 1; let id = props.updateConstants[0]; + return html`
@@ -34,4 +35,4 @@ Slider.style = html`` -export {Slider}; \ No newline at end of file +export { Slider }; \ No newline at end of file diff --git a/webgui/js/ui/components/SlotTestModeDialog.js b/webgui/js_new/ui/components/SlotTestModeDialog.js similarity index 100% rename from webgui/js/ui/components/SlotTestModeDialog.js rename to webgui/js_new/ui/components/SlotTestModeDialog.js diff --git a/webgui/js_new/ui/modals/ActionEditModal.js b/webgui/js_new/ui/modals/ActionEditModal.js new file mode 100644 index 0000000..199b40d --- /dev/null +++ b/webgui/js_new/ui/modals/ActionEditModal.js @@ -0,0 +1,278 @@ +import { h, Component, render } from '../../../lib/preact.min.js'; +import htm from '../../../lib/htm.min.js'; +import {InputKeyboard} from "../components/InputKeyboard.js"; +import {ManageIR} from "../components/ManageIR.js"; +import {RadioFieldset} from "../components/RadioFieldset.js"; +import {ATDevice} from "../../communication/ATDevice.js"; +import {InputMacro} from "../components/InputMacro.js"; +import {ActionButton} from "../components/ActionButton.js"; +import {FaIcon} from "../components/FaIcon.js"; + +const html = htm.bind(h); +class ActionEditModal extends Component { + + constructor(props) { + super(); + + ActionEditModal.instance = this; + ActionEditModal.ALL_CATEGORIES = 'ALL_CATEGORIES'; + + this.props = props; + let currentAtCmdString = ATDevice.getButtonActionATCmd(props.buttonMode.index, props.slot) || C.AT_CMD_NO_CMD; + let currentAtCmdObject = C.AT_CMDS_ACTIONS.filter(atCmd => currentAtCmdString === atCmd.cmd)[0] || C.AT_CMDS_ACTIONS[0]; + currentAtCmdString = currentAtCmdObject.cmd; + let showCategory = currentAtCmdString && currentAtCmdString !== C.AT_CMD_NO_CMD ? C.AT_CMDS_ACTIONS.filter(atCmd => atCmd.cmd === currentAtCmdString)[0].category: ActionEditModal.ALL_CATEGORIES; + let possibleAtCmds = C.AT_CMDS_ACTIONS.filter(atCmd => showCategory === ActionEditModal.ALL_CATEGORIES || atCmd.category === showCategory); + this.state = { + showCategory: showCategory, + atCmd: currentAtCmdObject, + atCmdSuffix: ATDevice.getButtonActionATCmdSuffix(props.buttonMode.index, props.slot), + atCmdSelectedByUser: false, + possibleAtCmds: possibleAtCmds, + selectOptions: [], + shouldChangeMode: false, + hasChanges: false + } + this.state.originalState = JSON.parse(JSON.stringify(this.state)); + this.updateSelect(currentAtCmdObject); + } + + selectActionCategory(category, dontChangeAtCmd) { + let possible = C.AT_CMDS_ACTIONS.filter(atCmd => category === ActionEditModal.ALL_CATEGORIES || atCmd.category === category); + this.setState({ + showCategory: category, + possibleAtCmds: possible + }); + if (!dontChangeAtCmd) { + let atCmd = possible.includes(this.state.atCmd) ? this.state.atCmd : possible[0]; + atCmd = !this.state.atCmdSelectedByUser && possible.map(atCmd => atCmd.cmd).includes(this.state.originalState.atCmd.cmd) ? this.state.originalState.atCmd : atCmd; + this.setAtCmd(atCmd.cmd); + } + } + + setAtCmd(atCmdString) { + let atCmdObject = C.AT_CMDS_ACTIONS.filter(atCmd => atCmdString === atCmd.cmd)[0]; + let isAndWasKeyboard = atCmdObject.input === C.INPUTFIELD_TYPE_KEYBOARD && this.state.atCmd.input === C.INPUTFIELD_TYPE_KEYBOARD; + let atCmdSuffix = isAndWasKeyboard ? this.state.atCmdSuffix : ''; + if (!this.state.atCmdSelectedByUser && atCmdObject.cmd === this.state.originalState.atCmd.cmd) { + atCmdSuffix = this.state.originalState.atCmdSuffix; + } + this.setState({ + atCmdSuffix: atCmdSuffix, + atCmd: atCmdObject, + selectOptions: [], + hasChanges: true + }); + this.updateSelect(atCmdObject); + } + + updateIrSelect(newName) { + if (newName) { + this.setAtCmdSuffix(newName); + } + this.updateSelect(); + } + + updateSelect(atCmdObject) { + atCmdObject = atCmdObject || this.state.atCmd; + if (atCmdObject.optionsFn) { + Promise.resolve(atCmdObject.optionsFn()).then(result => { + result = result || []; + this.setState({ + selectOptions: result, + atCmdSuffix: result.includes(this.state.atCmdSuffix) ? this.state.atCmdSuffix : result[0] + }); + }) + } + } + + setAtCmdSuffix(suffix) { + this.setState({ + atCmdSelectedByUser: true, + atCmdSuffix: suffix, + hasChanges: true + }) + } + + async save(forAllSlots) { + await ATDevice.setSlot(this.props.slot); + if (this.state.shouldChangeMode && ATDevice.setStickMode) { + await ATDevice.setStickMode(C.DEVICE_IS_FLIPPAD ? C.FLIPPAD_MODE_PAD_ALTERNATIVE.value : C.STICK_MODE_ALT.value) + } + if (this.state.hasChanges) { + let atCmd = this.state.atCmdSuffix ? this.state.atCmd.cmd + ' ' + this.state.atCmdSuffix : this.state.atCmd.cmd; + ATDevice.setButtonAction(this.props.buttonMode.index, atCmd); + } + if (forAllSlots) { + let constants = [C.AT_CMD_BTN_MODE + " " + this.props.buttonMode.index]; + if (this.props.buttonMode.category === C.BTN_CAT_STICK) { + constants.push(C.AT_CMD_STICK_MODE) + } + await ATDevice.copyConfigToAllSlots(constants, this.props.slot, true); + } + this.props.closeHandler(); + } + + saveButtonsDisabled() { + if (this.state.atCmd.minValue !== undefined && this.state.atCmdSuffix < this.state.atCmd.minValue) { + return true; + } + if (this.state.atCmd.maxValue !== undefined && this.state.atCmdSuffix > this.state.atCmd.maxValue) { + return true; + } + return this.state.atCmd.input && !this.state.atCmdSuffix; + } + + clearCommand() { + this.setAtCmd(C.AT_CMD_NO_CMD); + this.selectActionCategory(ActionEditModal.ALL_CATEGORIES, true); + let originalState = JSON.parse(JSON.stringify(this.state.originalState)); + originalState.atCmd = C.AT_CMDS_ACTIONS.filter(atCmd => atCmd.cmd === C.AT_CMD_NO_CMD)[0]; + this.setState({ + atCmdSelectedByUser: true, + originalState: originalState, + atCmdSuffix: '' + }); + } + + render(props) { + let state = this.state; + let btnMode = props.buttonMode; + let categoryElements = C.AT_CMD_CATEGORIES.map(cat => {return {value: cat.constant, label: cat.label}}); + categoryElements = [{value: ActionEditModal.ALL_CATEGORIES, label: 'All categories // Alle Kategorien'}].concat(categoryElements); + let flipmouseAltMode = C.DEVICE_IS_FM && ATDevice.getConfig(C.AT_CMD_STICK_MODE, props.slot) === C.STICK_MODE_ALT.value; + let flipadAltMode = C.DEVICE_IS_FLIPPAD && [C.FLIPPAD_MODE_PAD_ALTERNATIVE.value, C.FLIPPAD_MODE_STICK_ALTERNATIVE.value].includes(ATDevice.getConfig(C.AT_CMD_STICK_MODE, props.slot)); + let showActionSelection = C.DEVICE_IS_FABI || flipmouseAltMode || flipadAltMode || btnMode.category !== C.BTN_CAT_STICK || state.shouldChangeMode; + let modeLabel = C.STICK_MODES.filter(mode => mode.value === ATDevice.getConfig(C.AT_CMD_STICK_MODE, props.slot))[0].label; + + return html` + + ${ActionEditModal.style}` + } +} + +ActionEditModal.style = html`` + +export {ActionEditModal}; \ No newline at end of file diff --git a/webgui/js_new/ui/modals/FirmwareUpdateModal.js b/webgui/js_new/ui/modals/FirmwareUpdateModal.js new file mode 100644 index 0000000..54b980e --- /dev/null +++ b/webgui/js_new/ui/modals/FirmwareUpdateModal.js @@ -0,0 +1,121 @@ +import { h, Component } from '../../../lib/preact.min.js'; +import htm from '../../../lib/htm.min.js'; +import { FaIcon } from "../components/FaIcon.js"; +import { ATDevice } from "../../communication/ATDevice.js"; + +const html = htm.bind(h); +class FirmwareUpdateModal extends Component { + + constructor(props) { + super(); + + this.props = props; + this.state = { + enteredDownloadMode: false + } + + } + + enterDownloadMode() { + if (ATDevice.enterFwDownloadMode) { + ATDevice.enterFwDownloadMode(); + this.setState({ + enteredDownloadMode: true + }) + } + } + + close() { + if (this.state.enteredDownloadMode) { + return; + } + this.props.close(); + } + + render(props) { + if (!props || !props.fwInfo) { + return; + } + + let index = props.fwInfo.originalDownloadUrl.lastIndexOf("/"); + let fwFileName = props.fwInfo.originalDownloadUrl.substring(index + 1); + + return html` + + ${FirmwareUpdateModal.style}` + + } + + +} + +FirmwareUpdateModal.style = html`` + +export { FirmwareUpdateModal }; \ No newline at end of file diff --git a/webgui/js/ui/modals/TextModal.js b/webgui/js_new/ui/modals/TextModal.js similarity index 100% rename from webgui/js/ui/modals/TextModal.js rename to webgui/js_new/ui/modals/TextModal.js diff --git a/webgui/js_new/ui/views/MainView.js b/webgui/js_new/ui/views/MainView.js new file mode 100644 index 0000000..0a7a22d --- /dev/null +++ b/webgui/js_new/ui/views/MainView.js @@ -0,0 +1,367 @@ +import { h, Component, render } from '../../../lib/preact.min.js'; +import htm from '../../../lib/htm.min.js'; +import { ATDevice } from "../../communication/ATDevice.js"; +import { localStorageService } from "../../localStorageService.js"; +import { FaIcon } from "../components/FaIcon.js"; +import { firmwareUtil } from "../../util/firmwareUtil.js"; +import { helpUtil } from "../../util/helpUtil.js"; +import { SlotTestModeDialog } from "../components/SlotTestModeDialog.js"; +import { getBtnModesActionList } from '../../constantsGeneric.js'; + +const html = htm.bind(h); + +const SCREENS = { + CONNECTION: 'CONNECTION', + MAIN: 'MAIN', + FIRMWARE_UPDATE: 'FIRMWARE_UPDATE', + FIRMWARE_CONTINUE: 'FIRMWARE_CONTINUE' +} + +class MainView extends Component { + + constructor() { + super(); + log.info('last update: 19.07.2025'); + MainView.instance = this; + MainView.lastViewHash = ''; + this.state = { + currentView: {}, + showScreen: SCREENS.CONNECTION, + currentSlot: null, + slots: [], + connected: true, + menuOpen: false, + errorCode: null, + showSuccessMsg: window.location.href.indexOf(C.SUCCESS_FIRMWAREUPDATE) > -1, + updateProgress: null + } + C.BTN_MODES_ACTIONLIST = getBtnModesActionList(); // TBD: better ways to get a flexible action list? + + L('html')[0].lang = L.getLang(); + if (C.GUI_IS_MOCKED_VERSION || C.GUI_IS_ON_DEVICE) { + this.initATDevice(); + } else if (localStorageService.getFirmwareDownloadUrl()) { + this.setState({ + showScreen: SCREENS.FIRMWARE_CONTINUE, + }); + } else { + this.setState({ + showScreen: SCREENS.CONNECTION, + }); + } + + window.addEventListener(C.EVENT_REFRESH_MAIN, () => { + this.setState({}); + }); + } + + toConnectionScreen() { + this.setState({ + showScreen: SCREENS.CONNECTION, + errorCode: C.ERROR_CONNECTION_LOST, + showSuccessMsg: false, + currentView: {} + }); + } + + testMode() { + C.GUI_IS_MOCKED_VERSION = true; + this.initATDevice(); + } + + initATDevice() { + let thiz = this; + this.setState({ + errorCode: null, + showSuccessMsg: false + }); + ATDevice.init().then(function () { + C.BTN_MODES_ACTIONLIST = getBtnModesActionList(); + thiz.toView(); + thiz.setState({ + showScreen: SCREENS.MAIN, + currentSlot: ATDevice.getCurrentSlot(), + slots: ATDevice.getSlots() + }); + ATDevice.setSlotChangeHandler(() => { + thiz.setState({ + currentSlot: ATDevice.getCurrentSlot(), + slots: ATDevice.getSlots() + }); + if (thiz.state.currentView && thiz.state.currentView.object && thiz.state.currentView.object.slotChangeHandler) { + thiz.state.currentView.object.slotChangeHandler(); + } + }); + ATDevice.addConnectionTestHandler((isConnected) => { + if (isConnected !== thiz.state.connected) { + thiz.setState({ + connected: isConnected + }); + } + }); + }).catch(error => { + if (error === C.ERROR_LEGACY_FIRMWARE) { + let url = window.location.origin + '/legacy/'; + window.location.replace(url); + } else { + this.setState({ + errorCode: error + }); + } + }); + } + + toView(viewHash) { + MainView.lastViewHash = this.state.currentView ? this.state.currentView.hash : ''; + let viewHashes = C.VIEWS.map(el => el.hash); + viewHash = viewHash || window.location.hash; + viewHash = viewHashes.includes(viewHash) ? viewHash : C.VIEW_START_HASH || viewHashes[0]; + let view = C.VIEWS.filter(el => el.hash === viewHash)[0]; + + // hide views which are not supported by the current device + if (view.visibleFn !== undefined && !view.visibleFn(ATDevice)) { + return ''; + } + + helpUtil.setHash(view.helpHash); + + this.setState({ + currentView: view, + menuOpen: false + }); + + if (view.object.valueHandler && ATDevice.startLiveValueListener) { + ATDevice.startLiveValueListener(view.object.valueHandler); + } else if (ATDevice.stopLiveValueListener) { + ATDevice.stopLiveValueListener(); + } + + window.location.hash = viewHash; + document.body.scrollTop = 0; + document.documentElement.scrollTop = 0; + } + + toLastView() { + if (MainView.lastViewHash) { + this.toView(MainView.lastViewHash); + } + } + + continueFirmwareUpdate() { + let thiz = this; + ATDevice.updateFirmware(localStorageService.getFirmwareDownloadUrl(), (progress) => { + thiz.setState({ updateProgress: progress || 1 }); + }, true); + } + + startFirmwareUpdate() { + let thiz = this; + firmwareUtil.updateDeviceFirmware(progress => { + thiz.setState({ updateProgress: progress || 1 }); + }); + } + + async disconnect() { + window.location.reload(); + } + + render() { + let state = this.state; + + return html` +
+
+

${L.translate('{?} Configuration // {?} Konfiguration', C.CURRENT_DEVICE)}

+
+
+ ${html`<${FaIcon} icon="fas exclamation-triangle"/>`} + ${L.translate('The firmware of your device is outdated. Please update it to be able to use this web-based configuration. // Die Firmware des Geräts ist veraltet. Bitte aktualisieren, um die web-basierte Konfiguration verwenden zu können.')} +
+
+
+
+ +
+
+ +
+
+
+
+

${L.translate('{?} Configuration // {?} Konfiguration', C.CURRENT_DEVICE)}

+
+
+ ${html`<${FaIcon} icon="fas exclamation-triangle"/>`} + ${L.translate('Firmware update was not completed yet. Continue in order to be able to use your device. // Das Firmware-Update wurde noch nicht abgeschlossen. Setzen Sie das Update fort um das Gerät weiter verwenden zu können.')} +
+
+
+
+ +
+
+ +
+
+
+
+

+ ${L.translate('{?} Configuration // {?} Konfiguration', C.CURRENT_DEVICE)} +

+ + +
+
+
+ ${html`<${FirmwareUpdateModal} close="${() => this.setState({ showFirmwareModal: false })}" fwInfo="${state.mainVersionFWInfo}" currentFwVersion="${this.state.mainVersion}"/>`} +
+ + + +

${L.translate('Reset to default configuration // Rücksetzen auf Defaulteinstellungen')}

+
+
+ +
+
+

${L.translate('Keyboard shortcuts // Tastenkombinationen')}

+
+
+ ${L.translate('The following keyboard shortcuts can be used on this page: // Die folgenden Tastenkombinationen können auf dieser Seite verwendet werden:')} +
    +
  • ${L.translate('Ctrl + C // Strg + C')}: ${L.translate('Calibrate middle position // Mittelposition kalibrieren')}
  • +
  • ${L.translate('F1')}: ${L.translate('Open manual section for the currently active tab // Benutzerhandbuch-Abschnitt zum aktuell geöffneten Tab öffnen')}
  • +
  • ${L.translate('Ctrl + [1-{?}] // Strg + [1-{?}]', C.VIEWS.length)}: ${L.translate('Jump to tab with the chosen number // Springe zu Tab mit der gewählten Nummer')}
  • +
  • ${L.translate('Ctrl + Space // Strg + Leertaste')}: ${L.translate('Jump to last tab // Springe zu vorherigem Tab')}
  • +
  • ${L.translate('Ctrl + B // Strg + B')}: ${L.translate('Show / hide analog values in visualization // Zeigen / Verstecken der analogen Werte in der Visualisierung')}
  • +
+
+
+ `; + } +} + +export { TabGeneral }; \ No newline at end of file diff --git a/webgui/js_new/ui/views/TabSipPuff.js b/webgui/js_new/ui/views/TabSipPuff.js new file mode 100644 index 0000000..b07bb29 --- /dev/null +++ b/webgui/js_new/ui/views/TabSipPuff.js @@ -0,0 +1,221 @@ +import { h, Component, render } from '../../../lib/preact.min.js'; +import htm from '../../../lib/htm.min.js'; +import { ATDevice } from "../../communication/ATDevice.js"; +import { ActionButton } from "../components/ActionButton.js"; + +const html = htm.bind(h); +class TabSipPuff extends Component { + + constructor() { + super(); + + this.lastChangedA11yPressure = 0; + this.lastSliderChangedTime = 0; + + TabSipPuff.instance = this; + this.state = { + minRange: 0, + maxRange: 1023, + percent: 0, + percentMin: 0, + percentMax: 0, + value: 0, + valueA11y: 0, + valueMin: 0, + valueMax: 0, + sipThreshold: 0, + puffThreshold: 0, + strongSipThreshold: 0, + strongPuffThreshold: 0 + } + + ATDevice.resetMinMaxLiveValues(); + } + + updateData(data) { + let newState = {}; + + newState.minValue = data[C.LIVE_PRESSURE_MIN]; + newState.maxValue = data[C.LIVE_PRESSURE_MAX]; + newState.value = data[C.LIVE_PRESSURE]; + newState.SIP_THRESHOLD = ATDevice.getConfig(C.AT_CMD_SIP_THRESHOLD); + newState.PUFF_THRESHOLD = ATDevice.getConfig(C.AT_CMD_PUFF_THRESHOLD); + newState.SIP_STRONG_THRESHOLD = ATDevice.getConfig(C.AT_CMD_SIP_STRONG_THRESHOLD); + newState.PUFF_STRONG_THRESHOLD = ATDevice.getConfig(C.AT_CMD_PUFF_STRONG_THRESHOLD); + + let border = (this.state.maxRange - this.state.minRange) * 0.1; // 10% space that is left and right of the min/max values on the sliders + if (new Date().getTime() - this.lastSliderChangedTime > 500) { + this.state.minRange = Math.max(Math.min(newState.minValue - border, ATDevice.getConfig(C.AT_CMD_SIP_THRESHOLD) - border, ATDevice.getConfig(C.AT_CMD_SIP_STRONG_THRESHOLD) - border), 0); + this.state.maxRange = Math.min(Math.max(newState.maxValue + border, ATDevice.getConfig(C.AT_CMD_PUFF_THRESHOLD) + border, ATDevice.getConfig(C.AT_CMD_PUFF_STRONG_THRESHOLD) + border), 1023); + } + + newState.percent = L.getPercentage(newState.value, this.state.minRange, this.state.maxRange); + newState.percentMin = L.getPercentage(newState.minValue, this.state.minRange, this.state.maxRange); + newState.percentMax = L.getPercentage(newState.maxValue, this.state.minRange, this.state.maxRange); + + if (new Date().getTime() - this.lastChangedA11yPressure > 1000) { + this.lastChangedA11yPressure = new Date().getTime(); + newState.valueA11y = newState.value; + } + + this.setState(newState); + } + + sliderChanged(event, constant) { + let newValue = parseInt(event.target.value); + let oldValue = ATDevice.getConfig(constant); + let liveValue = ATDevice.getLiveData(C.LIVE_PRESSURE); + + let validPuff = (newValue > liveValue || newValue > oldValue); + let validSip = (newValue < liveValue || newValue < oldValue); + + //only move slider if sip thresholds are below and puff thresholds are above the current live value and if strong-values are below/above normal values + if ((constant === C.AT_CMD_SIP_THRESHOLD && validSip && (newValue > ATDevice.getConfig(C.AT_CMD_SIP_STRONG_THRESHOLD) || C.DEVICE_IS_FABI)) || + (constant === C.AT_CMD_SIP_STRONG_THRESHOLD && validSip && newValue < ATDevice.getConfig(C.AT_CMD_SIP_THRESHOLD)) || + (constant === C.AT_CMD_PUFF_THRESHOLD && validPuff && (newValue < ATDevice.getConfig(C.AT_CMD_PUFF_STRONG_THRESHOLD) || C.DEVICE_IS_FABI)) || + (constant === C.AT_CMD_PUFF_STRONG_THRESHOLD && validPuff && newValue > ATDevice.getConfig(C.AT_CMD_PUFF_THRESHOLD))) { + this.lastSliderChangedTime = new Date().getTime(); + let newState = {}; + newState[constant] = newValue; + this.setState(newState); + ATDevice.setConfig(constant, newValue); + } else { + this.setState({}); + } + } + valueChanged(value, constants) { + let state = {}; + constants.forEach(constant => { + state[constant] = value; + ATDevice.setConfig(constant, value); + }); + this.setState(state); + } + + resetSlidersPuffSip() { + const configureMappings = [ // These are array of objects. + { constant: C.AT_CMD_SIP_THRESHOLD, defaultValue: 400 }, + { constant: C.AT_CMD_SIP_STRONG_THRESHOLD, defaultValue: 10 }, + { constant: C.AT_CMD_PUFF_THRESHOLD, defaultValue: 600 }, + { constant: C.AT_CMD_PUFF_STRONG_THRESHOLD, defaultValue: 800 } + ]; + + configureMappings.forEach(mapping => { // Iterates through each constant in the mappings and passes its corresponding value to the valueChanged function. + const { constant, defaultValue } = mapping; // defaultValue and constant get extracted out of the object mapping. A different way of doing this would be like this: const constant = mapping.constant; It is the same procedure with defaultValue. + this.valueChanged(defaultValue, [constant]); + }); + + + } + + render() { + let state = this.state; + + return html` +

${L.translate('Sip/Puff configuration (slot "{?}") // Saug- / Pustesteuerung (Slot "{?}")', ATDevice.getCurrentSlot())}

+ ${L.translate('Current pressure value // Aktueller WertDruck')} + ${state.valueA11y} + +
+
+ +
+
+
+
+
+
+ +
+ +
+ + +
+ + +
+ + +
+ +
+ + + +
+
+ +
+ + +
+ + +
+ + +
+ +
+ + +
+ ${html` + <${ActionButton} + onclick="${() => ATDevice.copyConfigToAllSlots([C.AT_CMD_SIP_THRESHOLD, C.AT_CMD_SIP_STRONG_THRESHOLD, C.AT_CMD_PUFF_THRESHOLD, C.AT_CMD_PUFF_STRONG_THRESHOLD])}" + label="Copy config to all slots // Konfiguration auf alle Slots anwenden" + progressLabel="Applying to all slots... // Anwenden auf alle Slots..." faIcon="far clone" /> + + +
+
+ <${ActionButton} resetSlidersPuffSip="${() => this.resetSlidersPuffSip()}" + label="Reset All Thresholds // Alle Schwellenwerte zurücksetzen" faIcon="fas undo" progressLabel="Resetting Thresholds ... // Schwellenwerte werden zurückgesetzt..." /> +
+
+ + `} +
`; + + + + } +} + +TabSipPuff.valueHandler = function (data) { + TabSipPuff.instance.updateData(data); +}; + +export { TabSipPuff }; \ No newline at end of file diff --git a/webgui/js_new/ui/views/TabSlots.js b/webgui/js_new/ui/views/TabSlots.js new file mode 100644 index 0000000..93a98ee --- /dev/null +++ b/webgui/js_new/ui/views/TabSlots.js @@ -0,0 +1,648 @@ +import { h, Component, render } from '../../../lib/preact.min.js'; +import htm from '../../../lib/htm.min.js' +import { ATDevice } from "../../communication/ATDevice.js"; +import { FaIcon } from "../components/FaIcon.js"; +import { ActionButton } from "../components/ActionButton.js"; +import { TextModal } from "../modals/TextModal.js"; + +const html = htm.bind(h); +class TabSlots extends Component { + + constructor() { + super(); + + TabSlots.instance = this; + this.state = { + newSlotName: '', + slots: ATDevice.getSlots(), + uploadedSlots: [], + uploadProgress: 0, + selectedUploadSlots: [], + demoSettingSlots: [], + demoSettingSelected: {}, + demoSettingSelectedText: {}, + demoSettings: [], + showDemoDescription: false, + selectedFileValid: undefined, + showColorInput: true, + showWaitingPopup: false, + voiceMessage: '', + voiceLanguage: 'en-US', + voiceGender: 'male' + } + + let url; + url = `https://api.github.com/repos/asterics/${C.CURRENT_DEVICE}/contents/Settings`; // URL to get the settings from the GitHub repository. TBD: update for unified GUI version + + L.HTTPRequest(url, 'GET', 'json').then(result => { + this.setState({ + demoSettings: result + }) + }) + } + + createSlot() { + let thiz = this; + ATDevice.createSlot(this.state.newSlotName).then(function () { + thiz.setState({ + newSlotName: '', + slots: ATDevice.getSlots() + }); + }); + } + + deleteSlot(slot) { + let thiz = this; + let confirmMessage = L.translate('Do you really want to delete the slot "{?}"? // Möchten Sie den Slot "{?}" wirklich löschen?', slot); + if (!window.confirm(confirmMessage)) { + return; + } + ATDevice.deleteSlot(slot).then(function () { + thiz.setState({ + slots: ATDevice.getSlots() + }) + }); + }; + + fileUploadChanged(target) { + let thiz = this; + if (!target.files[0]) { + return thiz.setState({ + uploadedSlots: [], + selectedUploadSlots: [], + selectedFile: '' + }) + } + let reader = new FileReader(); + reader.readAsText(target.files[0]); + reader.onloadend = function (e) { + let parsedSlots = ATDevice.parseConfig(e.target.result); + let validConfig = parsedSlots.length > 0; + validConfig = validConfig && !!parsedSlots[0].config[C.AT_CMD_DEADZONE_X]; + parsedSlots.forEach(slot => { //prevent duplicated names + slot.name = slot.name.substring(0, C.MAX_LENGTH_SLOTNAME); + let originalSlotname = slot.name; + let counter = 1; + let otherSlotNames = parsedSlots.filter(s => s !== slot).map(slot => slot.name); + slot.dedupedName = slot.name; + while (thiz.state.slots.includes(slot.dedupedName) || otherSlotNames.includes(slot.dedupedName)) { + let postfix = ` (${counter})`; + let originalTrimmed = originalSlotname.substring(0, C.MAX_LENGTH_SLOTNAME - postfix.length); + slot.dedupedName = originalTrimmed + postfix; + counter++; + } + }) + thiz.setState({ + uploadedSlots: parsedSlots, + selectedFile: target.files[0].name, + selectedFileValid: validConfig + }) + }; + } + + uploadSlots() { + let thiz = this; + thiz.setState({ + uploading: true + }) + while (this.state.selectedUploadSlots.length + this.state.slots.length > C.MAX_NUMBER_SLOTS) { + this.state.selectedUploadSlots.pop(); + } + let uploadSlots = this.state.selectedUploadSlots.map(slot => { + slot.name = slot.dedupedName; + return slot; + }); + ATDevice.uploadSlots(uploadSlots, (progress) => { + thiz.setState({ uploadProgress: progress }) + }).then(() => { + thiz.resetUploadFile(); + }); + }; + + async uploadAllSlots() { + let confirmMsg = L.translate('Do you want to upload and replace all slots from file "{?}"? This will delete all existing slots on the device. // Möchten Sie alle Slots aus der Datei "{?}" hochladen und ersetzen? Dadurch werden alle bestehenden Slots gelöscht.', this.state.selectedFile); + if (!confirm(confirmMsg)) { + return; + } + ATDevice.deleteAllSlots(); + await ATDevice.uploadSlots(this.state.uploadedSlots, (progress) => { + this.setState({ uploadProgress: progress }) + }); + this.resetUploadFile(); + } + + resetUploadFile() { + this.setState({ + slots: ATDevice.getSlots(), + selectedUploadSlots: [], + uploadedSlots: [], + uploading: false, + selectedFile: '', + selectedFileValid: undefined, + showAdvancedUpload: false + }); + L('#fileInputSlotUpload').value = null; + } + + downloadSlot(slot) { + let datestr = new Date().toISOString().substr(0, 10); + L.downloadasTextFile(`${C.CURRENT_DEVICE}-slot-${slot}-${datestr}.set`, ATDevice.getSlotConfigText(slot)); + }; + + downloadAllSlots() { + let configstr = ""; + ATDevice.getSlots().forEach(function (item) { + configstr = configstr + ATDevice.getSlotConfigText(item) + "\n"; + }); + let d = new Date(); + let datestr = new Date().toISOString().substr(0, 10); + L.downloadasTextFile(`${C.CURRENT_DEVICE}-config-${datestr}.set`, configstr); + }; + + + // Function to convert PCM data to WAV format (22050Hz, mono, 16-bit) + convertToWav(audioBuffer) { + const numChannels = 1; // Mono + const sampleRate = 22050; + const bitsPerSample = 16; + const format = 1; // PCM + + const numFrames = audioBuffer.length; + const buffer = new ArrayBuffer(44 + numFrames * 2); + const view = new DataView(buffer); + + // WAV Header + const writeString = (offset, str) => { + for (let i = 0; i < str.length; i++) { + view.setUint8(offset + i, str.charCodeAt(i)); + } + }; + + writeString(0, "RIFF"); + view.setUint32(4, 36 + numFrames * 2, true); + writeString(8, "WAVE"); + writeString(12, "fmt "); + view.setUint32(16, 16, true); + view.setUint16(20, format, true); + view.setUint16(22, numChannels, true); + view.setUint32(24, sampleRate, true); + view.setUint32(28, sampleRate * numChannels * (bitsPerSample / 8), true); + view.setUint16(32, numChannels * (bitsPerSample / 8), true); + view.setUint16(34, bitsPerSample, true); + writeString(36, "data"); + view.setUint32(40, numFrames * 2, true); + + // Write PCM data + const pcmData = audioBuffer.getChannelData(0); + let offset = 44; + for (let i = 0; i < numFrames; i++) { + const sample = Math.max(-1, Math.min(1, pcmData[i])); + view.setInt16(offset, sample * 0x7FFF, true); + offset += 2; + } + + return new Uint8Array(buffer); + } + + + async createVoiceMessage() { + const message = this.state.voiceMessage.replaceAll(' ','-'); + const lang = this.state.voiceLanguage; + const gender = this.state.voiceGender; + console.log(`Generate Voice Message: ${message}, Voice: ${lang},${gender}`); + + // TBD: remove proxy when in production! + const proxyUrl = "https://proxy.asterics-foundation.org/proxy.php?csurl="; + const apiUrl = encodeURIComponent(`https://texttospeech.responsivevoice.org/v1/text:synthesize?lang=${lang}&engine=g1&name=&pitch=0.5&rate=0.5&volume=1&key=kvfbSITh&gender=${gender}&text=${message}`); + + // const proxyUrl = "https://cors-anywhere.herokuapp.com/"; + // const apiUrl = `https://texttospeech.responsivevoice.org/v1/text:synthesize?lang=${encodeURIComponent(lang)}&engine=g1&name=&pitch=0.5&rate=0.5&volume=1&key=kvfbSITh&gender=${encodeURIComponent(gender)}&text=${encodeURIComponent(message)}`; + + try { + // Show the waiting popup + this.setState({ showWaitingPopup: true }); + + // Fetch the MP3 file + console.log("Fetching Audio ..."); + const response = await fetch(proxyUrl+ apiUrl); + if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); + const mp3ArrayBuffer = await response.arrayBuffer(); + + // Decode MP3 to PCM using Web Audio API + const audioContext = new (window.AudioContext || window.webkitAudioContext)({ sampleRate: 22050 }); + const audioBuffer = await audioContext.decodeAudioData(mp3ArrayBuffer); + + // Convert PCM buffer to WAV format + const wavBuffer = this.convertToWav(audioBuffer); + + // Send WAV data over Web Serial API + await ATDevice.sendAudio(wavBuffer); + + // Play the WAV buffer + this.playWavBuffer(wavBuffer); + + + } catch (error) { + console.error("Error processing audio:", error); + } finally { + // Hide the waiting popup + this.setState({ showWaitingPopup: false }); + } + } + + playWavBuffer(wavBuffer) { + const audioContext = new (window.AudioContext || window.webkitAudioContext)(); + const wavArrayBuffer = wavBuffer.buffer; // Convert Uint8Array to ArrayBuffer + + // Decode the WAV buffer and play it + audioContext.decodeAudioData(wavArrayBuffer, (decodedData) => { + const source = audioContext.createBufferSource(); + source.buffer = decodedData; + source.connect(audioContext.destination); + source.start(0); + }, (error) => { + console.error("Error decoding WAV buffer:", error); + }); + } + + uploadCheckboxChanged(slot, selected) { + let currentList = this.state.selectedUploadSlots; + if (selected) { + currentList.push(slot); + } else { + currentList = currentList.filter(elem => elem !== slot); + } + this.setState({ + selectedUploadSlots: [...new Set(currentList)] + }); + } + + async colorChanged(slot, event) { + let colorValue = event.target.value.replace('#', '0x'); + await ATDevice.setConfigForSlot(C.AT_CMD_SET_COLOR, colorValue, slot, 500); + this.forceUpdate(); + } + + demoSettingChanged(settingSha) { + let setting = this.state.demoSettings.filter(s => s.sha === settingSha)[0]; // [0], meaning that the first element that fits that description will be chosen. + let settingText = this.state.demoSettings.filter(s => L.equalIgnoreCase(setting.name.replace('.set', ''), s.name.replace('.md', '')))[0]; // equalIgnoreCase = Compares two strings in which the cases (lower case, upper case) are ignored. '' is here, so that only the extact part of the word, that is being searched for is being looked for. + L.HTTPRequest(setting.download_url, 'GET', 'text').then(result => { + let parsedSlots = ATDevice.parseConfig(result); + this.setState({ + demoSettingSlots: parsedSlots, + demoSettingSelected: setting, + demoSettingSelectedText: settingText || {} + }); + }); + } + + async applyDemoSettings() { + let confirmMsg = L.translate('Do you want to apply demo setting "{?}"? This will delete all existing slots on the device. // Möchten Sie die Voreinstellung "{?}" anwenden? Dadurch werden alle bestehenden Slots gelöscht.', this.state.demoSettingSelected.name); + if (!confirm(confirmMsg)) { + return; + } + ATDevice.deleteAllSlots(); + await ATDevice.uploadSlots(this.state.demoSettingSlots, (progress) => { + this.setState({ uploadProgress: progress }) + }); + this.setState({ + slots: ATDevice.getSlots() + }) + } + + async toggleConnectionMode(slot) { + let currentMode = ATDevice.getConfig(C.AT_CMD_DEVICE_MODE, slot); + let newMode = currentMode === C.DEVICE_MODE_USB ? C.DEVICE_MODE_BT : C.DEVICE_MODE_USB; + await ATDevice.setConfigForSlot(C.AT_CMD_DEVICE_MODE, newMode, slot); + this.forceUpdate(); + window.dispatchEvent(new CustomEvent(C.EVENT_REFRESH_MAIN)); + } + + getDeactivatedText() { + return L.translate('Deactivated in Slot-Test-Modus // Deaktiviert im Slot-Test-Modus'); + } + + render() { + let state = this.state; + let slots = state.slots; + let maxSlotsReached = this.state.slots.length === C.MAX_NUMBER_SLOTS; + let isSlotTestMode = ATDevice.isSlotTestMode(); + let audioAvailable = C.CURRENT_DEVICE === 'FABI' ? true : false; // TBD: dynamically read this property from device! + + return html` +

${L.translate('Slot configuration // Slot-Konfiguration')}

+
+ ${this.state.showWaitingPopup ? html` + + ` : ''} + +

${L.translate('Current slots // Aktuelle slots')}

+
+
+
+
+ Name +
+
+ ${L.translate('Color // Farbe')} +
+
+ ${L.translate('Connection // Verbindung')} +
+
+ ${L.translate('Actions // Aktionen')} +
+
+
+
+
+
    + ${slots.map((slot, index) => html` +
  1. + +
    +
    + ${L.translate('Slot: // Slot:')} + ${L.translate('Slot: // Slot:')} + + ${slot} + ${slot} + ${L.translate('(active) // (aktiv)')} + +
    +
    + ${L.translate('Color: // Farbe:')} + +
    +
    + ${L.translate('Connection: // Verbindung:')} +
    + + USB + + BT + +
    +
    +
    + ${L.translate('Actions: // Aktionen:')} +
    + + +
    +
    +
    +
  2. `)} +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ + + +
+ +

${L.translate('Upload slots to device // Slots auf Gerät hochladen')}

+

${L.translate('Upload slots from file // Slots aus Datei hochladen')}

+
+
+ + +
+
+ ${L.translate('Selected file: // Gewählte Datei:')} ${this.state.selectedFile || L.translate('(none) // (keine)')}
+ ${this.state.uploadedSlots.length} Slots: ${JSON.stringify(this.state.uploadedSlots.map(slot => slot.name)).replaceAll(',', ', ')} +
+
+ ${L.translate('Selected file does not contain valid config for {?}! // Gewählte Datei beinhaltet keine gültige Konfiguration für {?}!', C.CURRENT_DEVICE)} +
+
+
+
+ ${html`<${ActionButton} onclick="${() => this.uploadAllSlots()}" + label="Upload and replace all Slots // Alle Slots hochladen und ersetzen" + disabled="${state.uploadedSlots.length === 0}" + progressLabel="${L.translate('Uploading slots {?}% ... // Slots hochladen {?}% ...', state.uploadProgress)}" faIcon="fas upload"/>`} +
+
+ + +
+
+
+ ${L.translate('Choose slots to upload // Wähle Slots zum Hochladen')} + ${state.uploadedSlots.map(slot => { + let disabled = !state.selectedUploadSlots.includes(slot) && state.selectedUploadSlots.length + state.slots.length >= C.MAX_NUMBER_SLOTS; + return html` +
+ + +
` + })} +
+
+
+
+ ${html`<${FaIcon} icon="fas exclamation-triangle"/>`} + ${L.translate("Maximum number of slots reached! // Maximale Anzahl an Slots erreicht!")} +
+
+
+
+ +
+
+
+ +
+

${L.translate('Upload demo settings // Demo-Einstellungen hochladen')}

+
+
+ +
+
+
+
+ +
+
+ ${state.demoSettingSlots.length} Slots: +
    + ${state.demoSettingSlots.map((slot, index) => html` +
  1. + ${slot.name} + , +
  2. + `)} +
+
+
+
+
+ ${L.translate('Show description for "{?}" // Zeige Beschreibung für "{?}"', state.demoSettingSelected.name)} +
+ ${html`<${TextModal} close="${() => this.setState({ showDemoDescription: false })}" header="${L.translate('Details for "{?}" // Details für "{?}"', state.demoSettingSelected.name)}" textUrl="${state.demoSettingSelectedText.download_url}"/>`} +
+
+
+
+
+ ${html`<${ActionButton} onclick="${() => this.applyDemoSettings()}" + label="Apply settings preset // Demo-Einstellungen anwenden" + title="${isSlotTestMode ? this.getDeactivatedText() : ''}" + disabled="${state.demoSettingSlots.length === 0 || isSlotTestMode}" + progressLabel="${L.translate('Uploading slots {?}% ... // Slots hochladen {?}% ...', state.uploadProgress)}" faIcon="fas upload"/>`} +
+
+
+ +

${L.translate('Create backup // Sicherung erstellen')}

+
+
+ +
+
+
+ ${TabSlots.style}`; + } +} + +TabSlots.style = html``; + +window.addEventListener(C.EVENT_REFRESH_MAIN, () => { + if (TabSlots.instance) { + TabSlots.instance.setState({ slots: ATDevice.getSlots() }); + } +}); + +export { TabSlots }; \ No newline at end of file diff --git a/webgui/js_new/ui/views/TabStick.js b/webgui/js_new/ui/views/TabStick.js new file mode 100644 index 0000000..9803125 --- /dev/null +++ b/webgui/js_new/ui/views/TabStick.js @@ -0,0 +1,199 @@ +import { h, Component } from '../../../lib/preact.min.js'; +import htm from '../../../lib/htm.min.js'; +import { PositionVisualization } from "../components/PositionVisualization.js"; +import { preactUtil } from "../../util/preactUtil.js"; +import { RadioFieldset } from "../components/RadioFieldset.js"; +import { Slider } from "../components/Slider.js"; +import { ATDevice } from "../../communication/ATDevice.js"; +import { ActionButton } from "../components/ActionButton.js"; +import { FaIcon } from "../components/FaIcon.js"; +import { localStorageService } from "../../localStorageService.js"; + + +const html = htm.bind(h); + +const KEY_TAB_STICK_SHOW_ADVANCED = 'KEY_TAB_STICK_SHOW_ADVANCED'; +const KEY_TAB_STICK_SHOW_BARS = 'KEY_TAB_STICK_SHOW_BARS'; + +class TabStick extends Component { + + constructor() { + super(); + + TabStick.instance = this; + this.state = {}; + this.atCmds = [C.AT_CMD_SENSITIVITY_X, C.AT_CMD_SENSITIVITY_Y, C.AT_CMD_DEADZONE_X, C.AT_CMD_DEADZONE_Y, C.AT_CMD_MAX_SPEED, C.AT_CMD_ACCELERATION]; + this.initValues(); + } + + componentWillUnmount() { + TabStick.instance = null; + } + + initValues() { + this.setState({ + splitSensitivity: ATDevice.getConfig(C.AT_CMD_SENSITIVITY_X) !== ATDevice.getConfig(C.AT_CMD_SENSITIVITY_Y), // splitSensitivity: is the key and the rest is the value. + splitDeadzone: ATDevice.getConfig(C.AT_CMD_DEADZONE_X) !== ATDevice.getConfig(C.AT_CMD_DEADZONE_Y), + showAdvanced: localStorageService.hasKey(KEY_TAB_STICK_SHOW_ADVANCED) ? localStorageService.get(KEY_TAB_STICK_SHOW_ADVANCED) : false, + showAnalogBars: localStorageService.hasKey(KEY_TAB_STICK_SHOW_BARS) ? localStorageService.get(KEY_TAB_STICK_SHOW_BARS) : false + }); + let additionalState = {}; + this.atCmds.forEach(atCmd => { + additionalState[atCmd] = ATDevice.getConfig(atCmd); + }); + this.setState(additionalState); + ATDevice.resetMinMaxLiveValues(); + } + + valueChanged(value, constants) { + let state = {}; + constants.forEach(constant => { + state[constant] = value; + ATDevice.setConfig(constant, value); + }); + this.setState(state); + } + + toggleState(toggleName, updateConstants) { + preactUtil.toggleState(this, toggleName); + this.valueChanged(this.state[updateConstants[0]], updateConstants); + } + + toggleShowBars() { + localStorageService.save(KEY_TAB_STICK_SHOW_BARS, !this.state.showAnalogBars); + this.setState({ + showAnalogBars: !this.state.showAnalogBars + }); + } + + render() { + let state = this.state; + + return html` +

${L.translate('Stick configuration (slot "{?}") // Stick-Konfiguration (Slot "{?}")', ATDevice.getCurrentSlot())}

+ ${L.translate('Current stick position // Aktuelle Position des Sticks')} + + +
+
+
+ ${html`<${RadioFieldset} legend="Use stick for: // Verwende Stick für:" onchange="${(value) => ATDevice.setStickMode(value)}" elements="${C.STICK_MODES}" value="${ATDevice.getConfig(C.AT_CMD_STICK_MODE)}"/>`} +
+
+ + +
+
+
+ <${PositionVisualization} showAnalogBars="${state.showAnalogBars}" showAnalogValues="${state.showAnalogBars}" showDeadzone="${true}" showOrientation="${true}" showMaxPos="${true}" circleRadius="${10}" showZoom="${true}"/> +
+
+ +
+ ${(() => { + if (state.splitSensitivity) { + return html` + <${Slider} label="Horizontal Sensitivity: // Sensitivität horizontal:" oninput="${(value, constants) => this.valueChanged(value, constants)}" value="${state[C.AT_CMD_SENSITIVITY_X]}" + min="0" max="255" updateConstants="${[C.AT_CMD_SENSITIVITY_X]}" + toggleFn="${() => this.toggleState('splitSensitivity', [C.AT_CMD_SENSITIVITY_X, C.AT_CMD_SENSITIVITY_Y])}" toggleFnLabel="hide separate x/y // zeige x/y gemeinsam"/> + <${Slider} label="Vertical Sensitivity: // Sensitivität vertikal:" oninput="${(value, constants) => this.valueChanged(value, constants)}" value="${state[C.AT_CMD_SENSITIVITY_Y]}" + min="0" max="255" updateConstants="${[C.AT_CMD_SENSITIVITY_Y]}"/>` + } else { + return html` + <${Slider} label="Sensitivity: // Sensitivität:" oninput="${(value, constants) => this.valueChanged(value, constants)}" value="${state[C.AT_CMD_SENSITIVITY_X]}" + min="0" max="255" updateConstants="${[C.AT_CMD_SENSITIVITY_X, C.AT_CMD_SENSITIVITY_Y]}" + toggleFn="${() => this.toggleState('splitSensitivity', [])}" toggleFnLabel="show x/y separately // zeige x/y getrennt"/>` + } + })()} +
+
+ ${(() => { + if (state.splitDeadzone) { + return html` + <${Slider} label="Horizontal Deadzone: // Deadzone horizontal:" oninput="${(value, constants) => this.valueChanged(value, constants)}" value="${state[C.AT_CMD_DEADZONE_X]}" + min="0" max="200" updateConstants="${[C.AT_CMD_DEADZONE_X]}" + toggleFn="${() => this.toggleState('splitDeadzone', [C.AT_CMD_DEADZONE_X, C.AT_CMD_DEADZONE_Y])}" toggleFnLabel="hide separate x/y // zeige x/y gemeinsam"/> + <${Slider} label="Vertical Deadzone: // Deadzone vertikal:" oninput="${(value, constants) => this.valueChanged(value, constants)}" value="${state[C.AT_CMD_DEADZONE_Y]}" + min="0" max="200" updateConstants="${[C.AT_CMD_DEADZONE_Y]}"/>` + } else { + return html` + <${Slider} label="Deadzone:" oninput="${(value, constants) => this.valueChanged(value, constants)}" value="${state[C.AT_CMD_DEADZONE_X]}" + min="0" max="200" updateConstants="${[C.AT_CMD_DEADZONE_X, C.AT_CMD_DEADZONE_Y]}" + toggleFn="${() => this.toggleState('splitDeadzone', [])}" toggleFnLabel="show x/y separately // zeige x/y getrennt"/>` + } + })()} +
+
+ ${html`<${Slider} label="Maximum speed: // Maximale Geschwindigkeit:" oninput="${(value, constants) => this.valueChanged(value, constants)}" value="${state[C.AT_CMD_MAX_SPEED]}" + min="0" max="100" updateConstants="${[C.AT_CMD_MAX_SPEED]}"/>`} +
+
+ ${html`<${Slider} label="Acceleration: // Beschleunigung:" oninput="${(value, constants) => this.valueChanged(value, constants)}" value="${state[C.AT_CMD_ACCELERATION]}" + min="0" max="100" updateConstants="${[C.AT_CMD_ACCELERATION]}"/>`} +
+ +
+
+ +
+ +
+
+
+
+
+ ${html`<${ActionButton} onclick="${() => ATDevice.copyConfigToAllSlots(this.atCmds)}" + label="Copy slider values to all slots // Regler-Werte auf alle Slots anwenden" + progressLabel="Applying to all slots... // Anwenden auf alle Slots..." faIcon="far clone"/>`} +
+
+ ${html`<${ActionButton} onclick="${() => ATDevice.copyConfigToAllSlots([C.AT_CMD_STICK_MODE])}" + label="Copy stick usage to all slots // Stick-Verwendung auf alle Slots anwenden" + progressLabel="Applying to all slots... // Anwenden auf alle Slots..." faIcon="far clone"/>`} +
+
+ `; + } + +} + +TabStick.valueHandler = function (data) { + if (PositionVisualization.instance) { + PositionVisualization.instance.updateData(data); + } +}; + +TabStick.slotChangeHandler = function (data) { + if (TabStick.instance) { + TabStick.instance.initValues(); + } +}; + +window.addEventListener(C.EVENT_REFRESH_MAIN, () => { + + if (TabStick.instance) { + TabStick.instance.initValues(); + + } +}); + +export { TabStick }; \ No newline at end of file diff --git a/webgui/js_new/ui/views/TabTimings.js b/webgui/js_new/ui/views/TabTimings.js new file mode 100644 index 0000000..35b3808 --- /dev/null +++ b/webgui/js_new/ui/views/TabTimings.js @@ -0,0 +1,123 @@ +import { h, Component } from '../../../lib/preact.min.js'; +import htm from '../../../lib/htm.min.js'; +import { Slider } from "../components/Slider.js"; +import { ATDevice } from "../../communication/ATDevice.js"; +import { ActionButton } from "../components/ActionButton.js"; + +const html = htm.bind(h); + +class TabTimings extends Component { + constructor() { + super(); + + TabTimings.instance = this; + this.state = {}; + this.atCmds = [C.AT_CMD_THRESHOLD_LONGPRESS, C.AT_CMD_THRESHOLD_AUTODWELL]; + // ,C.AT_CMD_THRESHOLD_DOUBLEPRESS, C.AT_CMD_ANTITREMOR_PRESS, C.AT_CMD_ANTITREMOR_RELEASE, C.AT_CMD_ANTITREMOR_IDLE + this.updateState(); + } + + updateState() { + let state = {}; + for (let atCmd of this.atCmds) { + state[atCmd] = ATDevice.getConfig(atCmd); + } + this.setState(state); + } + + valueChanged(value, constants) { // Every element of constants (which is an array) gets iterated. + let state = {}; + constants.forEach(constant => { + state[constant] = value; + ATDevice.setConfig(constant, value); + }); + this.setState(state); + } + + resetSlidersTiming() { + const newValue = 50; + const constants = [C.AT_CMD_THRESHOLD_LONGPRESS, C.AT_CMD_THRESHOLD_AUTODWELL]; + // C.AT_CMD_THRESHOLD_DOUBLEPRESS, C.AT_CMD_ANTITREMOR_PRESS, C.AT_CMD_ANTITREMOR_RELEASE, C.AT_CMD_ANTITREMOR_IDLE + this.valueChanged(newValue, constants); + } + + + + render() { + let state = this.state; + + return html` +

${L.translate('Timings configuration (Slot "{?}") // Timing-Konfiguration (Slot "{?}")', ATDevice.getCurrentSlot())} +

+

${L.translate('Timings for special functions // Schwellenwerte für Spezialfunktionen')}

+ + + +
+
+ <${ActionButton} resetSlidersTiming="${() => this.resetSlidersTiming()}" + label="Reset All Thresholds // Alle Schwellenwerte zurücksetzen" faIcon="fas undo" progressLabel="Resetting Thresholds... // Schwellenwerte werden zurückgesetzt..." /> +
+ +
+ <${ActionButton} onclick="${() => ATDevice.copyConfigToAllSlots(this.atCmds)}" + label="Copy config to all slots // Konfiguration auf alle Slots anwenden" + progressLabel="Applying to all slots... // Anwenden auf alle Slots..." faIcon="far clone" /> +
+
+ + `; + } +} + +TabTimings.slotChangeHandler = function (data) { + if (TabTimings.instance) { + TabTimings.instance.updateState(); + } +}; + +export { TabTimings }; \ No newline at end of file diff --git a/webgui/js_new/ui/views/TabVisualization.js b/webgui/js_new/ui/views/TabVisualization.js new file mode 100644 index 0000000..7ee378f --- /dev/null +++ b/webgui/js_new/ui/views/TabVisualization.js @@ -0,0 +1,73 @@ +import { h, Component, render } from '../../../lib/preact.min.js'; +import htm from '../../../lib/htm.min.js'; +import { styleUtil } from "../../util/styleUtil.js"; +import { ATDevice } from "../../communication/ATDevice.js"; +import { MouseAndKeyboardVisualization } from "../components/MouseAndKeyboardVisualization.js"; + +const html = htm.bind(h); + +class TabVisualization extends Component { + constructor(props) { + super(props); + + TabVisualization.instance = this; + + // Dynamically initialize the first C.PHYSICAL_BUTTON_COUNT elements + const physicalButtonCount = C.PHYSICAL_BUTTON_COUNT; + const physicalButtonNames = Array.from({ length: physicalButtonCount }, (_, i) => `${i + 1}`); + + // Add the remaining button (virtual button function) names + const additionalButtonNames = ["Up // Rauf", "Down // Runter", "Left // Links", "Right // Rechts", "Sip // Ansaugen", "Strong Sip // Starkes Ansaugen", "Puff // Pusten", "Strong Puff // Starkes Pusten"]; + TabVisualization.BTN_NAMES = [...physicalButtonNames, ...additionalButtonNames]; + + this.setState({ + liveData: {} + }) + } + + componentWillUnmount() { // When changing the visualisation tab (GUI) it resets. + TabVisualization.instance = null; + } + + render() { + let data = this.state.liveData; + if (!data.LIVE_BUTTONS) { + return; + } + let circleRadius = Math.min(70, window.innerWidth / 7); + let fontStyle = `text-align: center; line-height: ${circleRadius}px; font-size: 25px`; + let btnNames; + btnNames = TabVisualization.BTN_NAMES; + + return html`

${L.translate('Visualization of current button state // Visualisierung aktueller Button-Status')}

+ +

${L.translate('Currently pressed buttons // Aktuell gedrückte Tasten')}

+
+ <${MouseAndKeyboardVisualization}/> +
+ `; + } +} + +TabVisualization.valueHandler = function (data) { + TabVisualization.instance.setState({ + liveData: data + }) +}; + +export { TabVisualization }; diff --git a/webgui/js/util/audioUtil.js b/webgui/js_new/util/audioUtil.js similarity index 100% rename from webgui/js/util/audioUtil.js rename to webgui/js_new/util/audioUtil.js diff --git a/webgui/js_new/util/firmwareUtil.js b/webgui/js_new/util/firmwareUtil.js new file mode 100644 index 0000000..18f803c --- /dev/null +++ b/webgui/js_new/util/firmwareUtil.js @@ -0,0 +1,82 @@ +import { L } from "../lquery.js"; +import { ATDevice } from "../communication/ATDevice.js"; + + +let firmwareUtil = {}; + +//credits: https://www.geeksforgeeks.org/how-to-delay-a-loop-in-javascript-using-async-await-with-promise/ +firmwareUtil.wait = function (milliseconds) { + return new Promise(resolve => { + setTimeout(() => { + resolve('') + }, milliseconds); + }) +} + +firmwareUtil.extractBoardValue = function (versionSuffix) { + const match = versionSuffix.match(/Board=([^\s,]+)/); + return match ? match[1].trim() : null; +} + + +firmwareUtil.getBTFWInfo = function () { + return getFWInfo('https://api.github.com/repos/asterics/esp32_mouse_keyboard/releases/latest', '.bin'); // TBD: use .fm3 for new firmware versions? +} + +firmwareUtil.getDeviceFWInfo = function (device, majorVersion) { + + let assetPostfix = C.CURRENT_DEVICE; + let fwURL = 'https://api.github.com/repos/asterics/FabiWare/releases/latest'; + let boardValue = firmwareUtil.extractBoardValue(ATDevice.getVersionSuffix()); + if (boardValue === 'Raspberry_Pi_Pico_2W' || boardValue === 'Raspberry_Pi_Pico_2') { + assetPostfix += '_RP2350.uf2'; + } else if (boardValue === 'Raspberry_Pi_Pico_W' || boardValue === 'Raspberry_Pi_Pico') { + assetPostfix += '_RP2040.uf2'; + } else if (boardValue === 'ARDUINO_NANO_RP2040_CONNECT') { + assetPostfix = 'FLIPMOUSE.uf2'; + } + console.log('Microcontroller board=', boardValue ); + console.log('Release asset API URL='+ fwURL + ', Asset filter=' + assetPostfix); + + let fwInfo = getFWInfo(fwURL, assetPostfix); + return fwInfo; +} + +firmwareUtil.updateDeviceFirmware = function (progressHandler) { + progressHandler = progressHandler || (() => { }); + firmwareUtil.getDeviceFWInfo().then(result => { + + let message, deviceName; + + message = 'Do you want to update the firmware to version {?}? After confirming this message, put the downloaded .UF2 file to the ("{?}") drive. // Möchten Sie die Firmware auf Version {?} aktualisieren? Nach Bestätigung dieser Meldung bitte die heruntergeladene .UF2 Datei im ("{?}") Laufwerk ablegen.'; + if (C.DEVICE_IS_FM) { + deviceName = 'Arduino Nano RP2040 Connect'; + } else { + deviceName = 'Raspberry Pi Pico'; + } + + if (!confirm(L.translate(message, result.version, deviceName))) { + return; + } + progressHandler(1); + ATDevice.updateFirmware(result.downloadUrl, progressHandler); + }); +} + +function getFWInfo(apiUrl, binaryStringFilter) { + return L.HTTPRequest(apiUrl, 'GET', 'json').then(result => { + let binaryAssets = result.assets; + let filteredAsset= binaryAssets.filter(asset => asset.name.indexOf(binaryStringFilter) > -1)[0]; + console.log('Firmware assets:', binaryAssets); + console.log('Filtered asset:', filteredAsset); + + return { + version: L.formatVersion(result['tag_name']), + infoUrl: result['html_url'], + downloadUrl: 'https://proxy.asterics-foundation.org/proxybase64url.php?csurl=' + encodeURIComponent(btoa(filteredAsset.browser_download_url)), + originalDownloadUrl: filteredAsset.browser_download_url + }; + }); +} + +export { firmwareUtil }; \ No newline at end of file diff --git a/webgui/js/util/helpUtil.js b/webgui/js_new/util/helpUtil.js similarity index 100% rename from webgui/js/util/helpUtil.js rename to webgui/js_new/util/helpUtil.js diff --git a/webgui/js_fm/util/preactUtil.js b/webgui/js_new/util/preactUtil.js similarity index 100% rename from webgui/js_fm/util/preactUtil.js rename to webgui/js_new/util/preactUtil.js diff --git a/webgui/js/util/styleUtil.js b/webgui/js_new/util/styleUtil.js similarity index 100% rename from webgui/js/util/styleUtil.js rename to webgui/js_new/util/styleUtil.js diff --git a/webgui/legacy/css/bootstrap-custom.css b/webgui/legacy/css/bootstrap-custom.css new file mode 100644 index 0000000..dd3c861 --- /dev/null +++ b/webgui/legacy/css/bootstrap-custom.css @@ -0,0 +1,41 @@ +/* copied needed classes from bootstrap.css */ + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.sr-only-focusable:active, .sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + overflow: visible; + clip: auto; + white-space: normal; +} + +.font-weight-bold { + font-weight: bold; +} + +.justify-content-end { + -ms-flex-pack: end !important; + justify-content: flex-end !important; +} + +.justify-content-center { + -ms-flex-pack: center !important; + justify-content: center !important; +} + +.align-items-center { + -ms-flex-align: center !important; + align-items: center !important; +} \ No newline at end of file diff --git a/webgui/legacy/css/bootstrap-grid.css b/webgui/legacy/css/bootstrap-grid.css new file mode 100644 index 0000000..4ce4d53 --- /dev/null +++ b/webgui/legacy/css/bootstrap-grid.css @@ -0,0 +1,3917 @@ +/*! + * Bootstrap Grid v4.4.1 (https://getbootstrap.com/) + * Copyright 2011-2019 The Bootstrap Authors + * Copyright 2011-2019 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +html { + box-sizing: border-box; + -ms-overflow-style: scrollbar; +} + +*, +*::before, +*::after { + box-sizing: inherit; +} + +.container { + width: 100%; + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} + +@media (min-width: 576px) { + .container { + max-width: 540px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 720px; + } +} + +@media (min-width: 992px) { + .container { + max-width: 960px; + } +} + +@media (min-width: 1200px) { + .container { + max-width: 1140px; + } +} + +.container-fluid, .container-sm, .container-md, .container-lg, .container-xl { + width: 100%; + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} + +@media (min-width: 576px) { + .container, .container-sm { + max-width: 540px; + } +} + +@media (min-width: 768px) { + .container, .container-sm, .container-md { + max-width: 720px; + } +} + +@media (min-width: 992px) { + .container, .container-sm, .container-md, .container-lg { + max-width: 960px; + } +} + +@media (min-width: 1200px) { + .container, .container-sm, .container-md, .container-lg, .container-xl { + max-width: 1140px; + } +} + +.row { + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + margin-right: -15px; + margin-left: -15px; +} + +.no-gutters { + margin-right: 0; + margin-left: 0; +} + +.no-gutters > .col, +.no-gutters > [class*="col-"] { + padding-right: 0; + padding-left: 0; +} + +.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col, +.col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm, +.col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md, +.col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg, +.col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl, +.col-xl-auto { + position: relative; + width: 100%; + padding-right: 15px; + padding-left: 15px; +} + +.col { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; +} + +.row-cols-1 > * { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; +} + +.row-cols-2 > * { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; +} + +.row-cols-3 > * { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; +} + +.row-cols-4 > * { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; +} + +.row-cols-5 > * { + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20%; +} + +.row-cols-6 > * { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; +} + +.col-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: 100%; +} + +.col-1 { + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; +} + +.col-2 { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; +} + +.col-3 { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; +} + +.col-4 { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; +} + +.col-5 { + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; +} + +.col-6 { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; +} + +.col-7 { + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; +} + +.col-8 { + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; +} + +.col-9 { + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; +} + +.col-10 { + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; +} + +.col-11 { + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; +} + +.col-12 { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; +} + +.order-first { + -ms-flex-order: -1; + order: -1; +} + +.order-last { + -ms-flex-order: 13; + order: 13; +} + +.order-0 { + -ms-flex-order: 0; + order: 0; +} + +.order-1 { + -ms-flex-order: 1; + order: 1; +} + +.order-2 { + -ms-flex-order: 2; + order: 2; +} + +.order-3 { + -ms-flex-order: 3; + order: 3; +} + +.order-4 { + -ms-flex-order: 4; + order: 4; +} + +.order-5 { + -ms-flex-order: 5; + order: 5; +} + +.order-6 { + -ms-flex-order: 6; + order: 6; +} + +.order-7 { + -ms-flex-order: 7; + order: 7; +} + +.order-8 { + -ms-flex-order: 8; + order: 8; +} + +.order-9 { + -ms-flex-order: 9; + order: 9; +} + +.order-10 { + -ms-flex-order: 10; + order: 10; +} + +.order-11 { + -ms-flex-order: 11; + order: 11; +} + +.order-12 { + -ms-flex-order: 12; + order: 12; +} + +.offset-1 { + margin-left: 8.333333%; +} + +.offset-2 { + margin-left: 16.666667%; +} + +.offset-3 { + margin-left: 25%; +} + +.offset-4 { + margin-left: 33.333333%; +} + +.offset-5 { + margin-left: 41.666667%; +} + +.offset-6 { + margin-left: 50%; +} + +.offset-7 { + margin-left: 58.333333%; +} + +.offset-8 { + margin-left: 66.666667%; +} + +.offset-9 { + margin-left: 75%; +} + +.offset-10 { + margin-left: 83.333333%; +} + +.offset-11 { + margin-left: 91.666667%; +} + +@media (min-width: 350px) { + #selectSlots{ + width: 12em; + } +} + +@media (min-width: 576px) { + .col-sm { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .row-cols-sm-1 > * { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .row-cols-sm-2 > * { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .row-cols-sm-3 > * { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .row-cols-sm-4 > * { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .row-cols-sm-5 > * { + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20%; + } + .row-cols-sm-6 > * { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-sm-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: 100%; + } + .col-sm-1 { + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; + } + .col-sm-2 { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-sm-3 { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-sm-4 { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .col-sm-5 { + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; + } + .col-sm-6 { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-sm-7 { + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; + } + .col-sm-8 { + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; + } + .col-sm-9 { + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-sm-10 { + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; + } + .col-sm-11 { + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; + } + .col-sm-12 { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-sm-first { + -ms-flex-order: -1; + order: -1; + } + .order-sm-last { + -ms-flex-order: 13; + order: 13; + } + .order-sm-0 { + -ms-flex-order: 0; + order: 0; + } + .order-sm-1 { + -ms-flex-order: 1; + order: 1; + } + .order-sm-2 { + -ms-flex-order: 2; + order: 2; + } + .order-sm-3 { + -ms-flex-order: 3; + order: 3; + } + .order-sm-4 { + -ms-flex-order: 4; + order: 4; + } + .order-sm-5 { + -ms-flex-order: 5; + order: 5; + } + .order-sm-6 { + -ms-flex-order: 6; + order: 6; + } + .order-sm-7 { + -ms-flex-order: 7; + order: 7; + } + .order-sm-8 { + -ms-flex-order: 8; + order: 8; + } + .order-sm-9 { + -ms-flex-order: 9; + order: 9; + } + .order-sm-10 { + -ms-flex-order: 10; + order: 10; + } + .order-sm-11 { + -ms-flex-order: 11; + order: 11; + } + .order-sm-12 { + -ms-flex-order: 12; + order: 12; + } + .offset-sm-0 { + margin-left: 0; + } + .offset-sm-1 { + margin-left: 8.333333%; + } + .offset-sm-2 { + margin-left: 16.666667%; + } + .offset-sm-3 { + margin-left: 25%; + } + .offset-sm-4 { + margin-left: 33.333333%; + } + .offset-sm-5 { + margin-left: 41.666667%; + } + .offset-sm-6 { + margin-left: 50%; + } + .offset-sm-7 { + margin-left: 58.333333%; + } + .offset-sm-8 { + margin-left: 66.666667%; + } + .offset-sm-9 { + margin-left: 75%; + } + .offset-sm-10 { + margin-left: 83.333333%; + } + .offset-sm-11 { + margin-left: 91.666667%; + } + + #selectSlots{ + width: 12em; + } +} + +@media (min-width: 768px) { + .col-md { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .row-cols-md-1 > * { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .row-cols-md-2 > * { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .row-cols-md-3 > * { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .row-cols-md-4 > * { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .row-cols-md-5 > * { + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20%; + } + .row-cols-md-6 > * { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-md-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: 100%; + } + .col-md-1 { + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; + } + .col-md-2 { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-md-3 { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + + .col-md-4 { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .col-md-5 { + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; + } + .col-md-6 { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-md-7 { + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; + } + .col-md-8 { + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; + } + .col-md-9 { + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-md-10 { + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; + } + .col-md-11 { + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; + } + .col-md-12 { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-md-first { + -ms-flex-order: -1; + order: -1; + } + .order-md-last { + -ms-flex-order: 13; + order: 13; + } + .order-md-0 { + -ms-flex-order: 0; + order: 0; + } + .order-md-1 { + -ms-flex-order: 1; + order: 1; + } + .order-md-2 { + -ms-flex-order: 2; + order: 2; + } + .order-md-3 { + -ms-flex-order: 3; + order: 3; + } + .order-md-4 { + -ms-flex-order: 4; + order: 4; + } + .order-md-5 { + -ms-flex-order: 5; + order: 5; + } + .order-md-6 { + -ms-flex-order: 6; + order: 6; + } + .order-md-7 { + -ms-flex-order: 7; + order: 7; + } + .order-md-8 { + -ms-flex-order: 8; + order: 8; + } + .order-md-9 { + -ms-flex-order: 9; + order: 9; + } + .order-md-10 { + -ms-flex-order: 10; + order: 10; + } + .order-md-11 { + -ms-flex-order: 11; + order: 11; + } + .order-md-12 { + -ms-flex-order: 12; + order: 12; + } + .offset-md-0 { + margin-left: 0; + } + .offset-md-1 { + margin-left: 8.333333%; + } + .offset-md-2 { + margin-left: 16.666667%; + } + .offset-md-3 { + margin-left: 25%; + } + .offset-md-4 { + margin-left: 33.333333%; + } + .offset-md-5 { + margin-left: 41.666667%; + } + .offset-md-6 { + margin-left: 50%; + } + .offset-md-7 { + margin-left: 58.333333%; + } + .offset-md-8 { + margin-left: 66.666667%; + } + .offset-md-9 { + margin-left: 75%; + } + .offset-md-10 { + margin-left: 83.333333%; + } + .offset-md-11 { + margin-left: 91.666667%; + } + + #selectSlots{ + width: 12em; + } +} + +@media (min-width: 992px) { + .col-lg { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .row-cols-lg-1 > * { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .row-cols-lg-2 > * { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .row-cols-lg-3 > * { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .row-cols-lg-4 > * { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .row-cols-lg-5 > * { + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20%; + } + .row-cols-lg-6 > * { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-lg-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: 100%; + } + .col-lg-1 { + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; + } + .col-lg-2 { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-lg-3 { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-lg-4 { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .col-lg-5 { + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; + } + .col-lg-6 { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-lg-7 { + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; + } + .col-lg-8 { + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; + } + .col-lg-9 { + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-lg-10 { + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; + } + .col-lg-11 { + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; + } + .col-lg-12 { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-lg-first { + -ms-flex-order: -1; + order: -1; + } + .order-lg-last { + -ms-flex-order: 13; + order: 13; + } + .order-lg-0 { + -ms-flex-order: 0; + order: 0; + } + .order-lg-1 { + -ms-flex-order: 1; + order: 1; + } + .order-lg-2 { + -ms-flex-order: 2; + order: 2; + } + .order-lg-3 { + -ms-flex-order: 3; + order: 3; + } + .order-lg-4 { + -ms-flex-order: 4; + order: 4; + } + .order-lg-5 { + -ms-flex-order: 5; + order: 5; + } + .order-lg-6 { + -ms-flex-order: 6; + order: 6; + } + .order-lg-7 { + -ms-flex-order: 7; + order: 7; + } + .order-lg-8 { + -ms-flex-order: 8; + order: 8; + } + .order-lg-9 { + -ms-flex-order: 9; + order: 9; + } + .order-lg-10 { + -ms-flex-order: 10; + order: 10; + } + .order-lg-11 { + -ms-flex-order: 11; + order: 11; + } + .order-lg-12 { + -ms-flex-order: 12; + order: 12; + } + .offset-lg-0 { + margin-left: 0; + } + .offset-lg-1 { + margin-left: 8.333333%; + } + .offset-lg-2 { + margin-left: 16.666667%; + } + .offset-lg-3 { + margin-left: 25%; + } + .offset-lg-4 { + margin-left: 33.333333%; + } + .offset-lg-5 { + margin-left: 41.666667%; + } + .offset-lg-6 { + margin-left: 50%; + } + .offset-lg-7 { + margin-left: 58.333333%; + } + .offset-lg-8 { + margin-left: 66.666667%; + } + .offset-lg-9 { + margin-left: 75%; + } + .offset-lg-10 { + margin-left: 83.333333%; + } + .offset-lg-11 { + margin-left: 91.666667%; + } + #selectSlots{ + width: 12em; + } +} + +@media (min-width: 1200px) { + .col-xl { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .row-cols-xl-1 > * { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .row-cols-xl-2 > * { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .row-cols-xl-3 > * { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .row-cols-xl-4 > * { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .row-cols-xl-5 > * { + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20%; + } + .row-cols-xl-6 > * { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-xl-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: 100%; + } + .col-xl-1 { + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; + } + .col-xl-2 { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-xl-3 { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-xl-4 { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .col-xl-5 { + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; + } + .col-xl-6 { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-xl-7 { + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; + } + .col-xl-8 { + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; + } + .col-xl-9 { + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-xl-10 { + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; + } + .col-xl-11 { + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; + } + .col-xl-12 { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-xl-first { + -ms-flex-order: -1; + order: -1; + } + .order-xl-last { + -ms-flex-order: 13; + order: 13; + } + .order-xl-0 { + -ms-flex-order: 0; + order: 0; + } + .order-xl-1 { + -ms-flex-order: 1; + order: 1; + } + .order-xl-2 { + -ms-flex-order: 2; + order: 2; + } + .order-xl-3 { + -ms-flex-order: 3; + order: 3; + } + .order-xl-4 { + -ms-flex-order: 4; + order: 4; + } + .order-xl-5 { + -ms-flex-order: 5; + order: 5; + } + .order-xl-6 { + -ms-flex-order: 6; + order: 6; + } + .order-xl-7 { + -ms-flex-order: 7; + order: 7; + } + .order-xl-8 { + -ms-flex-order: 8; + order: 8; + } + .order-xl-9 { + -ms-flex-order: 9; + order: 9; + } + .order-xl-10 { + -ms-flex-order: 10; + order: 10; + } + .order-xl-11 { + -ms-flex-order: 11; + order: 11; + } + .order-xl-12 { + -ms-flex-order: 12; + order: 12; + } + .offset-xl-0 { + margin-left: 0; + } + .offset-xl-1 { + margin-left: 8.333333%; + } + .offset-xl-2 { + margin-left: 16.666667%; + } + .offset-xl-3 { + margin-left: 25%; + } + .offset-xl-4 { + margin-left: 33.333333%; + } + .offset-xl-5 { + margin-left: 41.666667%; + } + .offset-xl-6 { + margin-left: 50%; + } + .offset-xl-7 { + margin-left: 58.333333%; + } + .offset-xl-8 { + margin-left: 66.666667%; + } + .offset-xl-9 { + margin-left: 75%; + } + .offset-xl-10 { + margin-left: 83.333333%; + } + .offset-xl-11 { + margin-left: 91.666667%; + } +} + +.d-none { + display: none !important; +} + +.d-inline { + display: inline !important; +} + +.d-inline-block { + display: inline-block !important; +} + +.d-block { + display: block !important; +} + +.d-table { + display: table !important; +} + +.d-table-row { + display: table-row !important; +} + +.d-table-cell { + display: table-cell !important; +} + +.d-flex { + display: -ms-flexbox !important; + display: flex !important; +} + +.d-inline-flex { + display: -ms-inline-flexbox !important; + display: inline-flex !important; +} + +@media (min-width: 576px) { + .d-sm-none { + display: none !important; + } + .d-sm-inline { + display: inline !important; + } + .d-sm-inline-block { + display: inline-block !important; + } + .d-sm-block { + display: block !important; + } + .d-sm-table { + display: table !important; + } + .d-sm-table-row { + display: table-row !important; + } + .d-sm-table-cell { + display: table-cell !important; + } + .d-sm-flex { + display: -ms-flexbox !important; + display: flex !important; + } + .d-sm-inline-flex { + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} + +@media (min-width: 768px) { + .d-md-none { + display: none !important; + } + .d-md-inline { + display: inline !important; + } + .d-md-inline-block { + display: inline-block !important; + } + .d-md-block { + display: block !important; + } + .d-md-table { + display: table !important; + } + .d-md-table-row { + display: table-row !important; + } + .d-md-table-cell { + display: table-cell !important; + } + .d-md-flex { + display: -ms-flexbox !important; + display: flex !important; + } + .d-md-inline-flex { + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} + +@media (min-width: 992px) { + .d-lg-none { + display: none !important; + } + .d-lg-inline { + display: inline !important; + } + .d-lg-inline-block { + display: inline-block !important; + } + .d-lg-block { + display: block !important; + } + .d-lg-table { + display: table !important; + } + .d-lg-table-row { + display: table-row !important; + } + .d-lg-table-cell { + display: table-cell !important; + } + .d-lg-flex { + display: -ms-flexbox !important; + display: flex !important; + } + .d-lg-inline-flex { + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} + +@media (min-width: 1200px) { + .d-xl-none { + display: none !important; + } + .d-xl-inline { + display: inline !important; + } + .d-xl-inline-block { + display: inline-block !important; + } + .d-xl-block { + display: block !important; + } + .d-xl-table { + display: table !important; + } + .d-xl-table-row { + display: table-row !important; + } + .d-xl-table-cell { + display: table-cell !important; + } + .d-xl-flex { + display: -ms-flexbox !important; + display: flex !important; + } + .d-xl-inline-flex { + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} + +@media print { + .d-print-none { + display: none !important; + } + .d-print-inline { + display: inline !important; + } + .d-print-inline-block { + display: inline-block !important; + } + .d-print-block { + display: block !important; + } + .d-print-table { + display: table !important; + } + .d-print-table-row { + display: table-row !important; + } + .d-print-table-cell { + display: table-cell !important; + } + .d-print-flex { + display: -ms-flexbox !important; + display: flex !important; + } + .d-print-inline-flex { + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} + +.flex-row { + -ms-flex-direction: row !important; + flex-direction: row !important; +} + +.flex-column { + -ms-flex-direction: column !important; + flex-direction: column !important; +} + +.flex-row-reverse { + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; +} + +.flex-column-reverse { + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; +} + +.flex-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; +} + +.flex-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; +} + +.flex-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; +} + +.flex-fill { + -ms-flex: 1 1 auto !important; + flex: 1 1 auto !important; +} + +.flex-grow-0 { + -ms-flex-positive: 0 !important; + flex-grow: 0 !important; +} + +.flex-grow-1 { + -ms-flex-positive: 1 !important; + flex-grow: 1 !important; +} + +.flex-shrink-0 { + -ms-flex-negative: 0 !important; + flex-shrink: 0 !important; +} + +.flex-shrink-1 { + -ms-flex-negative: 1 !important; + flex-shrink: 1 !important; +} + +.justify-content-start { + -ms-flex-pack: start !important; + justify-content: flex-start !important; +} + +.justify-content-end { + -ms-flex-pack: end !important; + justify-content: flex-end !important; +} + +.justify-content-center { + -ms-flex-pack: center !important; + justify-content: center !important; +} + +.justify-content-between { + -ms-flex-pack: justify !important; + justify-content: space-between !important; +} + +.justify-content-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; +} + +.align-items-start { + -ms-flex-align: start !important; + align-items: flex-start !important; +} + +.align-items-end { + -ms-flex-align: end !important; + align-items: flex-end !important; +} + +.align-items-center { + -ms-flex-align: center !important; + align-items: center !important; +} + +.align-items-baseline { + -ms-flex-align: baseline !important; + align-items: baseline !important; +} + +.align-items-stretch { + -ms-flex-align: stretch !important; + align-items: stretch !important; +} + +.align-content-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; +} + +.align-content-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; +} + +.align-content-center { + -ms-flex-line-pack: center !important; + align-content: center !important; +} + +.align-content-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; +} + +.align-content-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; +} + +.align-content-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; +} + +.align-self-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; +} + +.align-self-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; +} + +.align-self-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; +} + +.align-self-center { + -ms-flex-item-align: center !important; + align-self: center !important; +} + +.align-self-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; +} + +.align-self-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; +} + +@media (min-width: 576px) { + .flex-sm-row { + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-sm-column { + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-sm-row-reverse { + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-sm-column-reverse { + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-sm-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-sm-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-sm-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .flex-sm-fill { + -ms-flex: 1 1 auto !important; + flex: 1 1 auto !important; + } + .flex-sm-grow-0 { + -ms-flex-positive: 0 !important; + flex-grow: 0 !important; + } + .flex-sm-grow-1 { + -ms-flex-positive: 1 !important; + flex-grow: 1 !important; + } + .flex-sm-shrink-0 { + -ms-flex-negative: 0 !important; + flex-shrink: 0 !important; + } + .flex-sm-shrink-1 { + -ms-flex-negative: 1 !important; + flex-shrink: 1 !important; + } + .justify-content-sm-start { + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-sm-end { + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-sm-center { + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-sm-between { + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-sm-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-sm-start { + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-sm-end { + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-sm-center { + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-sm-baseline { + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-sm-stretch { + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-sm-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-sm-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-sm-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-sm-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-sm-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-sm-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-sm-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-sm-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-sm-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-sm-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-sm-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-sm-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} + +@media (min-width: 768px) { + .flex-md-row { + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-md-column { + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-md-row-reverse { + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-md-column-reverse { + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-md-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-md-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-md-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .flex-md-fill { + -ms-flex: 1 1 auto !important; + flex: 1 1 auto !important; + } + .flex-md-grow-0 { + -ms-flex-positive: 0 !important; + flex-grow: 0 !important; + } + .flex-md-grow-1 { + -ms-flex-positive: 1 !important; + flex-grow: 1 !important; + } + .flex-md-shrink-0 { + -ms-flex-negative: 0 !important; + flex-shrink: 0 !important; + } + .flex-md-shrink-1 { + -ms-flex-negative: 1 !important; + flex-shrink: 1 !important; + } + .justify-content-md-start { + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-md-end { + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-md-center { + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-md-between { + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-md-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-md-start { + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-md-end { + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-md-center { + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-md-baseline { + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-md-stretch { + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-md-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-md-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-md-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-md-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-md-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-md-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-md-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-md-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-md-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-md-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-md-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-md-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} + +@media (min-width: 992px) { + .flex-lg-row { + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-lg-column { + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-lg-row-reverse { + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-lg-column-reverse { + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-lg-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-lg-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-lg-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .flex-lg-fill { + -ms-flex: 1 1 auto !important; + flex: 1 1 auto !important; + } + .flex-lg-grow-0 { + -ms-flex-positive: 0 !important; + flex-grow: 0 !important; + } + .flex-lg-grow-1 { + -ms-flex-positive: 1 !important; + flex-grow: 1 !important; + } + .flex-lg-shrink-0 { + -ms-flex-negative: 0 !important; + flex-shrink: 0 !important; + } + .flex-lg-shrink-1 { + -ms-flex-negative: 1 !important; + flex-shrink: 1 !important; + } + .justify-content-lg-start { + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-lg-end { + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-lg-center { + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-lg-between { + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-lg-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-lg-start { + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-lg-end { + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-lg-center { + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-lg-baseline { + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-lg-stretch { + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-lg-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-lg-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-lg-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-lg-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-lg-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-lg-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-lg-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-lg-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-lg-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-lg-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-lg-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-lg-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} + +@media (min-width: 1200px) { + .flex-xl-row { + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-xl-column { + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-xl-row-reverse { + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-xl-column-reverse { + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-xl-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-xl-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-xl-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .flex-xl-fill { + -ms-flex: 1 1 auto !important; + flex: 1 1 auto !important; + } + .flex-xl-grow-0 { + -ms-flex-positive: 0 !important; + flex-grow: 0 !important; + } + .flex-xl-grow-1 { + -ms-flex-positive: 1 !important; + flex-grow: 1 !important; + } + .flex-xl-shrink-0 { + -ms-flex-negative: 0 !important; + flex-shrink: 0 !important; + } + .flex-xl-shrink-1 { + -ms-flex-negative: 1 !important; + flex-shrink: 1 !important; + } + .justify-content-xl-start { + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-xl-end { + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-xl-center { + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-xl-between { + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-xl-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-xl-start { + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-xl-end { + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-xl-center { + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-xl-baseline { + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-xl-stretch { + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-xl-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-xl-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-xl-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-xl-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-xl-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-xl-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-xl-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-xl-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-xl-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-xl-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-xl-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-xl-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} + +.m-0 { + margin: 0 !important; +} + +.mt-0, +.my-0 { + margin-top: 0 !important; +} + +.mr-0, +.mx-0 { + margin-right: 0 !important; +} + +.mb-0, +.my-0 { + margin-bottom: 0 !important; +} + +.ml-0, +.mx-0 { + margin-left: 0 !important; +} + +.m-1 { + margin: 0.25rem !important; +} + +.mt-1, +.my-1 { + margin-top: 0.25rem !important; +} + +.mr-1, +.mx-1 { + margin-right: 0.25rem !important; +} + +.mb-1, +.my-1 { + margin-bottom: 0.25rem !important; +} + +.ml-1, +.mx-1 { + margin-left: 0.25rem !important; +} + +.m-2 { + margin: 0.5rem !important; +} + +.mt-2, +.my-2 { + margin-top: 0.5rem !important; +} + +.mr-2, +.mx-2 { + margin-right: 0.5rem !important; +} + +.mb-2, +.my-2 { + margin-bottom: 0.5rem !important; +} + +.ml-2, +.mx-2 { + margin-left: 0.5rem !important; +} + +.m-3 { + margin: 1rem !important; +} + +.mt-3, +.my-3 { + margin-top: 1rem !important; +} + +.mr-3, +.mx-3 { + margin-right: 1rem !important; +} + +.mb-3, +.my-3 { + margin-bottom: 1rem !important; +} + +.ml-3, +.mx-3 { + margin-left: 1rem !important; +} + +.m-4 { + margin: 1.5rem !important; +} + +.mt-4, +.my-4 { + margin-top: 1.5rem !important; +} + +.mr-4, +.mx-4 { + margin-right: 1.5rem !important; +} + +.mb-4, +.my-4 { + margin-bottom: 1.5rem !important; +} + +.ml-4, +.mx-4 { + margin-left: 1.5rem !important; +} + +.m-5 { + margin: 3rem !important; +} + +.mt-5, +.my-5 { + margin-top: 3rem !important; +} + +.mr-5, +.mx-5 { + margin-right: 3rem !important; +} + +.mb-5, +.my-5 { + margin-bottom: 3rem !important; +} + +.ml-5, +.mx-5 { + margin-left: 3rem !important; +} + +.p-0 { + padding: 0 !important; +} + +.pt-0, +.py-0 { + padding-top: 0 !important; +} + +.pr-0, +.px-0 { + padding-right: 0 !important; +} + +.pb-0, +.py-0 { + padding-bottom: 0 !important; +} + +.pl-0, +.px-0 { + padding-left: 0 !important; +} + +.p-1 { + padding: 0.25rem !important; +} + +.pt-1, +.py-1 { + padding-top: 0.25rem !important; +} + +.pr-1, +.px-1 { + padding-right: 0.25rem !important; +} + +.pb-1, +.py-1 { + padding-bottom: 0.25rem !important; +} + +.pl-1, +.px-1 { + padding-left: 0.25rem !important; +} + +.p-2 { + padding: 0.5rem !important; +} + +.pt-2, +.py-2 { + padding-top: 0.5rem !important; +} + +.pr-2, +.px-2 { + padding-right: 0.5rem !important; +} + +.pb-2, +.py-2 { + padding-bottom: 0.5rem !important; +} + +.pl-2, +.px-2 { + padding-left: 0.5rem !important; +} + +.p-3 { + padding: 1rem !important; +} + +.pt-3, +.py-3 { + padding-top: 1rem !important; +} + +.pr-3, +.px-3 { + padding-right: 1rem !important; +} + +.pb-3, +.py-3 { + padding-bottom: 1rem !important; +} + +.pl-3, +.px-3 { + padding-left: 1rem !important; +} + +.p-4 { + padding: 1.5rem !important; +} + +.pt-4, +.py-4 { + padding-top: 1.5rem !important; +} + +.pr-4, +.px-4 { + padding-right: 1.5rem !important; +} + +.pb-4, +.py-4 { + padding-bottom: 1.5rem !important; +} + +.pl-4, +.px-4 { + padding-left: 1.5rem !important; +} + +.p-5 { + padding: 3rem !important; +} + +.pt-5, +.py-5 { + padding-top: 3rem !important; +} + +.pr-5, +.px-5 { + padding-right: 3rem !important; +} + +.pb-5, +.py-5 { + padding-bottom: 3rem !important; +} + +.pl-5, +.px-5 { + padding-left: 3rem !important; +} + +.m-n1 { + margin: -0.25rem !important; +} + +.mt-n1, +.my-n1 { + margin-top: -0.25rem !important; +} + +.mr-n1, +.mx-n1 { + margin-right: -0.25rem !important; +} + +.mb-n1, +.my-n1 { + margin-bottom: -0.25rem !important; +} + +.ml-n1, +.mx-n1 { + margin-left: -0.25rem !important; +} + +.m-n2 { + margin: -0.5rem !important; +} + +.mt-n2, +.my-n2 { + margin-top: -0.5rem !important; +} + +.mr-n2, +.mx-n2 { + margin-right: -0.5rem !important; +} + +.mb-n2, +.my-n2 { + margin-bottom: -0.5rem !important; +} + +.ml-n2, +.mx-n2 { + margin-left: -0.5rem !important; +} + +.m-n3 { + margin: -1rem !important; +} + +.mt-n3, +.my-n3 { + margin-top: -1rem !important; +} + +.mr-n3, +.mx-n3 { + margin-right: -1rem !important; +} + +.mb-n3, +.my-n3 { + margin-bottom: -1rem !important; +} + +.ml-n3, +.mx-n3 { + margin-left: -1rem !important; +} + +.m-n4 { + margin: -1.5rem !important; +} + +.mt-n4, +.my-n4 { + margin-top: -1.5rem !important; +} + +.mr-n4, +.mx-n4 { + margin-right: -1.5rem !important; +} + +.mb-n4, +.my-n4 { + margin-bottom: -1.5rem !important; +} + +.ml-n4, +.mx-n4 { + margin-left: -1.5rem !important; +} + +.m-n5 { + margin: -3rem !important; +} + +.mt-n5, +.my-n5 { + margin-top: -3rem !important; +} + +.mr-n5, +.mx-n5 { + margin-right: -3rem !important; +} + +.mb-n5, +.my-n5 { + margin-bottom: -3rem !important; +} + +.ml-n5, +.mx-n5 { + margin-left: -3rem !important; +} + +.m-auto { + margin: auto !important; +} + +.mt-auto, +.my-auto { + margin-top: auto !important; +} + +.mr-auto, +.mx-auto { + margin-right: auto !important; +} + +.mb-auto, +.my-auto { + margin-bottom: auto !important; +} + +.ml-auto, +.mx-auto { + margin-left: auto !important; +} + +@media (min-width: 576px) { + .m-sm-0 { + margin: 0 !important; + } + .mt-sm-0, + .my-sm-0 { + margin-top: 0 !important; + } + .mr-sm-0, + .mx-sm-0 { + margin-right: 0 !important; + } + .mb-sm-0, + .my-sm-0 { + margin-bottom: 0 !important; + } + .ml-sm-0, + .mx-sm-0 { + margin-left: 0 !important; + } + .m-sm-1 { + margin: 0.25rem !important; + } + .mt-sm-1, + .my-sm-1 { + margin-top: 0.25rem !important; + } + .mr-sm-1, + .mx-sm-1 { + margin-right: 0.25rem !important; + } + .mb-sm-1, + .my-sm-1 { + margin-bottom: 0.25rem !important; + } + .ml-sm-1, + .mx-sm-1 { + margin-left: 0.25rem !important; + } + .m-sm-2 { + margin: 0.5rem !important; + } + .mt-sm-2, + .my-sm-2 { + margin-top: 0.5rem !important; + } + .mr-sm-2, + .mx-sm-2 { + margin-right: 0.5rem !important; + } + .mb-sm-2, + .my-sm-2 { + margin-bottom: 0.5rem !important; + } + .ml-sm-2, + .mx-sm-2 { + margin-left: 0.5rem !important; + } + .m-sm-3 { + margin: 1rem !important; + } + .mt-sm-3, + .my-sm-3 { + margin-top: 1rem !important; + } + .mr-sm-3, + .mx-sm-3 { + margin-right: 1rem !important; + } + .mb-sm-3, + .my-sm-3 { + margin-bottom: 1rem !important; + } + .ml-sm-3, + .mx-sm-3 { + margin-left: 1rem !important; + } + .m-sm-4 { + margin: 1.5rem !important; + } + .mt-sm-4, + .my-sm-4 { + margin-top: 1.5rem !important; + } + .mr-sm-4, + .mx-sm-4 { + margin-right: 1.5rem !important; + } + .mb-sm-4, + .my-sm-4 { + margin-bottom: 1.5rem !important; + } + .ml-sm-4, + .mx-sm-4 { + margin-left: 1.5rem !important; + } + .m-sm-5 { + margin: 3rem !important; + } + .mt-sm-5, + .my-sm-5 { + margin-top: 3rem !important; + } + .mr-sm-5, + .mx-sm-5 { + margin-right: 3rem !important; + } + .mb-sm-5, + .my-sm-5 { + margin-bottom: 3rem !important; + } + .ml-sm-5, + .mx-sm-5 { + margin-left: 3rem !important; + } + .p-sm-0 { + padding: 0 !important; + } + .pt-sm-0, + .py-sm-0 { + padding-top: 0 !important; + } + .pr-sm-0, + .px-sm-0 { + padding-right: 0 !important; + } + .pb-sm-0, + .py-sm-0 { + padding-bottom: 0 !important; + } + .pl-sm-0, + .px-sm-0 { + padding-left: 0 !important; + } + .p-sm-1 { + padding: 0.25rem !important; + } + .pt-sm-1, + .py-sm-1 { + padding-top: 0.25rem !important; + } + .pr-sm-1, + .px-sm-1 { + padding-right: 0.25rem !important; + } + .pb-sm-1, + .py-sm-1 { + padding-bottom: 0.25rem !important; + } + .pl-sm-1, + .px-sm-1 { + padding-left: 0.25rem !important; + } + .p-sm-2 { + padding: 0.5rem !important; + } + .pt-sm-2, + .py-sm-2 { + padding-top: 0.5rem !important; + } + .pr-sm-2, + .px-sm-2 { + padding-right: 0.5rem !important; + } + .pb-sm-2, + .py-sm-2 { + padding-bottom: 0.5rem !important; + } + .pl-sm-2, + .px-sm-2 { + padding-left: 0.5rem !important; + } + .p-sm-3 { + padding: 1rem !important; + } + .pt-sm-3, + .py-sm-3 { + padding-top: 1rem !important; + } + .pr-sm-3, + .px-sm-3 { + padding-right: 1rem !important; + } + .pb-sm-3, + .py-sm-3 { + padding-bottom: 1rem !important; + } + .pl-sm-3, + .px-sm-3 { + padding-left: 1rem !important; + } + .p-sm-4 { + padding: 1.5rem !important; + } + .pt-sm-4, + .py-sm-4 { + padding-top: 1.5rem !important; + } + .pr-sm-4, + .px-sm-4 { + padding-right: 1.5rem !important; + } + .pb-sm-4, + .py-sm-4 { + padding-bottom: 1.5rem !important; + } + .pl-sm-4, + .px-sm-4 { + padding-left: 1.5rem !important; + } + .p-sm-5 { + padding: 3rem !important; + } + .pt-sm-5, + .py-sm-5 { + padding-top: 3rem !important; + } + .pr-sm-5, + .px-sm-5 { + padding-right: 3rem !important; + } + .pb-sm-5, + .py-sm-5 { + padding-bottom: 3rem !important; + } + .pl-sm-5, + .px-sm-5 { + padding-left: 3rem !important; + } + .m-sm-n1 { + margin: -0.25rem !important; + } + .mt-sm-n1, + .my-sm-n1 { + margin-top: -0.25rem !important; + } + .mr-sm-n1, + .mx-sm-n1 { + margin-right: -0.25rem !important; + } + .mb-sm-n1, + .my-sm-n1 { + margin-bottom: -0.25rem !important; + } + .ml-sm-n1, + .mx-sm-n1 { + margin-left: -0.25rem !important; + } + .m-sm-n2 { + margin: -0.5rem !important; + } + .mt-sm-n2, + .my-sm-n2 { + margin-top: -0.5rem !important; + } + .mr-sm-n2, + .mx-sm-n2 { + margin-right: -0.5rem !important; + } + .mb-sm-n2, + .my-sm-n2 { + margin-bottom: -0.5rem !important; + } + .ml-sm-n2, + .mx-sm-n2 { + margin-left: -0.5rem !important; + } + .m-sm-n3 { + margin: -1rem !important; + } + .mt-sm-n3, + .my-sm-n3 { + margin-top: -1rem !important; + } + .mr-sm-n3, + .mx-sm-n3 { + margin-right: -1rem !important; + } + .mb-sm-n3, + .my-sm-n3 { + margin-bottom: -1rem !important; + } + .ml-sm-n3, + .mx-sm-n3 { + margin-left: -1rem !important; + } + .m-sm-n4 { + margin: -1.5rem !important; + } + .mt-sm-n4, + .my-sm-n4 { + margin-top: -1.5rem !important; + } + .mr-sm-n4, + .mx-sm-n4 { + margin-right: -1.5rem !important; + } + .mb-sm-n4, + .my-sm-n4 { + margin-bottom: -1.5rem !important; + } + .ml-sm-n4, + .mx-sm-n4 { + margin-left: -1.5rem !important; + } + .m-sm-n5 { + margin: -3rem !important; + } + .mt-sm-n5, + .my-sm-n5 { + margin-top: -3rem !important; + } + .mr-sm-n5, + .mx-sm-n5 { + margin-right: -3rem !important; + } + .mb-sm-n5, + .my-sm-n5 { + margin-bottom: -3rem !important; + } + .ml-sm-n5, + .mx-sm-n5 { + margin-left: -3rem !important; + } + .m-sm-auto { + margin: auto !important; + } + .mt-sm-auto, + .my-sm-auto { + margin-top: auto !important; + } + .mr-sm-auto, + .mx-sm-auto { + margin-right: auto !important; + } + .mb-sm-auto, + .my-sm-auto { + margin-bottom: auto !important; + } + .ml-sm-auto, + .mx-sm-auto { + margin-left: auto !important; + } +} + +@media (min-width: 768px) { + .m-md-0 { + margin: 0 !important; + } + .mt-md-0, + .my-md-0 { + margin-top: 0 !important; + } + .mr-md-0, + .mx-md-0 { + margin-right: 0 !important; + } + .mb-md-0, + .my-md-0 { + margin-bottom: 0 !important; + } + .ml-md-0, + .mx-md-0 { + margin-left: 0 !important; + } + .m-md-1 { + margin: 0.25rem !important; + } + .mt-md-1, + .my-md-1 { + margin-top: 0.25rem !important; + } + .mr-md-1, + .mx-md-1 { + margin-right: 0.25rem !important; + } + .mb-md-1, + .my-md-1 { + margin-bottom: 0.25rem !important; + } + .ml-md-1, + .mx-md-1 { + margin-left: 0.25rem !important; + } + .m-md-2 { + margin: 0.5rem !important; + } + .mt-md-2, + .my-md-2 { + margin-top: 0.5rem !important; + } + .mr-md-2, + .mx-md-2 { + margin-right: 0.5rem !important; + } + .mb-md-2, + .my-md-2 { + margin-bottom: 0.5rem !important; + } + .ml-md-2, + .mx-md-2 { + margin-left: 0.5rem !important; + } + .m-md-3 { + margin: 1rem !important; + } + .mt-md-3, + .my-md-3 { + margin-top: 1rem !important; + } + .mr-md-3, + .mx-md-3 { + margin-right: 1rem !important; + } + .mb-md-3, + .my-md-3 { + margin-bottom: 1rem !important; + } + .ml-md-3, + .mx-md-3 { + margin-left: 1rem !important; + } + .m-md-4 { + margin: 1.5rem !important; + } + .mt-md-4, + .my-md-4 { + margin-top: 1.5rem !important; + } + .mr-md-4, + .mx-md-4 { + margin-right: 1.5rem !important; + } + .mb-md-4, + .my-md-4 { + margin-bottom: 1.5rem !important; + } + .ml-md-4, + .mx-md-4 { + margin-left: 1.5rem !important; + } + .m-md-5 { + margin: 3rem !important; + } + .mt-md-5, + .my-md-5 { + margin-top: 3rem !important; + } + .mr-md-5, + .mx-md-5 { + margin-right: 3rem !important; + } + .mb-md-5, + .my-md-5 { + margin-bottom: 3rem !important; + } + .ml-md-5, + .mx-md-5 { + margin-left: 3rem !important; + } + .p-md-0 { + padding: 0 !important; + } + .pt-md-0, + .py-md-0 { + padding-top: 0 !important; + } + .pr-md-0, + .px-md-0 { + padding-right: 0 !important; + } + .pb-md-0, + .py-md-0 { + padding-bottom: 0 !important; + } + .pl-md-0, + .px-md-0 { + padding-left: 0 !important; + } + .p-md-1 { + padding: 0.25rem !important; + } + .pt-md-1, + .py-md-1 { + padding-top: 0.25rem !important; + } + .pr-md-1, + .px-md-1 { + padding-right: 0.25rem !important; + } + .pb-md-1, + .py-md-1 { + padding-bottom: 0.25rem !important; + } + .pl-md-1, + .px-md-1 { + padding-left: 0.25rem !important; + } + .p-md-2 { + padding: 0.5rem !important; + } + .pt-md-2, + .py-md-2 { + padding-top: 0.5rem !important; + } + .pr-md-2, + .px-md-2 { + padding-right: 0.5rem !important; + } + .pb-md-2, + .py-md-2 { + padding-bottom: 0.5rem !important; + } + .pl-md-2, + .px-md-2 { + padding-left: 0.5rem !important; + } + .p-md-3 { + padding: 1rem !important; + } + .pt-md-3, + .py-md-3 { + padding-top: 1rem !important; + } + .pr-md-3, + .px-md-3 { + padding-right: 1rem !important; + } + .pb-md-3, + .py-md-3 { + padding-bottom: 1rem !important; + } + .pl-md-3, + .px-md-3 { + padding-left: 1rem !important; + } + .p-md-4 { + padding: 1.5rem !important; + } + .pt-md-4, + .py-md-4 { + padding-top: 1.5rem !important; + } + .pr-md-4, + .px-md-4 { + padding-right: 1.5rem !important; + } + .pb-md-4, + .py-md-4 { + padding-bottom: 1.5rem !important; + } + .pl-md-4, + .px-md-4 { + padding-left: 1.5rem !important; + } + .p-md-5 { + padding: 3rem !important; + } + .pt-md-5, + .py-md-5 { + padding-top: 3rem !important; + } + .pr-md-5, + .px-md-5 { + padding-right: 3rem !important; + } + .pb-md-5, + .py-md-5 { + padding-bottom: 3rem !important; + } + .pl-md-5, + .px-md-5 { + padding-left: 3rem !important; + } + .m-md-n1 { + margin: -0.25rem !important; + } + .mt-md-n1, + .my-md-n1 { + margin-top: -0.25rem !important; + } + .mr-md-n1, + .mx-md-n1 { + margin-right: -0.25rem !important; + } + .mb-md-n1, + .my-md-n1 { + margin-bottom: -0.25rem !important; + } + .ml-md-n1, + .mx-md-n1 { + margin-left: -0.25rem !important; + } + .m-md-n2 { + margin: -0.5rem !important; + } + .mt-md-n2, + .my-md-n2 { + margin-top: -0.5rem !important; + } + .mr-md-n2, + .mx-md-n2 { + margin-right: -0.5rem !important; + } + .mb-md-n2, + .my-md-n2 { + margin-bottom: -0.5rem !important; + } + .ml-md-n2, + .mx-md-n2 { + margin-left: -0.5rem !important; + } + .m-md-n3 { + margin: -1rem !important; + } + .mt-md-n3, + .my-md-n3 { + margin-top: -1rem !important; + } + .mr-md-n3, + .mx-md-n3 { + margin-right: -1rem !important; + } + .mb-md-n3, + .my-md-n3 { + margin-bottom: -1rem !important; + } + .ml-md-n3, + .mx-md-n3 { + margin-left: -1rem !important; + } + .m-md-n4 { + margin: -1.5rem !important; + } + .mt-md-n4, + .my-md-n4 { + margin-top: -1.5rem !important; + } + .mr-md-n4, + .mx-md-n4 { + margin-right: -1.5rem !important; + } + .mb-md-n4, + .my-md-n4 { + margin-bottom: -1.5rem !important; + } + .ml-md-n4, + .mx-md-n4 { + margin-left: -1.5rem !important; + } + .m-md-n5 { + margin: -3rem !important; + } + .mt-md-n5, + .my-md-n5 { + margin-top: -3rem !important; + } + .mr-md-n5, + .mx-md-n5 { + margin-right: -3rem !important; + } + .mb-md-n5, + .my-md-n5 { + margin-bottom: -3rem !important; + } + .ml-md-n5, + .mx-md-n5 { + margin-left: -3rem !important; + } + .m-md-auto { + margin: auto !important; + } + .mt-md-auto, + .my-md-auto { + margin-top: auto !important; + } + .mr-md-auto, + .mx-md-auto { + margin-right: auto !important; + } + .mb-md-auto, + .my-md-auto { + margin-bottom: auto !important; + } + .ml-md-auto, + .mx-md-auto { + margin-left: auto !important; + } +} + +@media (min-width: 992px) { + .m-lg-0 { + margin: 0 !important; + } + .mt-lg-0, + .my-lg-0 { + margin-top: 0 !important; + } + .mr-lg-0, + .mx-lg-0 { + margin-right: 0 !important; + } + .mb-lg-0, + .my-lg-0 { + margin-bottom: 0 !important; + } + .ml-lg-0, + .mx-lg-0 { + margin-left: 0 !important; + } + .m-lg-1 { + margin: 0.25rem !important; + } + .mt-lg-1, + .my-lg-1 { + margin-top: 0.25rem !important; + } + .mr-lg-1, + .mx-lg-1 { + margin-right: 0.25rem !important; + } + .mb-lg-1, + .my-lg-1 { + margin-bottom: 0.25rem !important; + } + .ml-lg-1, + .mx-lg-1 { + margin-left: 0.25rem !important; + } + .m-lg-2 { + margin: 0.5rem !important; + } + .mt-lg-2, + .my-lg-2 { + margin-top: 0.5rem !important; + } + .mr-lg-2, + .mx-lg-2 { + margin-right: 0.5rem !important; + } + .mb-lg-2, + .my-lg-2 { + margin-bottom: 0.5rem !important; + } + .ml-lg-2, + .mx-lg-2 { + margin-left: 0.5rem !important; + } + .m-lg-3 { + margin: 1rem !important; + } + .mt-lg-3, + .my-lg-3 { + margin-top: 1rem !important; + } + .mr-lg-3, + .mx-lg-3 { + margin-right: 1rem !important; + } + .mb-lg-3, + .my-lg-3 { + margin-bottom: 1rem !important; + } + .ml-lg-3, + .mx-lg-3 { + margin-left: 1rem !important; + } + .m-lg-4 { + margin: 1.5rem !important; + } + .mt-lg-4, + .my-lg-4 { + margin-top: 1.5rem !important; + } + .mr-lg-4, + .mx-lg-4 { + margin-right: 1.5rem !important; + } + .mb-lg-4, + .my-lg-4 { + margin-bottom: 1.5rem !important; + } + .ml-lg-4, + .mx-lg-4 { + margin-left: 1.5rem !important; + } + .m-lg-5 { + margin: 3rem !important; + } + .mt-lg-5, + .my-lg-5 { + margin-top: 3rem !important; + } + .mr-lg-5, + .mx-lg-5 { + margin-right: 3rem !important; + } + .mb-lg-5, + .my-lg-5 { + margin-bottom: 3rem !important; + } + .ml-lg-5, + .mx-lg-5 { + margin-left: 3rem !important; + } + .p-lg-0 { + padding: 0 !important; + } + .pt-lg-0, + .py-lg-0 { + padding-top: 0 !important; + } + .pr-lg-0, + .px-lg-0 { + padding-right: 0 !important; + } + .pb-lg-0, + .py-lg-0 { + padding-bottom: 0 !important; + } + .pl-lg-0, + .px-lg-0 { + padding-left: 0 !important; + } + .p-lg-1 { + padding: 0.25rem !important; + } + .pt-lg-1, + .py-lg-1 { + padding-top: 0.25rem !important; + } + .pr-lg-1, + .px-lg-1 { + padding-right: 0.25rem !important; + } + .pb-lg-1, + .py-lg-1 { + padding-bottom: 0.25rem !important; + } + .pl-lg-1, + .px-lg-1 { + padding-left: 0.25rem !important; + } + .p-lg-2 { + padding: 0.5rem !important; + } + .pt-lg-2, + .py-lg-2 { + padding-top: 0.5rem !important; + } + .pr-lg-2, + .px-lg-2 { + padding-right: 0.5rem !important; + } + .pb-lg-2, + .py-lg-2 { + padding-bottom: 0.5rem !important; + } + .pl-lg-2, + .px-lg-2 { + padding-left: 0.5rem !important; + } + .p-lg-3 { + padding: 1rem !important; + } + .pt-lg-3, + .py-lg-3 { + padding-top: 1rem !important; + } + .pr-lg-3, + .px-lg-3 { + padding-right: 1rem !important; + } + .pb-lg-3, + .py-lg-3 { + padding-bottom: 1rem !important; + } + .pl-lg-3, + .px-lg-3 { + padding-left: 1rem !important; + } + .p-lg-4 { + padding: 1.5rem !important; + } + .pt-lg-4, + .py-lg-4 { + padding-top: 1.5rem !important; + } + .pr-lg-4, + .px-lg-4 { + padding-right: 1.5rem !important; + } + .pb-lg-4, + .py-lg-4 { + padding-bottom: 1.5rem !important; + } + .pl-lg-4, + .px-lg-4 { + padding-left: 1.5rem !important; + } + .p-lg-5 { + padding: 3rem !important; + } + .pt-lg-5, + .py-lg-5 { + padding-top: 3rem !important; + } + .pr-lg-5, + .px-lg-5 { + padding-right: 3rem !important; + } + .pb-lg-5, + .py-lg-5 { + padding-bottom: 3rem !important; + } + .pl-lg-5, + .px-lg-5 { + padding-left: 3rem !important; + } + .m-lg-n1 { + margin: -0.25rem !important; + } + .mt-lg-n1, + .my-lg-n1 { + margin-top: -0.25rem !important; + } + .mr-lg-n1, + .mx-lg-n1 { + margin-right: -0.25rem !important; + } + .mb-lg-n1, + .my-lg-n1 { + margin-bottom: -0.25rem !important; + } + .ml-lg-n1, + .mx-lg-n1 { + margin-left: -0.25rem !important; + } + .m-lg-n2 { + margin: -0.5rem !important; + } + .mt-lg-n2, + .my-lg-n2 { + margin-top: -0.5rem !important; + } + .mr-lg-n2, + .mx-lg-n2 { + margin-right: -0.5rem !important; + } + .mb-lg-n2, + .my-lg-n2 { + margin-bottom: -0.5rem !important; + } + .ml-lg-n2, + .mx-lg-n2 { + margin-left: -0.5rem !important; + } + .m-lg-n3 { + margin: -1rem !important; + } + .mt-lg-n3, + .my-lg-n3 { + margin-top: -1rem !important; + } + .mr-lg-n3, + .mx-lg-n3 { + margin-right: -1rem !important; + } + .mb-lg-n3, + .my-lg-n3 { + margin-bottom: -1rem !important; + } + .ml-lg-n3, + .mx-lg-n3 { + margin-left: -1rem !important; + } + .m-lg-n4 { + margin: -1.5rem !important; + } + .mt-lg-n4, + .my-lg-n4 { + margin-top: -1.5rem !important; + } + .mr-lg-n4, + .mx-lg-n4 { + margin-right: -1.5rem !important; + } + .mb-lg-n4, + .my-lg-n4 { + margin-bottom: -1.5rem !important; + } + .ml-lg-n4, + .mx-lg-n4 { + margin-left: -1.5rem !important; + } + .m-lg-n5 { + margin: -3rem !important; + } + .mt-lg-n5, + .my-lg-n5 { + margin-top: -3rem !important; + } + .mr-lg-n5, + .mx-lg-n5 { + margin-right: -3rem !important; + } + .mb-lg-n5, + .my-lg-n5 { + margin-bottom: -3rem !important; + } + .ml-lg-n5, + .mx-lg-n5 { + margin-left: -3rem !important; + } + .m-lg-auto { + margin: auto !important; + } + .mt-lg-auto, + .my-lg-auto { + margin-top: auto !important; + } + .mr-lg-auto, + .mx-lg-auto { + margin-right: auto !important; + } + .mb-lg-auto, + .my-lg-auto { + margin-bottom: auto !important; + } + .ml-lg-auto, + .mx-lg-auto { + margin-left: auto !important; + } +} + +@media (min-width: 1200px) { + .m-xl-0 { + margin: 0 !important; + } + .mt-xl-0, + .my-xl-0 { + margin-top: 0 !important; + } + .mr-xl-0, + .mx-xl-0 { + margin-right: 0 !important; + } + .mb-xl-0, + .my-xl-0 { + margin-bottom: 0 !important; + } + .ml-xl-0, + .mx-xl-0 { + margin-left: 0 !important; + } + .m-xl-1 { + margin: 0.25rem !important; + } + .mt-xl-1, + .my-xl-1 { + margin-top: 0.25rem !important; + } + .mr-xl-1, + .mx-xl-1 { + margin-right: 0.25rem !important; + } + .mb-xl-1, + .my-xl-1 { + margin-bottom: 0.25rem !important; + } + .ml-xl-1, + .mx-xl-1 { + margin-left: 0.25rem !important; + } + .m-xl-2 { + margin: 0.5rem !important; + } + .mt-xl-2, + .my-xl-2 { + margin-top: 0.5rem !important; + } + .mr-xl-2, + .mx-xl-2 { + margin-right: 0.5rem !important; + } + .mb-xl-2, + .my-xl-2 { + margin-bottom: 0.5rem !important; + } + .ml-xl-2, + .mx-xl-2 { + margin-left: 0.5rem !important; + } + .m-xl-3 { + margin: 1rem !important; + } + .mt-xl-3, + .my-xl-3 { + margin-top: 1rem !important; + } + .mr-xl-3, + .mx-xl-3 { + margin-right: 1rem !important; + } + .mb-xl-3, + .my-xl-3 { + margin-bottom: 1rem !important; + } + .ml-xl-3, + .mx-xl-3 { + margin-left: 1rem !important; + } + .m-xl-4 { + margin: 1.5rem !important; + } + .mt-xl-4, + .my-xl-4 { + margin-top: 1.5rem !important; + } + .mr-xl-4, + .mx-xl-4 { + margin-right: 1.5rem !important; + } + .mb-xl-4, + .my-xl-4 { + margin-bottom: 1.5rem !important; + } + .ml-xl-4, + .mx-xl-4 { + margin-left: 1.5rem !important; + } + .m-xl-5 { + margin: 3rem !important; + } + .mt-xl-5, + .my-xl-5 { + margin-top: 3rem !important; + } + .mr-xl-5, + .mx-xl-5 { + margin-right: 3rem !important; + } + .mb-xl-5, + .my-xl-5 { + margin-bottom: 3rem !important; + } + .ml-xl-5, + .mx-xl-5 { + margin-left: 3rem !important; + } + .p-xl-0 { + padding: 0 !important; + } + .pt-xl-0, + .py-xl-0 { + padding-top: 0 !important; + } + .pr-xl-0, + .px-xl-0 { + padding-right: 0 !important; + } + .pb-xl-0, + .py-xl-0 { + padding-bottom: 0 !important; + } + .pl-xl-0, + .px-xl-0 { + padding-left: 0 !important; + } + .p-xl-1 { + padding: 0.25rem !important; + } + .pt-xl-1, + .py-xl-1 { + padding-top: 0.25rem !important; + } + .pr-xl-1, + .px-xl-1 { + padding-right: 0.25rem !important; + } + .pb-xl-1, + .py-xl-1 { + padding-bottom: 0.25rem !important; + } + .pl-xl-1, + .px-xl-1 { + padding-left: 0.25rem !important; + } + .p-xl-2 { + padding: 0.5rem !important; + } + .pt-xl-2, + .py-xl-2 { + padding-top: 0.5rem !important; + } + .pr-xl-2, + .px-xl-2 { + padding-right: 0.5rem !important; + } + .pb-xl-2, + .py-xl-2 { + padding-bottom: 0.5rem !important; + } + .pl-xl-2, + .px-xl-2 { + padding-left: 0.5rem !important; + } + .p-xl-3 { + padding: 1rem !important; + } + .pt-xl-3, + .py-xl-3 { + padding-top: 1rem !important; + } + .pr-xl-3, + .px-xl-3 { + padding-right: 1rem !important; + } + .pb-xl-3, + .py-xl-3 { + padding-bottom: 1rem !important; + } + .pl-xl-3, + .px-xl-3 { + padding-left: 1rem !important; + } + .p-xl-4 { + padding: 1.5rem !important; + } + .pt-xl-4, + .py-xl-4 { + padding-top: 1.5rem !important; + } + .pr-xl-4, + .px-xl-4 { + padding-right: 1.5rem !important; + } + .pb-xl-4, + .py-xl-4 { + padding-bottom: 1.5rem !important; + } + .pl-xl-4, + .px-xl-4 { + padding-left: 1.5rem !important; + } + .p-xl-5 { + padding: 3rem !important; + } + .pt-xl-5, + .py-xl-5 { + padding-top: 3rem !important; + } + .pr-xl-5, + .px-xl-5 { + padding-right: 3rem !important; + } + .pb-xl-5, + .py-xl-5 { + padding-bottom: 3rem !important; + } + .pl-xl-5, + .px-xl-5 { + padding-left: 3rem !important; + } + .m-xl-n1 { + margin: -0.25rem !important; + } + .mt-xl-n1, + .my-xl-n1 { + margin-top: -0.25rem !important; + } + .mr-xl-n1, + .mx-xl-n1 { + margin-right: -0.25rem !important; + } + .mb-xl-n1, + .my-xl-n1 { + margin-bottom: -0.25rem !important; + } + .ml-xl-n1, + .mx-xl-n1 { + margin-left: -0.25rem !important; + } + .m-xl-n2 { + margin: -0.5rem !important; + } + .mt-xl-n2, + .my-xl-n2 { + margin-top: -0.5rem !important; + } + .mr-xl-n2, + .mx-xl-n2 { + margin-right: -0.5rem !important; + } + .mb-xl-n2, + .my-xl-n2 { + margin-bottom: -0.5rem !important; + } + .ml-xl-n2, + .mx-xl-n2 { + margin-left: -0.5rem !important; + } + .m-xl-n3 { + margin: -1rem !important; + } + .mt-xl-n3, + .my-xl-n3 { + margin-top: -1rem !important; + } + .mr-xl-n3, + .mx-xl-n3 { + margin-right: -1rem !important; + } + .mb-xl-n3, + .my-xl-n3 { + margin-bottom: -1rem !important; + } + .ml-xl-n3, + .mx-xl-n3 { + margin-left: -1rem !important; + } + .m-xl-n4 { + margin: -1.5rem !important; + } + .mt-xl-n4, + .my-xl-n4 { + margin-top: -1.5rem !important; + } + .mr-xl-n4, + .mx-xl-n4 { + margin-right: -1.5rem !important; + } + .mb-xl-n4, + .my-xl-n4 { + margin-bottom: -1.5rem !important; + } + .ml-xl-n4, + .mx-xl-n4 { + margin-left: -1.5rem !important; + } + .m-xl-n5 { + margin: -3rem !important; + } + .mt-xl-n5, + .my-xl-n5 { + margin-top: -3rem !important; + } + .mr-xl-n5, + .mx-xl-n5 { + margin-right: -3rem !important; + } + .mb-xl-n5, + .my-xl-n5 { + margin-bottom: -3rem !important; + } + .ml-xl-n5, + .mx-xl-n5 { + margin-left: -3rem !important; + } + .m-xl-auto { + margin: auto !important; + } + .mt-xl-auto, + .my-xl-auto { + margin-top: auto !important; + } + .mr-xl-auto, + .mx-xl-auto { + margin-right: auto !important; + } + .mb-xl-auto, + .my-xl-auto { + margin-bottom: auto !important; + } + .ml-xl-auto, + .mx-xl-auto { + margin-left: auto !important; + } +} +/*# sourceMappingURL=bootstrap-grid.css.map */ \ No newline at end of file diff --git a/webgui/legacy/css/bootstrap.css b/webgui/legacy/css/bootstrap.css new file mode 100644 index 0000000..e9e0474 --- /dev/null +++ b/webgui/legacy/css/bootstrap.css @@ -0,0 +1,10225 @@ +/*! + * Bootstrap v4.4.1 (https://getbootstrap.com/) + * Copyright 2011-2019 The Bootstrap Authors + * Copyright 2011-2019 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +:root { + --blue: #007bff; + --indigo: #6610f2; + --purple: #6f42c1; + --pink: #e83e8c; + --red: #dc3545; + --orange: #fd7e14; + --yellow: #ffc107; + --green: #28a745; + --teal: #20c997; + --cyan: #17a2b8; + --white: #fff; + --gray: #6c757d; + --gray-dark: #343a40; + --primary: #007bff; + --secondary: #6c757d; + --success: #28a745; + --info: #17a2b8; + --warning: #ffc107; + --danger: #dc3545; + --light: #f8f9fa; + --dark: #343a40; + --breakpoint-xs: 0; + --breakpoint-sm: 576px; + --breakpoint-md: 768px; + --breakpoint-lg: 992px; + --breakpoint-xl: 1200px; + --font-family-sans-serif: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + --font-family-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; +} + +*, +*::before, +*::after { + box-sizing: border-box; +} + +html { + font-family: sans-serif; + line-height: 1.15; + -webkit-text-size-adjust: 100%; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +article, aside, figcaption, figure, footer, header, hgroup, main, nav, section { + display: block; +} + +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #212529; + text-align: left; + background-color: #fff; +} + +[tabindex="-1"]:focus:not(:focus-visible) { + outline: 0 !important; +} + +hr { + box-sizing: content-box; + height: 0; + overflow: visible; +} + +h1, h2, h3, h4, h5, h6 { + margin-top: 0; + margin-bottom: 0.5rem; +} + +p { + margin-top: 0; + margin-bottom: 1rem; +} + +abbr[title], +abbr[data-original-title] { + text-decoration: underline; + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; + cursor: help; + border-bottom: 0; + -webkit-text-decoration-skip-ink: none; + text-decoration-skip-ink: none; +} + +address { + margin-bottom: 1rem; + font-style: normal; + line-height: inherit; +} + +ol, +ul, +dl { + margin-top: 0; + margin-bottom: 1rem; +} + +ol ol, +ul ul, +ol ul, +ul ol { + margin-bottom: 0; +} + +dt { + font-weight: 700; +} + +dd { + margin-bottom: .5rem; + margin-left: 0; +} + +blockquote { + margin: 0 0 1rem; +} + +b, +strong { + font-weight: bolder; +} + +small { + font-size: 80%; +} + +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} + +sub { + bottom: -.25em; +} + +sup { + top: -.5em; +} + +a { + color: #007bff; + text-decoration: none; + background-color: transparent; +} + +a:hover { + color: #0056b3; + text-decoration: underline; +} + +a:not([href]) { + color: inherit; + text-decoration: none; +} + +a:not([href]):hover { + color: inherit; + text-decoration: none; +} + +pre, +code, +kbd, +samp { + font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + font-size: 1em; +} + +pre { + margin-top: 0; + margin-bottom: 1rem; + overflow: auto; +} + +figure { + margin: 0 0 1rem; +} + +img { + vertical-align: middle; + border-style: none; +} + +svg { + overflow: hidden; + vertical-align: middle; +} + +table { + border-collapse: collapse; +} + +caption { + padding-top: 0.75rem; + padding-bottom: 0.75rem; + color: #6c757d; + text-align: left; + caption-side: bottom; +} + +th { + text-align: inherit; +} + +label { + display: inline-block; + margin-bottom: 0.5rem; +} + +button { + border-radius: 0; +} + +button:focus { + outline: 1px dotted; + outline: 5px auto -webkit-focus-ring-color; +} + +input, +button, +select, +optgroup, +textarea { + margin: 0; + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +button, +input { + overflow: visible; +} + +button, +select { + text-transform: none; +} + +select { + word-wrap: normal; +} + +button, +[type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; +} + +button:not(:disabled), +[type="button"]:not(:disabled), +[type="reset"]:not(:disabled), +[type="submit"]:not(:disabled) { + cursor: pointer; +} + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + padding: 0; + border-style: none; +} + +input[type="radio"], +input[type="checkbox"] { + box-sizing: border-box; + padding: 0; +} + +input[type="date"], +input[type="time"], +input[type="datetime-local"], +input[type="month"] { + -webkit-appearance: listbox; +} + +textarea { + overflow: auto; + resize: vertical; +} + +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; +} + +legend { + display: block; + width: 100%; + max-width: 100%; + padding: 0; + margin-bottom: .5rem; + font-size: 1.5rem; + line-height: inherit; + color: inherit; + white-space: normal; +} + +progress { + vertical-align: baseline; +} + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +[type="search"] { + outline-offset: -2px; + -webkit-appearance: none; +} + +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +::-webkit-file-upload-button { + font: inherit; + -webkit-appearance: button; +} + +output { + display: inline-block; +} + +summary { + display: list-item; + cursor: pointer; +} + +template { + display: none; +} + +[hidden] { + display: none !important; +} + +h1, h2, h3, h4, h5, h6, +.h1, .h2, .h3, .h4, .h5, .h6 { + margin-bottom: 0.5rem; + font-weight: 500; + line-height: 1.2; +} + +h1, .h1 { + font-size: 2.5rem; +} + +h2, .h2 { + font-size: 2rem; +} + +h3, .h3 { + font-size: 1.75rem; +} + +h4, .h4 { + font-size: 1.5rem; +} + +h5, .h5 { + font-size: 1.25rem; +} + +h6, .h6 { + font-size: 1rem; +} + +.lead { + font-size: 1.25rem; + font-weight: 300; +} + +.display-1 { + font-size: 6rem; + font-weight: 300; + line-height: 1.2; +} + +.display-2 { + font-size: 5.5rem; + font-weight: 300; + line-height: 1.2; +} + +.display-3 { + font-size: 4.5rem; + font-weight: 300; + line-height: 1.2; +} + +.display-4 { + font-size: 3.5rem; + font-weight: 300; + line-height: 1.2; +} + +hr { + margin-top: 1rem; + margin-bottom: 1rem; + border: 0; + border-top: 1px solid rgba(0, 0, 0, 0.1); +} + +small, +.small { + font-size: 80%; + font-weight: 400; +} + +mark, +.mark { + padding: 0.2em; + background-color: #fcf8e3; +} + +.list-unstyled { + padding-left: 0; + list-style: none; +} + +.list-inline { + padding-left: 0; + list-style: none; +} + +.list-inline-item { + display: inline-block; +} + +.list-inline-item:not(:last-child) { + margin-right: 0.5rem; +} + +.initialism { + font-size: 90%; + text-transform: uppercase; +} + +.blockquote { + margin-bottom: 1rem; + font-size: 1.25rem; +} + +.blockquote-footer { + display: block; + font-size: 80%; + color: #6c757d; +} + +.blockquote-footer::before { + content: "\2014\00A0"; +} + +.img-fluid { + max-width: 100%; + height: auto; +} + +.img-thumbnail { + padding: 0.25rem; + background-color: #fff; + border: 1px solid #dee2e6; + border-radius: 0.25rem; + max-width: 100%; + height: auto; +} + +.figure { + display: inline-block; +} + +.figure-img { + margin-bottom: 0.5rem; + line-height: 1; +} + +.figure-caption { + font-size: 90%; + color: #6c757d; +} + +code { + font-size: 87.5%; + color: #e83e8c; + word-wrap: break-word; +} + +a > code { + color: inherit; +} + +kbd { + padding: 0.2rem 0.4rem; + font-size: 87.5%; + color: #fff; + background-color: #212529; + border-radius: 0.2rem; +} + +kbd kbd { + padding: 0; + font-size: 100%; + font-weight: 700; +} + +pre { + display: block; + font-size: 87.5%; + color: #212529; +} + +pre code { + font-size: inherit; + color: inherit; + word-break: normal; +} + +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} + +.container { + width: 100%; + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} + +@media (min-width: 576px) { + .container { + max-width: 540px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 720px; + } +} + +@media (min-width: 992px) { + .container { + max-width: 960px; + } +} + +@media (min-width: 1200px) { + .container { + max-width: 1140px; + } +} + +.container-fluid, .container-sm, .container-md, .container-lg, .container-xl { + width: 100%; + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} + +@media (min-width: 576px) { + .container, .container-sm { + max-width: 540px; + } +} + +@media (min-width: 768px) { + .container, .container-sm, .container-md { + max-width: 720px; + } +} + +@media (min-width: 992px) { + .container, .container-sm, .container-md, .container-lg { + max-width: 960px; + } +} + +@media (min-width: 1200px) { + .container, .container-sm, .container-md, .container-lg, .container-xl { + max-width: 1140px; + } +} + +.row { + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + margin-right: -15px; + margin-left: -15px; +} + +.no-gutters { + margin-right: 0; + margin-left: 0; +} + +.no-gutters > .col, +.no-gutters > [class*="col-"] { + padding-right: 0; + padding-left: 0; +} + +.col-1, .col-2, .col-3, .col-4, .col-5, .col-6, .col-7, .col-8, .col-9, .col-10, .col-11, .col-12, .col, +.col-auto, .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12, .col-sm, +.col-sm-auto, .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12, .col-md, +.col-md-auto, .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12, .col-lg, +.col-lg-auto, .col-xl-1, .col-xl-2, .col-xl-3, .col-xl-4, .col-xl-5, .col-xl-6, .col-xl-7, .col-xl-8, .col-xl-9, .col-xl-10, .col-xl-11, .col-xl-12, .col-xl, +.col-xl-auto { + position: relative; + width: 100%; + padding-right: 15px; + padding-left: 15px; +} + +.col { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; +} + +.row-cols-1 > * { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; +} + +.row-cols-2 > * { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; +} + +.row-cols-3 > * { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; +} + +.row-cols-4 > * { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; +} + +.row-cols-5 > * { + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20%; +} + +.row-cols-6 > * { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; +} + +.col-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: 100%; +} + +.col-1 { + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; +} + +.col-2 { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; +} + +.col-3 { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; +} + +.col-4 { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; +} + +.col-5 { + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; +} + +.col-6 { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; +} + +.col-7 { + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; +} + +.col-8 { + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; +} + +.col-9 { + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; +} + +.col-10 { + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; +} + +.col-11 { + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; +} + +.col-12 { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; +} + +.order-first { + -ms-flex-order: -1; + order: -1; +} + +.order-last { + -ms-flex-order: 13; + order: 13; +} + +.order-0 { + -ms-flex-order: 0; + order: 0; +} + +.order-1 { + -ms-flex-order: 1; + order: 1; +} + +.order-2 { + -ms-flex-order: 2; + order: 2; +} + +.order-3 { + -ms-flex-order: 3; + order: 3; +} + +.order-4 { + -ms-flex-order: 4; + order: 4; +} + +.order-5 { + -ms-flex-order: 5; + order: 5; +} + +.order-6 { + -ms-flex-order: 6; + order: 6; +} + +.order-7 { + -ms-flex-order: 7; + order: 7; +} + +.order-8 { + -ms-flex-order: 8; + order: 8; +} + +.order-9 { + -ms-flex-order: 9; + order: 9; +} + +.order-10 { + -ms-flex-order: 10; + order: 10; +} + +.order-11 { + -ms-flex-order: 11; + order: 11; +} + +.order-12 { + -ms-flex-order: 12; + order: 12; +} + +.offset-1 { + margin-left: 8.333333%; +} + +.offset-2 { + margin-left: 16.666667%; +} + +.offset-3 { + margin-left: 25%; +} + +.offset-4 { + margin-left: 33.333333%; +} + +.offset-5 { + margin-left: 41.666667%; +} + +.offset-6 { + margin-left: 50%; +} + +.offset-7 { + margin-left: 58.333333%; +} + +.offset-8 { + margin-left: 66.666667%; +} + +.offset-9 { + margin-left: 75%; +} + +.offset-10 { + margin-left: 83.333333%; +} + +.offset-11 { + margin-left: 91.666667%; +} + +@media (min-width: 576px) { + .col-sm { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .row-cols-sm-1 > * { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .row-cols-sm-2 > * { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .row-cols-sm-3 > * { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .row-cols-sm-4 > * { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .row-cols-sm-5 > * { + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20%; + } + .row-cols-sm-6 > * { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-sm-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: 100%; + } + .col-sm-1 { + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; + } + .col-sm-2 { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-sm-3 { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-sm-4 { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .col-sm-5 { + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; + } + .col-sm-6 { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-sm-7 { + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; + } + .col-sm-8 { + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; + } + .col-sm-9 { + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-sm-10 { + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; + } + .col-sm-11 { + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; + } + .col-sm-12 { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-sm-first { + -ms-flex-order: -1; + order: -1; + } + .order-sm-last { + -ms-flex-order: 13; + order: 13; + } + .order-sm-0 { + -ms-flex-order: 0; + order: 0; + } + .order-sm-1 { + -ms-flex-order: 1; + order: 1; + } + .order-sm-2 { + -ms-flex-order: 2; + order: 2; + } + .order-sm-3 { + -ms-flex-order: 3; + order: 3; + } + .order-sm-4 { + -ms-flex-order: 4; + order: 4; + } + .order-sm-5 { + -ms-flex-order: 5; + order: 5; + } + .order-sm-6 { + -ms-flex-order: 6; + order: 6; + } + .order-sm-7 { + -ms-flex-order: 7; + order: 7; + } + .order-sm-8 { + -ms-flex-order: 8; + order: 8; + } + .order-sm-9 { + -ms-flex-order: 9; + order: 9; + } + .order-sm-10 { + -ms-flex-order: 10; + order: 10; + } + .order-sm-11 { + -ms-flex-order: 11; + order: 11; + } + .order-sm-12 { + -ms-flex-order: 12; + order: 12; + } + .offset-sm-0 { + margin-left: 0; + } + .offset-sm-1 { + margin-left: 8.333333%; + } + .offset-sm-2 { + margin-left: 16.666667%; + } + .offset-sm-3 { + margin-left: 25%; + } + .offset-sm-4 { + margin-left: 33.333333%; + } + .offset-sm-5 { + margin-left: 41.666667%; + } + .offset-sm-6 { + margin-left: 50%; + } + .offset-sm-7 { + margin-left: 58.333333%; + } + .offset-sm-8 { + margin-left: 66.666667%; + } + .offset-sm-9 { + margin-left: 75%; + } + .offset-sm-10 { + margin-left: 83.333333%; + } + .offset-sm-11 { + margin-left: 91.666667%; + } +} + +@media (min-width: 768px) { + .col-md { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .row-cols-md-1 > * { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .row-cols-md-2 > * { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .row-cols-md-3 > * { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .row-cols-md-4 > * { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .row-cols-md-5 > * { + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20%; + } + .row-cols-md-6 > * { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-md-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: 100%; + } + .col-md-1 { + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; + } + .col-md-2 { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-md-3 { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-md-4 { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .col-md-5 { + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; + } + .col-md-6 { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-md-7 { + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; + } + .col-md-8 { + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; + } + .col-md-9 { + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-md-10 { + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; + } + .col-md-11 { + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; + } + .col-md-12 { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-md-first { + -ms-flex-order: -1; + order: -1; + } + .order-md-last { + -ms-flex-order: 13; + order: 13; + } + .order-md-0 { + -ms-flex-order: 0; + order: 0; + } + .order-md-1 { + -ms-flex-order: 1; + order: 1; + } + .order-md-2 { + -ms-flex-order: 2; + order: 2; + } + .order-md-3 { + -ms-flex-order: 3; + order: 3; + } + .order-md-4 { + -ms-flex-order: 4; + order: 4; + } + .order-md-5 { + -ms-flex-order: 5; + order: 5; + } + .order-md-6 { + -ms-flex-order: 6; + order: 6; + } + .order-md-7 { + -ms-flex-order: 7; + order: 7; + } + .order-md-8 { + -ms-flex-order: 8; + order: 8; + } + .order-md-9 { + -ms-flex-order: 9; + order: 9; + } + .order-md-10 { + -ms-flex-order: 10; + order: 10; + } + .order-md-11 { + -ms-flex-order: 11; + order: 11; + } + .order-md-12 { + -ms-flex-order: 12; + order: 12; + } + .offset-md-0 { + margin-left: 0; + } + .offset-md-1 { + margin-left: 8.333333%; + } + .offset-md-2 { + margin-left: 16.666667%; + } + .offset-md-3 { + margin-left: 25%; + } + .offset-md-4 { + margin-left: 33.333333%; + } + .offset-md-5 { + margin-left: 41.666667%; + } + .offset-md-6 { + margin-left: 50%; + } + .offset-md-7 { + margin-left: 58.333333%; + } + .offset-md-8 { + margin-left: 66.666667%; + } + .offset-md-9 { + margin-left: 75%; + } + .offset-md-10 { + margin-left: 83.333333%; + } + .offset-md-11 { + margin-left: 91.666667%; + } +} + +@media (min-width: 992px) { + .col-lg { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .row-cols-lg-1 > * { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .row-cols-lg-2 > * { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .row-cols-lg-3 > * { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .row-cols-lg-4 > * { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .row-cols-lg-5 > * { + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20%; + } + .row-cols-lg-6 > * { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-lg-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: 100%; + } + .col-lg-1 { + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; + } + .col-lg-2 { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-lg-3 { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-lg-4 { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .col-lg-5 { + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; + } + .col-lg-6 { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-lg-7 { + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; + } + .col-lg-8 { + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; + } + .col-lg-9 { + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-lg-10 { + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; + } + .col-lg-11 { + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; + } + .col-lg-12 { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-lg-first { + -ms-flex-order: -1; + order: -1; + } + .order-lg-last { + -ms-flex-order: 13; + order: 13; + } + .order-lg-0 { + -ms-flex-order: 0; + order: 0; + } + .order-lg-1 { + -ms-flex-order: 1; + order: 1; + } + .order-lg-2 { + -ms-flex-order: 2; + order: 2; + } + .order-lg-3 { + -ms-flex-order: 3; + order: 3; + } + .order-lg-4 { + -ms-flex-order: 4; + order: 4; + } + .order-lg-5 { + -ms-flex-order: 5; + order: 5; + } + .order-lg-6 { + -ms-flex-order: 6; + order: 6; + } + .order-lg-7 { + -ms-flex-order: 7; + order: 7; + } + .order-lg-8 { + -ms-flex-order: 8; + order: 8; + } + .order-lg-9 { + -ms-flex-order: 9; + order: 9; + } + .order-lg-10 { + -ms-flex-order: 10; + order: 10; + } + .order-lg-11 { + -ms-flex-order: 11; + order: 11; + } + .order-lg-12 { + -ms-flex-order: 12; + order: 12; + } + .offset-lg-0 { + margin-left: 0; + } + .offset-lg-1 { + margin-left: 8.333333%; + } + .offset-lg-2 { + margin-left: 16.666667%; + } + .offset-lg-3 { + margin-left: 25%; + } + .offset-lg-4 { + margin-left: 33.333333%; + } + .offset-lg-5 { + margin-left: 41.666667%; + } + .offset-lg-6 { + margin-left: 50%; + } + .offset-lg-7 { + margin-left: 58.333333%; + } + .offset-lg-8 { + margin-left: 66.666667%; + } + .offset-lg-9 { + margin-left: 75%; + } + .offset-lg-10 { + margin-left: 83.333333%; + } + .offset-lg-11 { + margin-left: 91.666667%; + } +} + +@media (min-width: 1200px) { + .col-xl { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + max-width: 100%; + } + .row-cols-xl-1 > * { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .row-cols-xl-2 > * { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .row-cols-xl-3 > * { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .row-cols-xl-4 > * { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .row-cols-xl-5 > * { + -ms-flex: 0 0 20%; + flex: 0 0 20%; + max-width: 20%; + } + .row-cols-xl-6 > * { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-xl-auto { + -ms-flex: 0 0 auto; + flex: 0 0 auto; + width: auto; + max-width: 100%; + } + .col-xl-1 { + -ms-flex: 0 0 8.333333%; + flex: 0 0 8.333333%; + max-width: 8.333333%; + } + .col-xl-2 { + -ms-flex: 0 0 16.666667%; + flex: 0 0 16.666667%; + max-width: 16.666667%; + } + .col-xl-3 { + -ms-flex: 0 0 25%; + flex: 0 0 25%; + max-width: 25%; + } + .col-xl-4 { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + .col-xl-5 { + -ms-flex: 0 0 41.666667%; + flex: 0 0 41.666667%; + max-width: 41.666667%; + } + .col-xl-6 { + -ms-flex: 0 0 50%; + flex: 0 0 50%; + max-width: 50%; + } + .col-xl-7 { + -ms-flex: 0 0 58.333333%; + flex: 0 0 58.333333%; + max-width: 58.333333%; + } + .col-xl-8 { + -ms-flex: 0 0 66.666667%; + flex: 0 0 66.666667%; + max-width: 66.666667%; + } + .col-xl-9 { + -ms-flex: 0 0 75%; + flex: 0 0 75%; + max-width: 75%; + } + .col-xl-10 { + -ms-flex: 0 0 83.333333%; + flex: 0 0 83.333333%; + max-width: 83.333333%; + } + .col-xl-11 { + -ms-flex: 0 0 91.666667%; + flex: 0 0 91.666667%; + max-width: 91.666667%; + } + .col-xl-12 { + -ms-flex: 0 0 100%; + flex: 0 0 100%; + max-width: 100%; + } + .order-xl-first { + -ms-flex-order: -1; + order: -1; + } + .order-xl-last { + -ms-flex-order: 13; + order: 13; + } + .order-xl-0 { + -ms-flex-order: 0; + order: 0; + } + .order-xl-1 { + -ms-flex-order: 1; + order: 1; + } + .order-xl-2 { + -ms-flex-order: 2; + order: 2; + } + .order-xl-3 { + -ms-flex-order: 3; + order: 3; + } + .order-xl-4 { + -ms-flex-order: 4; + order: 4; + } + .order-xl-5 { + -ms-flex-order: 5; + order: 5; + } + .order-xl-6 { + -ms-flex-order: 6; + order: 6; + } + .order-xl-7 { + -ms-flex-order: 7; + order: 7; + } + .order-xl-8 { + -ms-flex-order: 8; + order: 8; + } + .order-xl-9 { + -ms-flex-order: 9; + order: 9; + } + .order-xl-10 { + -ms-flex-order: 10; + order: 10; + } + .order-xl-11 { + -ms-flex-order: 11; + order: 11; + } + .order-xl-12 { + -ms-flex-order: 12; + order: 12; + } + .offset-xl-0 { + margin-left: 0; + } + .offset-xl-1 { + margin-left: 8.333333%; + } + .offset-xl-2 { + margin-left: 16.666667%; + } + .offset-xl-3 { + margin-left: 25%; + } + .offset-xl-4 { + margin-left: 33.333333%; + } + .offset-xl-5 { + margin-left: 41.666667%; + } + .offset-xl-6 { + margin-left: 50%; + } + .offset-xl-7 { + margin-left: 58.333333%; + } + .offset-xl-8 { + margin-left: 66.666667%; + } + .offset-xl-9 { + margin-left: 75%; + } + .offset-xl-10 { + margin-left: 83.333333%; + } + .offset-xl-11 { + margin-left: 91.666667%; + } +} + +.table { + width: 100%; + margin-bottom: 1rem; + color: #212529; +} + +.table th, +.table td { + padding: 0.75rem; + vertical-align: top; + border-top: 1px solid #dee2e6; +} + +.table thead th { + vertical-align: bottom; + border-bottom: 2px solid #dee2e6; +} + +.table tbody + tbody { + border-top: 2px solid #dee2e6; +} + +.table-sm th, +.table-sm td { + padding: 0.3rem; +} + +.table-bordered { + border: 1px solid #dee2e6; +} + +.table-bordered th, +.table-bordered td { + border: 1px solid #dee2e6; +} + +.table-bordered thead th, +.table-bordered thead td { + border-bottom-width: 2px; +} + +.table-borderless th, +.table-borderless td, +.table-borderless thead th, +.table-borderless tbody + tbody { + border: 0; +} + +.table-striped tbody tr:nth-of-type(odd) { + background-color: rgba(0, 0, 0, 0.05); +} + +.table-hover tbody tr:hover { + color: #212529; + background-color: rgba(0, 0, 0, 0.075); +} + +.table-primary, +.table-primary > th, +.table-primary > td { + background-color: #b8daff; +} + +.table-primary th, +.table-primary td, +.table-primary thead th, +.table-primary tbody + tbody { + border-color: #7abaff; +} + +.table-hover .table-primary:hover { + background-color: #9fcdff; +} + +.table-hover .table-primary:hover > td, +.table-hover .table-primary:hover > th { + background-color: #9fcdff; +} + +.table-secondary, +.table-secondary > th, +.table-secondary > td { + background-color: #d6d8db; +} + +.table-secondary th, +.table-secondary td, +.table-secondary thead th, +.table-secondary tbody + tbody { + border-color: #b3b7bb; +} + +.table-hover .table-secondary:hover { + background-color: #c8cbcf; +} + +.table-hover .table-secondary:hover > td, +.table-hover .table-secondary:hover > th { + background-color: #c8cbcf; +} + +.table-success, +.table-success > th, +.table-success > td { + background-color: #c3e6cb; +} + +.table-success th, +.table-success td, +.table-success thead th, +.table-success tbody + tbody { + border-color: #8fd19e; +} + +.table-hover .table-success:hover { + background-color: #b1dfbb; +} + +.table-hover .table-success:hover > td, +.table-hover .table-success:hover > th { + background-color: #b1dfbb; +} + +.table-info, +.table-info > th, +.table-info > td { + background-color: #bee5eb; +} + +.table-info th, +.table-info td, +.table-info thead th, +.table-info tbody + tbody { + border-color: #86cfda; +} + +.table-hover .table-info:hover { + background-color: #abdde5; +} + +.table-hover .table-info:hover > td, +.table-hover .table-info:hover > th { + background-color: #abdde5; +} + +.table-warning, +.table-warning > th, +.table-warning > td { + background-color: #ffeeba; +} + +.table-warning th, +.table-warning td, +.table-warning thead th, +.table-warning tbody + tbody { + border-color: #ffdf7e; +} + +.table-hover .table-warning:hover { + background-color: #ffe8a1; +} + +.table-hover .table-warning:hover > td, +.table-hover .table-warning:hover > th { + background-color: #ffe8a1; +} + +.table-danger, +.table-danger > th, +.table-danger > td { + background-color: #f5c6cb; +} + +.table-danger th, +.table-danger td, +.table-danger thead th, +.table-danger tbody + tbody { + border-color: #ed969e; +} + +.table-hover .table-danger:hover { + background-color: #f1b0b7; +} + +.table-hover .table-danger:hover > td, +.table-hover .table-danger:hover > th { + background-color: #f1b0b7; +} + +.table-light, +.table-light > th, +.table-light > td { + background-color: #fdfdfe; +} + +.table-light th, +.table-light td, +.table-light thead th, +.table-light tbody + tbody { + border-color: #fbfcfc; +} + +.table-hover .table-light:hover { + background-color: #ececf6; +} + +.table-hover .table-light:hover > td, +.table-hover .table-light:hover > th { + background-color: #ececf6; +} + +.table-dark, +.table-dark > th, +.table-dark > td { + background-color: #c6c8ca; +} + +.table-dark th, +.table-dark td, +.table-dark thead th, +.table-dark tbody + tbody { + border-color: #95999c; +} + +.table-hover .table-dark:hover { + background-color: #b9bbbe; +} + +.table-hover .table-dark:hover > td, +.table-hover .table-dark:hover > th { + background-color: #b9bbbe; +} + +.table-active, +.table-active > th, +.table-active > td { + background-color: rgba(0, 0, 0, 0.075); +} + +.table-hover .table-active:hover { + background-color: rgba(0, 0, 0, 0.075); +} + +.table-hover .table-active:hover > td, +.table-hover .table-active:hover > th { + background-color: rgba(0, 0, 0, 0.075); +} + +.table .thead-dark th { + color: #fff; + background-color: #343a40; + border-color: #454d55; +} + +.table .thead-light th { + color: #495057; + background-color: #e9ecef; + border-color: #dee2e6; +} + +.table-dark { + color: #fff; + background-color: #343a40; +} + +.table-dark th, +.table-dark td, +.table-dark thead th { + border-color: #454d55; +} + +.table-dark.table-bordered { + border: 0; +} + +.table-dark.table-striped tbody tr:nth-of-type(odd) { + background-color: rgba(255, 255, 255, 0.05); +} + +.table-dark.table-hover tbody tr:hover { + color: #fff; + background-color: rgba(255, 255, 255, 0.075); +} + +@media (max-width: 575.98px) { + .table-responsive-sm { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + .table-responsive-sm > .table-bordered { + border: 0; + } +} + +@media (max-width: 767.98px) { + .table-responsive-md { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + .table-responsive-md > .table-bordered { + border: 0; + } +} + +@media (max-width: 991.98px) { + .table-responsive-lg { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + .table-responsive-lg > .table-bordered { + border: 0; + } +} + +@media (max-width: 1199.98px) { + .table-responsive-xl { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; + } + .table-responsive-xl > .table-bordered { + border: 0; + } +} + +.table-responsive { + display: block; + width: 100%; + overflow-x: auto; + -webkit-overflow-scrolling: touch; +} + +.table-responsive > .table-bordered { + border: 0; +} + +.form-control { + display: block; + width: 100%; + height: calc(1.5em + 0.75rem + 2px); + padding: 0.375rem 0.75rem; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #495057; + background-color: #fff; + background-clip: padding-box; + border: 1px solid #ced4da; + border-radius: 0.25rem; + transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} + +@media (prefers-reduced-motion: reduce) { + .form-control { + transition: none; + } +} + +.form-control::-ms-expand { + background-color: transparent; + border: 0; +} + +.form-control:-moz-focusring { + color: transparent; + text-shadow: 0 0 0 #495057; +} + +.form-control:focus { + color: #495057; + background-color: #fff; + border-color: #80bdff; + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} + +.form-control::-webkit-input-placeholder { + color: #6c757d; + opacity: 1; +} + +.form-control::-moz-placeholder { + color: #6c757d; + opacity: 1; +} + +.form-control:-ms-input-placeholder { + color: #6c757d; + opacity: 1; +} + +.form-control::-ms-input-placeholder { + color: #6c757d; + opacity: 1; +} + +.form-control::placeholder { + color: #6c757d; + opacity: 1; +} + +.form-control:disabled, .form-control[readonly] { + background-color: #e9ecef; + opacity: 1; +} + +select.form-control:focus::-ms-value { + color: #495057; + background-color: #fff; +} + +.form-control-file, +.form-control-range { + display: block; + width: 100%; +} + +.col-form-label { + padding-top: calc(0.375rem + 1px); + padding-bottom: calc(0.375rem + 1px); + margin-bottom: 0; + font-size: inherit; + line-height: 1.5; +} + +.col-form-label-lg { + padding-top: calc(0.5rem + 1px); + padding-bottom: calc(0.5rem + 1px); + font-size: 1.25rem; + line-height: 1.5; +} + +.col-form-label-sm { + padding-top: calc(0.25rem + 1px); + padding-bottom: calc(0.25rem + 1px); + font-size: 0.875rem; + line-height: 1.5; +} + +.form-control-plaintext { + display: block; + width: 100%; + padding: 0.375rem 0; + margin-bottom: 0; + font-size: 1rem; + line-height: 1.5; + color: #212529; + background-color: transparent; + border: solid transparent; + border-width: 1px 0; +} + +.form-control-plaintext.form-control-sm, .form-control-plaintext.form-control-lg { + padding-right: 0; + padding-left: 0; +} + +.form-control-sm { + height: calc(1.5em + 0.5rem + 2px); + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; + border-radius: 0.2rem; +} + +.form-control-lg { + height: calc(1.5em + 1rem + 2px); + padding: 0.5rem 1rem; + font-size: 1.25rem; + line-height: 1.5; + border-radius: 0.3rem; +} + +select.form-control[size], select.form-control[multiple] { + height: auto; +} + +textarea.form-control { + height: auto; +} + +.form-group { + margin-bottom: 1rem; +} + +.form-text { + display: block; + margin-top: 0.25rem; +} + +.form-row { + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + margin-right: -5px; + margin-left: -5px; +} + +.form-row > .col, +.form-row > [class*="col-"] { + padding-right: 5px; + padding-left: 5px; +} + +.form-check { + position: relative; + display: block; + padding-left: 1.25rem; +} + +.form-check-input { + position: absolute; + margin-top: 0.3rem; + margin-left: -1.25rem; +} + +.form-check-input[disabled] ~ .form-check-label, +.form-check-input:disabled ~ .form-check-label { + color: #6c757d; +} + +.form-check-label { + margin-bottom: 0; +} + +.form-check-inline { + display: -ms-inline-flexbox; + display: inline-flex; + -ms-flex-align: center; + align-items: center; + padding-left: 0; + margin-right: 0.75rem; +} + +.form-check-inline .form-check-input { + position: static; + margin-top: 0; + margin-right: 0.3125rem; + margin-left: 0; +} + +.valid-feedback { + display: none; + width: 100%; + margin-top: 0.25rem; + font-size: 80%; + color: #28a745; +} + +.valid-tooltip { + position: absolute; + top: 100%; + z-index: 5; + display: none; + max-width: 100%; + padding: 0.25rem 0.5rem; + margin-top: .1rem; + font-size: 0.875rem; + line-height: 1.5; + color: #fff; + background-color: rgba(40, 167, 69, 0.9); + border-radius: 0.25rem; +} + +.was-validated :valid ~ .valid-feedback, +.was-validated :valid ~ .valid-tooltip, +.is-valid ~ .valid-feedback, +.is-valid ~ .valid-tooltip { + display: block; +} + +.was-validated .form-control:valid, .form-control.is-valid { + border-color: #28a745; + padding-right: calc(1.5em + 0.75rem); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right calc(0.375em + 0.1875rem) center; + background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); +} + +.was-validated .form-control:valid:focus, .form-control.is-valid:focus { + border-color: #28a745; + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); +} + +.was-validated textarea.form-control:valid, textarea.form-control.is-valid { + padding-right: calc(1.5em + 0.75rem); + background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem); +} + +.was-validated .custom-select:valid, .custom-select.is-valid { + border-color: #28a745; + padding-right: calc(0.75em + 2.3125rem); + background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px, url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%2328a745' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); +} + +.was-validated .custom-select:valid:focus, .custom-select.is-valid:focus { + border-color: #28a745; + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); +} + +.was-validated .form-check-input:valid ~ .form-check-label, .form-check-input.is-valid ~ .form-check-label { + color: #28a745; +} + +.was-validated .form-check-input:valid ~ .valid-feedback, +.was-validated .form-check-input:valid ~ .valid-tooltip, .form-check-input.is-valid ~ .valid-feedback, +.form-check-input.is-valid ~ .valid-tooltip { + display: block; +} + +.was-validated .custom-control-input:valid ~ .custom-control-label, .custom-control-input.is-valid ~ .custom-control-label { + color: #28a745; +} + +.was-validated .custom-control-input:valid ~ .custom-control-label::before, .custom-control-input.is-valid ~ .custom-control-label::before { + border-color: #28a745; +} + +.was-validated .custom-control-input:valid:checked ~ .custom-control-label::before, .custom-control-input.is-valid:checked ~ .custom-control-label::before { + border-color: #34ce57; + background-color: #34ce57; +} + +.was-validated .custom-control-input:valid:focus ~ .custom-control-label::before, .custom-control-input.is-valid:focus ~ .custom-control-label::before { + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); +} + +.was-validated .custom-control-input:valid:focus:not(:checked) ~ .custom-control-label::before, .custom-control-input.is-valid:focus:not(:checked) ~ .custom-control-label::before { + border-color: #28a745; +} + +.was-validated .custom-file-input:valid ~ .custom-file-label, .custom-file-input.is-valid ~ .custom-file-label { + border-color: #28a745; +} + +.was-validated .custom-file-input:valid:focus ~ .custom-file-label, .custom-file-input.is-valid:focus ~ .custom-file-label { + border-color: #28a745; + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.25); +} + +.invalid-feedback { + display: none; + width: 100%; + margin-top: 0.25rem; + font-size: 80%; + color: #dc3545; +} + +.invalid-tooltip { + position: absolute; + top: 100%; + z-index: 5; + display: none; + max-width: 100%; + padding: 0.25rem 0.5rem; + margin-top: .1rem; + font-size: 0.875rem; + line-height: 1.5; + color: #fff; + background-color: rgba(220, 53, 69, 0.9); + border-radius: 0.25rem; +} + +.was-validated :invalid ~ .invalid-feedback, +.was-validated :invalid ~ .invalid-tooltip, +.is-invalid ~ .invalid-feedback, +.is-invalid ~ .invalid-tooltip { + display: block; +} + +.was-validated .form-control:invalid, .form-control.is-invalid { + border-color: #dc3545; + padding-right: calc(1.5em + 0.75rem); + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e"); + background-repeat: no-repeat; + background-position: right calc(0.375em + 0.1875rem) center; + background-size: calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); +} + +.was-validated .form-control:invalid:focus, .form-control.is-invalid:focus { + border-color: #dc3545; + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); +} + +.was-validated textarea.form-control:invalid, textarea.form-control.is-invalid { + padding-right: calc(1.5em + 0.75rem); + background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem); +} + +.was-validated .custom-select:invalid, .custom-select.is-invalid { + border-color: #dc3545; + padding-right: calc(0.75em + 2.3125rem); + background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px, url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='none' stroke='%23dc3545' viewBox='0 0 12 12'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e") #fff no-repeat center right 1.75rem/calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); +} + +.was-validated .custom-select:invalid:focus, .custom-select.is-invalid:focus { + border-color: #dc3545; + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); +} + +.was-validated .form-check-input:invalid ~ .form-check-label, .form-check-input.is-invalid ~ .form-check-label { + color: #dc3545; +} + +.was-validated .form-check-input:invalid ~ .invalid-feedback, +.was-validated .form-check-input:invalid ~ .invalid-tooltip, .form-check-input.is-invalid ~ .invalid-feedback, +.form-check-input.is-invalid ~ .invalid-tooltip { + display: block; +} + +.was-validated .custom-control-input:invalid ~ .custom-control-label, .custom-control-input.is-invalid ~ .custom-control-label { + color: #dc3545; +} + +.was-validated .custom-control-input:invalid ~ .custom-control-label::before, .custom-control-input.is-invalid ~ .custom-control-label::before { + border-color: #dc3545; +} + +.was-validated .custom-control-input:invalid:checked ~ .custom-control-label::before, .custom-control-input.is-invalid:checked ~ .custom-control-label::before { + border-color: #e4606d; + background-color: #e4606d; +} + +.was-validated .custom-control-input:invalid:focus ~ .custom-control-label::before, .custom-control-input.is-invalid:focus ~ .custom-control-label::before { + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); +} + +.was-validated .custom-control-input:invalid:focus:not(:checked) ~ .custom-control-label::before, .custom-control-input.is-invalid:focus:not(:checked) ~ .custom-control-label::before { + border-color: #dc3545; +} + +.was-validated .custom-file-input:invalid ~ .custom-file-label, .custom-file-input.is-invalid ~ .custom-file-label { + border-color: #dc3545; +} + +.was-validated .custom-file-input:invalid:focus ~ .custom-file-label, .custom-file-input.is-invalid:focus ~ .custom-file-label { + border-color: #dc3545; + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); +} + +.form-inline { + display: -ms-flexbox; + display: flex; + -ms-flex-flow: row wrap; + flex-flow: row wrap; + -ms-flex-align: center; + align-items: center; +} + +.form-inline .form-check { + width: 100%; +} + +@media (min-width: 576px) { + .form-inline label { + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; + -ms-flex-pack: center; + justify-content: center; + margin-bottom: 0; + } + .form-inline .form-group { + display: -ms-flexbox; + display: flex; + -ms-flex: 0 0 auto; + flex: 0 0 auto; + -ms-flex-flow: row wrap; + flex-flow: row wrap; + -ms-flex-align: center; + align-items: center; + margin-bottom: 0; + } + .form-inline .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .form-inline .form-control-plaintext { + display: inline-block; + } + .form-inline .input-group, + .form-inline .custom-select { + width: auto; + } + .form-inline .form-check { + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; + -ms-flex-pack: center; + justify-content: center; + width: auto; + padding-left: 0; + } + .form-inline .form-check-input { + position: relative; + -ms-flex-negative: 0; + flex-shrink: 0; + margin-top: 0; + margin-right: 0.25rem; + margin-left: 0; + } + .form-inline .custom-control { + -ms-flex-align: center; + align-items: center; + -ms-flex-pack: center; + justify-content: center; + } + .form-inline .custom-control-label { + margin-bottom: 0; + } +} + +.btn { + display: inline-block; + font-weight: 400; + color: #212529; + text-align: center; + vertical-align: middle; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-color: transparent; + border: 1px solid transparent; + padding: 0.375rem 0.75rem; + font-size: 1rem; + line-height: 1.5; + border-radius: 0.25rem; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} + +@media (prefers-reduced-motion: reduce) { + .btn { + transition: none; + } +} + +.btn:hover { + color: #212529; + text-decoration: none; +} + +.btn:focus, .btn.focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} + +.btn.disabled, .btn:disabled { + opacity: 0.65; +} + +a.btn.disabled, +fieldset:disabled a.btn { + pointer-events: none; +} + +.btn-primary { + color: #fff; + background-color: #007bff; + border-color: #007bff; +} + +.btn-primary:hover { + color: #fff; + background-color: #0069d9; + border-color: #0062cc; +} + +.btn-primary:focus, .btn-primary.focus { + color: #fff; + background-color: #0069d9; + border-color: #0062cc; + box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.5); +} + +.btn-primary.disabled, .btn-primary:disabled { + color: #fff; + background-color: #007bff; + border-color: #007bff; +} + +.btn-primary:not(:disabled):not(.disabled):active, .btn-primary:not(:disabled):not(.disabled).active, +.show > .btn-primary.dropdown-toggle { + color: #fff; + background-color: #0062cc; + border-color: #005cbf; +} + +.btn-primary:not(:disabled):not(.disabled):active:focus, .btn-primary:not(:disabled):not(.disabled).active:focus, +.show > .btn-primary.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(38, 143, 255, 0.5); +} + +.btn-secondary { + color: #fff; + background-color: #6c757d; + border-color: #6c757d; +} + +.btn-secondary:hover { + color: #fff; + background-color: #5a6268; + border-color: #545b62; +} + +.btn-secondary:focus, .btn-secondary.focus { + color: #fff; + background-color: #5a6268; + border-color: #545b62; + box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5); +} + +.btn-secondary.disabled, .btn-secondary:disabled { + color: #fff; + background-color: #6c757d; + border-color: #6c757d; +} + +.btn-secondary:not(:disabled):not(.disabled):active, .btn-secondary:not(:disabled):not(.disabled).active, +.show > .btn-secondary.dropdown-toggle { + color: #fff; + background-color: #545b62; + border-color: #4e555b; +} + +.btn-secondary:not(:disabled):not(.disabled):active:focus, .btn-secondary:not(:disabled):not(.disabled).active:focus, +.show > .btn-secondary.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(130, 138, 145, 0.5); +} + +.btn-success { + color: #fff; + background-color: #28a745; + border-color: #28a745; +} + +.btn-success:hover { + color: #fff; + background-color: #218838; + border-color: #1e7e34; +} + +.btn-success:focus, .btn-success.focus { + color: #fff; + background-color: #218838; + border-color: #1e7e34; + box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5); +} + +.btn-success.disabled, .btn-success:disabled { + color: #fff; + background-color: #28a745; + border-color: #28a745; +} + +.btn-success:not(:disabled):not(.disabled):active, .btn-success:not(:disabled):not(.disabled).active, +.show > .btn-success.dropdown-toggle { + color: #fff; + background-color: #1e7e34; + border-color: #1c7430; +} + +.btn-success:not(:disabled):not(.disabled):active:focus, .btn-success:not(:disabled):not(.disabled).active:focus, +.show > .btn-success.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(72, 180, 97, 0.5); +} + +.btn-info { + color: #fff; + background-color: #17a2b8; + border-color: #17a2b8; +} + +.btn-info:hover { + color: #fff; + background-color: #138496; + border-color: #117a8b; +} + +.btn-info:focus, .btn-info.focus { + color: #fff; + background-color: #138496; + border-color: #117a8b; + box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5); +} + +.btn-info.disabled, .btn-info:disabled { + color: #fff; + background-color: #17a2b8; + border-color: #17a2b8; +} + +.btn-info:not(:disabled):not(.disabled):active, .btn-info:not(:disabled):not(.disabled).active, +.show > .btn-info.dropdown-toggle { + color: #fff; + background-color: #117a8b; + border-color: #10707f; +} + +.btn-info:not(:disabled):not(.disabled):active:focus, .btn-info:not(:disabled):not(.disabled).active:focus, +.show > .btn-info.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(58, 176, 195, 0.5); +} + +.btn-warning { + color: #212529; + background-color: #ffc107; + border-color: #ffc107; +} + +.btn-warning:hover { + color: #212529; + background-color: #e0a800; + border-color: #d39e00; +} + +.btn-warning:focus, .btn-warning.focus { + color: #212529; + background-color: #e0a800; + border-color: #d39e00; + box-shadow: 0 0 0 0.2rem rgba(222, 170, 12, 0.5); +} + +.btn-warning.disabled, .btn-warning:disabled { + color: #212529; + background-color: #ffc107; + border-color: #ffc107; +} + +.btn-warning:not(:disabled):not(.disabled):active, .btn-warning:not(:disabled):not(.disabled).active, +.show > .btn-warning.dropdown-toggle { + color: #212529; + background-color: #d39e00; + border-color: #c69500; +} + +.btn-warning:not(:disabled):not(.disabled):active:focus, .btn-warning:not(:disabled):not(.disabled).active:focus, +.show > .btn-warning.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(222, 170, 12, 0.5); +} + +.btn-danger { + color: #fff; + background-color: #dc3545; + border-color: #dc3545; +} + +.btn-danger:hover { + color: #fff; + background-color: #c82333; + border-color: #bd2130; +} + +.btn-danger:focus, .btn-danger.focus { + color: #fff; + background-color: #c82333; + border-color: #bd2130; + box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5); +} + +.btn-danger.disabled, .btn-danger:disabled { + color: #fff; + background-color: #dc3545; + border-color: #dc3545; +} + +.btn-danger:not(:disabled):not(.disabled):active, .btn-danger:not(:disabled):not(.disabled).active, +.show > .btn-danger.dropdown-toggle { + color: #fff; + background-color: #bd2130; + border-color: #b21f2d; +} + +.btn-danger:not(:disabled):not(.disabled):active:focus, .btn-danger:not(:disabled):not(.disabled).active:focus, +.show > .btn-danger.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(225, 83, 97, 0.5); +} + +.btn-light { + color: #212529; + background-color: #f8f9fa; + border-color: #f8f9fa; +} + +.btn-light:hover { + color: #212529; + background-color: #e2e6ea; + border-color: #dae0e5; +} + +.btn-light:focus, .btn-light.focus { + color: #212529; + background-color: #e2e6ea; + border-color: #dae0e5; + box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5); +} + +.btn-light.disabled, .btn-light:disabled { + color: #212529; + background-color: #f8f9fa; + border-color: #f8f9fa; +} + +.btn-light:not(:disabled):not(.disabled):active, .btn-light:not(:disabled):not(.disabled).active, +.show > .btn-light.dropdown-toggle { + color: #212529; + background-color: #dae0e5; + border-color: #d3d9df; +} + +.btn-light:not(:disabled):not(.disabled):active:focus, .btn-light:not(:disabled):not(.disabled).active:focus, +.show > .btn-light.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(216, 217, 219, 0.5); +} + +.btn-dark { + color: #fff; + background-color: #343a40; + border-color: #343a40; +} + +.btn-dark:hover { + color: #fff; + background-color: #23272b; + border-color: #1d2124; +} + +.btn-dark:focus, .btn-dark.focus { + color: #fff; + background-color: #23272b; + border-color: #1d2124; + box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5); +} + +.btn-dark.disabled, .btn-dark:disabled { + color: #fff; + background-color: #343a40; + border-color: #343a40; +} + +.btn-dark:not(:disabled):not(.disabled):active, .btn-dark:not(:disabled):not(.disabled).active, +.show > .btn-dark.dropdown-toggle { + color: #fff; + background-color: #1d2124; + border-color: #171a1d; +} + +.btn-dark:not(:disabled):not(.disabled):active:focus, .btn-dark:not(:disabled):not(.disabled).active:focus, +.show > .btn-dark.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(82, 88, 93, 0.5); +} + +.btn-outline-primary { + color: #007bff; + border-color: #007bff; +} + +.btn-outline-primary:hover { + color: #fff; + background-color: #007bff; + border-color: #007bff; +} + +.btn-outline-primary:focus, .btn-outline-primary.focus { + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); +} + +.btn-outline-primary.disabled, .btn-outline-primary:disabled { + color: #007bff; + background-color: transparent; +} + +.btn-outline-primary:not(:disabled):not(.disabled):active, .btn-outline-primary:not(:disabled):not(.disabled).active, +.show > .btn-outline-primary.dropdown-toggle { + color: #fff; + background-color: #007bff; + border-color: #007bff; +} + +.btn-outline-primary:not(:disabled):not(.disabled):active:focus, .btn-outline-primary:not(:disabled):not(.disabled).active:focus, +.show > .btn-outline-primary.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); +} + +.btn-outline-secondary { + color: #6c757d; + border-color: #6c757d; +} + +.btn-outline-secondary:hover { + color: #fff; + background-color: #6c757d; + border-color: #6c757d; +} + +.btn-outline-secondary:focus, .btn-outline-secondary.focus { + box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); +} + +.btn-outline-secondary.disabled, .btn-outline-secondary:disabled { + color: #6c757d; + background-color: transparent; +} + +.btn-outline-secondary:not(:disabled):not(.disabled):active, .btn-outline-secondary:not(:disabled):not(.disabled).active, +.show > .btn-outline-secondary.dropdown-toggle { + color: #fff; + background-color: #6c757d; + border-color: #6c757d; +} + +.btn-outline-secondary:not(:disabled):not(.disabled):active:focus, .btn-outline-secondary:not(:disabled):not(.disabled).active:focus, +.show > .btn-outline-secondary.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); +} + +.btn-outline-success { + color: #28a745; + border-color: #28a745; +} + +.btn-outline-success:hover { + color: #fff; + background-color: #28a745; + border-color: #28a745; +} + +.btn-outline-success:focus, .btn-outline-success.focus { + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); +} + +.btn-outline-success.disabled, .btn-outline-success:disabled { + color: #28a745; + background-color: transparent; +} + +.btn-outline-success:not(:disabled):not(.disabled):active, .btn-outline-success:not(:disabled):not(.disabled).active, +.show > .btn-outline-success.dropdown-toggle { + color: #fff; + background-color: #28a745; + border-color: #28a745; +} + +.btn-outline-success:not(:disabled):not(.disabled):active:focus, .btn-outline-success:not(:disabled):not(.disabled).active:focus, +.show > .btn-outline-success.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); +} + +.btn-outline-info { + color: #17a2b8; + border-color: #17a2b8; +} + +.btn-outline-info:hover { + color: #fff; + background-color: #17a2b8; + border-color: #17a2b8; +} + +.btn-outline-info:focus, .btn-outline-info.focus { + box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); +} + +.btn-outline-info.disabled, .btn-outline-info:disabled { + color: #17a2b8; + background-color: transparent; +} + +.btn-outline-info:not(:disabled):not(.disabled):active, .btn-outline-info:not(:disabled):not(.disabled).active, +.show > .btn-outline-info.dropdown-toggle { + color: #fff; + background-color: #17a2b8; + border-color: #17a2b8; +} + +.btn-outline-info:not(:disabled):not(.disabled):active:focus, .btn-outline-info:not(:disabled):not(.disabled).active:focus, +.show > .btn-outline-info.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); +} + +.btn-outline-warning { + color: #ffc107; + border-color: #ffc107; +} + +.btn-outline-warning:hover { + color: #212529; + background-color: #ffc107; + border-color: #ffc107; +} + +.btn-outline-warning:focus, .btn-outline-warning.focus { + box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); +} + +.btn-outline-warning.disabled, .btn-outline-warning:disabled { + color: #ffc107; + background-color: transparent; +} + +.btn-outline-warning:not(:disabled):not(.disabled):active, .btn-outline-warning:not(:disabled):not(.disabled).active, +.show > .btn-outline-warning.dropdown-toggle { + color: #212529; + background-color: #ffc107; + border-color: #ffc107; +} + +.btn-outline-warning:not(:disabled):not(.disabled):active:focus, .btn-outline-warning:not(:disabled):not(.disabled).active:focus, +.show > .btn-outline-warning.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); +} + +.btn-outline-danger { + color: #dc3545; + border-color: #dc3545; +} + +.btn-outline-danger:hover { + color: #fff; + background-color: #dc3545; + border-color: #dc3545; +} + +.btn-outline-danger:focus, .btn-outline-danger.focus { + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); +} + +.btn-outline-danger.disabled, .btn-outline-danger:disabled { + color: #dc3545; + background-color: transparent; +} + +.btn-outline-danger:not(:disabled):not(.disabled):active, .btn-outline-danger:not(:disabled):not(.disabled).active, +.show > .btn-outline-danger.dropdown-toggle { + color: #fff; + background-color: #dc3545; + border-color: #dc3545; +} + +.btn-outline-danger:not(:disabled):not(.disabled):active:focus, .btn-outline-danger:not(:disabled):not(.disabled).active:focus, +.show > .btn-outline-danger.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); +} + +.btn-outline-light { + color: #f8f9fa; + border-color: #f8f9fa; +} + +.btn-outline-light:hover { + color: #212529; + background-color: #f8f9fa; + border-color: #f8f9fa; +} + +.btn-outline-light:focus, .btn-outline-light.focus { + box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); +} + +.btn-outline-light.disabled, .btn-outline-light:disabled { + color: #f8f9fa; + background-color: transparent; +} + +.btn-outline-light:not(:disabled):not(.disabled):active, .btn-outline-light:not(:disabled):not(.disabled).active, +.show > .btn-outline-light.dropdown-toggle { + color: #212529; + background-color: #f8f9fa; + border-color: #f8f9fa; +} + +.btn-outline-light:not(:disabled):not(.disabled):active:focus, .btn-outline-light:not(:disabled):not(.disabled).active:focus, +.show > .btn-outline-light.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); +} + +.btn-outline-dark { + color: #343a40; + border-color: #343a40; +} + +.btn-outline-dark:hover { + color: #fff; + background-color: #343a40; + border-color: #343a40; +} + +.btn-outline-dark:focus, .btn-outline-dark.focus { + box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); +} + +.btn-outline-dark.disabled, .btn-outline-dark:disabled { + color: #343a40; + background-color: transparent; +} + +.btn-outline-dark:not(:disabled):not(.disabled):active, .btn-outline-dark:not(:disabled):not(.disabled).active, +.show > .btn-outline-dark.dropdown-toggle { + color: #fff; + background-color: #343a40; + border-color: #343a40; +} + +.btn-outline-dark:not(:disabled):not(.disabled):active:focus, .btn-outline-dark:not(:disabled):not(.disabled).active:focus, +.show > .btn-outline-dark.dropdown-toggle:focus { + box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); +} + +.btn-link { + font-weight: 400; + color: #007bff; + text-decoration: none; +} + +.btn-link:hover { + color: #0056b3; + text-decoration: underline; +} + +.btn-link:focus, .btn-link.focus { + text-decoration: underline; + box-shadow: none; +} + +.btn-link:disabled, .btn-link.disabled { + color: #6c757d; + pointer-events: none; +} + +.btn-lg, .btn-group-lg > .btn { + padding: 0.5rem 1rem; + font-size: 1.25rem; + line-height: 1.5; + border-radius: 0.3rem; +} + +.btn-sm, .btn-group-sm > .btn { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; + border-radius: 0.2rem; +} + +.btn-block { + display: block; + width: 100%; +} + +.btn-block + .btn-block { + margin-top: 0.5rem; +} + +input[type="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].btn-block { + width: 100%; +} + +.fade { + transition: opacity 0.15s linear; +} + +@media (prefers-reduced-motion: reduce) { + .fade { + transition: none; + } +} + +.fade:not(.show) { + opacity: 0; +} + +.collapse:not(.show) { + display: none; +} + +.collapsing { + position: relative; + height: 0; + overflow: hidden; + transition: height 0.35s ease; +} + +@media (prefers-reduced-motion: reduce) { + .collapsing { + transition: none; + } +} + +.dropup, +.dropright, +.dropdown, +.dropleft { + position: relative; +} + +.dropdown-toggle { + white-space: nowrap; +} + +.dropdown-toggle::after { + display: inline-block; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid; + border-right: 0.3em solid transparent; + border-bottom: 0; + border-left: 0.3em solid transparent; +} + +.dropdown-toggle:empty::after { + margin-left: 0; +} + +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 10rem; + padding: 0.5rem 0; + margin: 0.125rem 0 0; + font-size: 1rem; + color: #212529; + text-align: left; + list-style: none; + background-color: #fff; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 0.25rem; +} + +.dropdown-menu-left { + right: auto; + left: 0; +} + +.dropdown-menu-right { + right: 0; + left: auto; +} + +@media (min-width: 576px) { + .dropdown-menu-sm-left { + right: auto; + left: 0; + } + .dropdown-menu-sm-right { + right: 0; + left: auto; + } +} + +@media (min-width: 768px) { + .dropdown-menu-md-left { + right: auto; + left: 0; + } + .dropdown-menu-md-right { + right: 0; + left: auto; + } +} + +@media (min-width: 992px) { + .dropdown-menu-lg-left { + right: auto; + left: 0; + } + .dropdown-menu-lg-right { + right: 0; + left: auto; + } +} + +@media (min-width: 1200px) { + .dropdown-menu-xl-left { + right: auto; + left: 0; + } + .dropdown-menu-xl-right { + right: 0; + left: auto; + } +} + +.dropup .dropdown-menu { + top: auto; + bottom: 100%; + margin-top: 0; + margin-bottom: 0.125rem; +} + +.dropup .dropdown-toggle::after { + display: inline-block; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0; + border-right: 0.3em solid transparent; + border-bottom: 0.3em solid; + border-left: 0.3em solid transparent; +} + +.dropup .dropdown-toggle:empty::after { + margin-left: 0; +} + +.dropright .dropdown-menu { + top: 0; + right: auto; + left: 100%; + margin-top: 0; + margin-left: 0.125rem; +} + +.dropright .dropdown-toggle::after { + display: inline-block; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid transparent; + border-right: 0; + border-bottom: 0.3em solid transparent; + border-left: 0.3em solid; +} + +.dropright .dropdown-toggle:empty::after { + margin-left: 0; +} + +.dropright .dropdown-toggle::after { + vertical-align: 0; +} + +.dropleft .dropdown-menu { + top: 0; + right: 100%; + left: auto; + margin-top: 0; + margin-right: 0.125rem; +} + +.dropleft .dropdown-toggle::after { + display: inline-block; + margin-left: 0.255em; + vertical-align: 0.255em; + content: ""; +} + +.dropleft .dropdown-toggle::after { + display: none; +} + +.dropleft .dropdown-toggle::before { + display: inline-block; + margin-right: 0.255em; + vertical-align: 0.255em; + content: ""; + border-top: 0.3em solid transparent; + border-right: 0.3em solid; + border-bottom: 0.3em solid transparent; +} + +.dropleft .dropdown-toggle:empty::after { + margin-left: 0; +} + +.dropleft .dropdown-toggle::before { + vertical-align: 0; +} + +.dropdown-menu[x-placement^="top"], .dropdown-menu[x-placement^="right"], .dropdown-menu[x-placement^="bottom"], .dropdown-menu[x-placement^="left"] { + right: auto; + bottom: auto; +} + +.dropdown-divider { + height: 0; + margin: 0.5rem 0; + overflow: hidden; + border-top: 1px solid #e9ecef; +} + +.dropdown-item { + display: block; + width: 100%; + padding: 0.25rem 1.5rem; + clear: both; + font-weight: 400; + color: #212529; + text-align: inherit; + white-space: nowrap; + background-color: transparent; + border: 0; +} + +.dropdown-item:hover, .dropdown-item:focus { + color: #16181b; + text-decoration: none; + background-color: #f8f9fa; +} + +.dropdown-item.active, .dropdown-item:active { + color: #fff; + text-decoration: none; + background-color: #007bff; +} + +.dropdown-item.disabled, .dropdown-item:disabled { + color: #6c757d; + pointer-events: none; + background-color: transparent; +} + +.dropdown-menu.show { + display: block; +} + +.dropdown-header { + display: block; + padding: 0.5rem 1.5rem; + margin-bottom: 0; + font-size: 0.875rem; + color: #6c757d; + white-space: nowrap; +} + +.dropdown-item-text { + display: block; + padding: 0.25rem 1.5rem; + color: #212529; +} + +.btn-group, +.btn-group-vertical { + position: relative; + display: -ms-inline-flexbox; + display: inline-flex; + vertical-align: middle; +} + +.btn-group > .btn, +.btn-group-vertical > .btn { + position: relative; + -ms-flex: 1 1 auto; + flex: 1 1 auto; +} + +.btn-group > .btn:hover, +.btn-group-vertical > .btn:hover { + z-index: 1; +} + +.btn-group > .btn:focus, .btn-group > .btn:active, .btn-group > .btn.active, +.btn-group-vertical > .btn:focus, +.btn-group-vertical > .btn:active, +.btn-group-vertical > .btn.active { + z-index: 1; +} + +.btn-toolbar { + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.btn-toolbar .input-group { + width: auto; +} + +.btn-group > .btn:not(:first-child), +.btn-group > .btn-group:not(:first-child) { + margin-left: -1px; +} + +.btn-group > .btn:not(:last-child):not(.dropdown-toggle), +.btn-group > .btn-group:not(:last-child) > .btn { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.btn-group > .btn:not(:first-child), +.btn-group > .btn-group:not(:first-child) > .btn { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +.dropdown-toggle-split { + padding-right: 0.5625rem; + padding-left: 0.5625rem; +} + +.dropdown-toggle-split::after, +.dropup .dropdown-toggle-split::after, +.dropright .dropdown-toggle-split::after { + margin-left: 0; +} + +.dropleft .dropdown-toggle-split::before { + margin-right: 0; +} + +.btn-sm + .dropdown-toggle-split, .btn-group-sm > .btn + .dropdown-toggle-split { + padding-right: 0.375rem; + padding-left: 0.375rem; +} + +.btn-lg + .dropdown-toggle-split, .btn-group-lg > .btn + .dropdown-toggle-split { + padding-right: 0.75rem; + padding-left: 0.75rem; +} + +.btn-group-vertical { + -ms-flex-direction: column; + flex-direction: column; + -ms-flex-align: start; + align-items: flex-start; + -ms-flex-pack: center; + justify-content: center; +} + +.btn-group-vertical > .btn, +.btn-group-vertical > .btn-group { + width: 100%; +} + +.btn-group-vertical > .btn:not(:first-child), +.btn-group-vertical > .btn-group:not(:first-child) { + margin-top: -1px; +} + +.btn-group-vertical > .btn:not(:last-child):not(.dropdown-toggle), +.btn-group-vertical > .btn-group:not(:last-child) > .btn { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} + +.btn-group-vertical > .btn:not(:first-child), +.btn-group-vertical > .btn-group:not(:first-child) > .btn { + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.btn-group-toggle > .btn, +.btn-group-toggle > .btn-group > .btn { + margin-bottom: 0; +} + +.btn-group-toggle > .btn input[type="radio"], +.btn-group-toggle > .btn input[type="checkbox"], +.btn-group-toggle > .btn-group > .btn input[type="radio"], +.btn-group-toggle > .btn-group > .btn input[type="checkbox"] { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none; +} + +.input-group { + position: relative; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -ms-flex-align: stretch; + align-items: stretch; + width: 100%; +} + +.input-group > .form-control, +.input-group > .form-control-plaintext, +.input-group > .custom-select, +.input-group > .custom-file { + position: relative; + -ms-flex: 1 1 0%; + flex: 1 1 0%; + min-width: 0; + margin-bottom: 0; +} + +.input-group > .form-control + .form-control, +.input-group > .form-control + .custom-select, +.input-group > .form-control + .custom-file, +.input-group > .form-control-plaintext + .form-control, +.input-group > .form-control-plaintext + .custom-select, +.input-group > .form-control-plaintext + .custom-file, +.input-group > .custom-select + .form-control, +.input-group > .custom-select + .custom-select, +.input-group > .custom-select + .custom-file, +.input-group > .custom-file + .form-control, +.input-group > .custom-file + .custom-select, +.input-group > .custom-file + .custom-file { + margin-left: -1px; +} + +.input-group > .form-control:focus, +.input-group > .custom-select:focus, +.input-group > .custom-file .custom-file-input:focus ~ .custom-file-label { + z-index: 3; +} + +.input-group > .custom-file .custom-file-input:focus { + z-index: 4; +} + +.input-group > .form-control:not(:last-child), +.input-group > .custom-select:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.input-group > .form-control:not(:first-child), +.input-group > .custom-select:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +.input-group > .custom-file { + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; +} + +.input-group > .custom-file:not(:last-child) .custom-file-label, +.input-group > .custom-file:not(:last-child) .custom-file-label::after { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.input-group > .custom-file:not(:first-child) .custom-file-label { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +.input-group-prepend, +.input-group-append { + display: -ms-flexbox; + display: flex; +} + +.input-group-prepend .btn, +.input-group-append .btn { + position: relative; + z-index: 2; +} + +.input-group-prepend .btn:focus, +.input-group-append .btn:focus { + z-index: 3; +} + +.input-group-prepend .btn + .btn, +.input-group-prepend .btn + .input-group-text, +.input-group-prepend .input-group-text + .input-group-text, +.input-group-prepend .input-group-text + .btn, +.input-group-append .btn + .btn, +.input-group-append .btn + .input-group-text, +.input-group-append .input-group-text + .input-group-text, +.input-group-append .input-group-text + .btn { + margin-left: -1px; +} + +.input-group-prepend { + margin-right: -1px; +} + +.input-group-append { + margin-left: -1px; +} + +.input-group-text { + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; + padding: 0.375rem 0.75rem; + margin-bottom: 0; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #495057; + text-align: center; + white-space: nowrap; + background-color: #e9ecef; + border: 1px solid #ced4da; + border-radius: 0.25rem; +} + +.input-group-text input[type="radio"], +.input-group-text input[type="checkbox"] { + margin-top: 0; +} + +.input-group-lg > .form-control:not(textarea), +.input-group-lg > .custom-select { + height: calc(1.5em + 1rem + 2px); +} + +.input-group-lg > .form-control, +.input-group-lg > .custom-select, +.input-group-lg > .input-group-prepend > .input-group-text, +.input-group-lg > .input-group-append > .input-group-text, +.input-group-lg > .input-group-prepend > .btn, +.input-group-lg > .input-group-append > .btn { + padding: 0.5rem 1rem; + font-size: 1.25rem; + line-height: 1.5; + border-radius: 0.3rem; +} + +.input-group-sm > .form-control:not(textarea), +.input-group-sm > .custom-select { + height: calc(1.5em + 0.5rem + 2px); +} + +.input-group-sm > .form-control, +.input-group-sm > .custom-select, +.input-group-sm > .input-group-prepend > .input-group-text, +.input-group-sm > .input-group-append > .input-group-text, +.input-group-sm > .input-group-prepend > .btn, +.input-group-sm > .input-group-append > .btn { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; + border-radius: 0.2rem; +} + +.input-group-lg > .custom-select, +.input-group-sm > .custom-select { + padding-right: 1.75rem; +} + +.input-group > .input-group-prepend > .btn, +.input-group > .input-group-prepend > .input-group-text, +.input-group > .input-group-append:not(:last-child) > .btn, +.input-group > .input-group-append:not(:last-child) > .input-group-text, +.input-group > .input-group-append:last-child > .btn:not(:last-child):not(.dropdown-toggle), +.input-group > .input-group-append:last-child > .input-group-text:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +.input-group > .input-group-append > .btn, +.input-group > .input-group-append > .input-group-text, +.input-group > .input-group-prepend:not(:first-child) > .btn, +.input-group > .input-group-prepend:not(:first-child) > .input-group-text, +.input-group > .input-group-prepend:first-child > .btn:not(:first-child), +.input-group > .input-group-prepend:first-child > .input-group-text:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} + +.custom-control { + position: relative; + display: block; + min-height: 1.5rem; + padding-left: 1.5rem; +} + +.custom-control-inline { + display: -ms-inline-flexbox; + display: inline-flex; + margin-right: 1rem; +} + +.custom-control-input { + position: absolute; + left: 0; + z-index: -1; + width: 1rem; + height: 1.25rem; + opacity: 0; +} + +.custom-control-input:checked ~ .custom-control-label::before { + color: #fff; + border-color: #007bff; + background-color: #007bff; +} + +.custom-control-input:focus ~ .custom-control-label::before { + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} + +.custom-control-input:focus:not(:checked) ~ .custom-control-label::before { + border-color: #80bdff; +} + +.custom-control-input:not(:disabled):active ~ .custom-control-label::before { + color: #fff; + background-color: #b3d7ff; + border-color: #b3d7ff; +} + +.custom-control-input[disabled] ~ .custom-control-label, .custom-control-input:disabled ~ .custom-control-label { + color: #6c757d; +} + +.custom-control-input[disabled] ~ .custom-control-label::before, .custom-control-input:disabled ~ .custom-control-label::before { + background-color: #e9ecef; +} + +.custom-control-label { + position: relative; + margin-bottom: 0; + vertical-align: top; +} + +.custom-control-label::before { + position: absolute; + top: 0.25rem; + left: -1.5rem; + display: block; + width: 1rem; + height: 1rem; + pointer-events: none; + content: ""; + background-color: #fff; + border: #adb5bd solid 1px; +} + +.custom-control-label::after { + position: absolute; + top: 0.25rem; + left: -1.5rem; + display: block; + width: 1rem; + height: 1rem; + content: ""; + background: no-repeat 50% / 50% 50%; +} + +.custom-checkbox .custom-control-label::before { + border-radius: 0.25rem; +} + +.custom-checkbox .custom-control-input:checked ~ .custom-control-label::after { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e"); +} + +.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::before { + border-color: #007bff; + background-color: #007bff; +} + +.custom-checkbox .custom-control-input:indeterminate ~ .custom-control-label::after { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e"); +} + +.custom-checkbox .custom-control-input:disabled:checked ~ .custom-control-label::before { + background-color: rgba(0, 123, 255, 0.5); +} + +.custom-checkbox .custom-control-input:disabled:indeterminate ~ .custom-control-label::before { + background-color: rgba(0, 123, 255, 0.5); +} + +.custom-radio .custom-control-label::before { + border-radius: 50%; +} + +.custom-radio .custom-control-input:checked ~ .custom-control-label::after { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e"); +} + +.custom-radio .custom-control-input:disabled:checked ~ .custom-control-label::before { + background-color: rgba(0, 123, 255, 0.5); +} + +.custom-switch { + padding-left: 2.25rem; +} + +.custom-switch .custom-control-label::before { + left: -2.25rem; + width: 1.75rem; + pointer-events: all; + border-radius: 0.5rem; +} + +.custom-switch .custom-control-label::after { + top: calc(0.25rem + 2px); + left: calc(-2.25rem + 2px); + width: calc(1rem - 4px); + height: calc(1rem - 4px); + background-color: #adb5bd; + border-radius: 0.5rem; + transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-transform 0.15s ease-in-out; + transition: transform 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + transition: transform 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out, -webkit-transform 0.15s ease-in-out; +} + +@media (prefers-reduced-motion: reduce) { + .custom-switch .custom-control-label::after { + transition: none; + } +} + +.custom-switch .custom-control-input:checked ~ .custom-control-label::after { + background-color: #fff; + -webkit-transform: translateX(0.75rem); + transform: translateX(0.75rem); +} + +.custom-switch .custom-control-input:disabled:checked ~ .custom-control-label::before { + background-color: rgba(0, 123, 255, 0.5); +} + +.custom-select { + display: inline-block; + width: 100%; + height: calc(1.5em + 0.75rem + 2px); + padding: 0.375rem 1.75rem 0.375rem 0.75rem; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #495057; + vertical-align: middle; + background: #fff url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='5' viewBox='0 0 4 5'%3e%3cpath fill='%23343a40' d='M2 0L0 2h4zm0 5L0 3h4z'/%3e%3c/svg%3e") no-repeat right 0.75rem center/8px 10px; + border: 1px solid #ced4da; + border-radius: 0.25rem; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +.custom-select:focus { + border-color: #80bdff; + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} + +.custom-select:focus::-ms-value { + color: #495057; + background-color: #fff; +} + +.custom-select[multiple], .custom-select[size]:not([size="1"]) { + height: auto; + padding-right: 0.75rem; + background-image: none; +} + +.custom-select:disabled { + color: #6c757d; + background-color: #e9ecef; +} + +.custom-select::-ms-expand { + display: none; +} + +.custom-select:-moz-focusring { + color: transparent; + text-shadow: 0 0 0 #495057; +} + +.custom-select-sm { + height: calc(1.5em + 0.5rem + 2px); + padding-top: 0.25rem; + padding-bottom: 0.25rem; + padding-left: 0.5rem; + font-size: 0.875rem; +} + +.custom-select-lg { + height: calc(1.5em + 1rem + 2px); + padding-top: 0.5rem; + padding-bottom: 0.5rem; + padding-left: 1rem; + font-size: 1.25rem; +} + +.custom-file { + position: relative; + display: inline-block; + width: 100%; + height: calc(1.5em + 0.75rem + 2px); + margin-bottom: 0; +} + +.custom-file-input { + position: relative; + z-index: 2; + width: 100%; + height: calc(1.5em + 0.75rem + 2px); + margin: 0; + opacity: 0; +} + +.custom-file-input:focus ~ .custom-file-label { + border-color: #80bdff; + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} + +.custom-file-input[disabled] ~ .custom-file-label, +.custom-file-input:disabled ~ .custom-file-label { + background-color: #e9ecef; +} + +.custom-file-input:lang(en) ~ .custom-file-label::after { + content: "Browse"; +} + +.custom-file-input ~ .custom-file-label[data-browse]::after { + content: attr(data-browse); +} + +.custom-file-label { + position: absolute; + top: 0; + right: 0; + left: 0; + z-index: 1; + height: calc(1.5em + 0.75rem + 2px); + padding: 0.375rem 0.75rem; + font-weight: 400; + line-height: 1.5; + color: #495057; + background-color: #fff; + border: 1px solid #ced4da; + border-radius: 0.25rem; +} + +.custom-file-label::after { + position: absolute; + top: 0; + right: 0; + bottom: 0; + z-index: 3; + display: block; + height: calc(1.5em + 0.75rem); + padding: 0.375rem 0.75rem; + line-height: 1.5; + color: #495057; + content: "Browse"; + background-color: #e9ecef; + border-left: inherit; + border-radius: 0 0.25rem 0.25rem 0; +} + +.custom-range { + width: 100%; + height: 1.4rem; + padding: 0; + background-color: transparent; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +.custom-range:focus { + outline: none; +} + +.custom-range:focus::-webkit-slider-thumb { + box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} + +.custom-range:focus::-moz-range-thumb { + box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} + +.custom-range:focus::-ms-thumb { + box-shadow: 0 0 0 1px #fff, 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} + +.custom-range::-moz-focus-outer { + border: 0; +} + +.custom-range::-webkit-slider-thumb { + width: 1rem; + height: 1rem; + margin-top: -0.25rem; + background-color: #007bff; + border: 0; + border-radius: 1rem; + -webkit-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + -webkit-appearance: none; + appearance: none; +} + +@media (prefers-reduced-motion: reduce) { + .custom-range::-webkit-slider-thumb { + -webkit-transition: none; + transition: none; + } +} + +.custom-range::-webkit-slider-thumb:active { + background-color: #b3d7ff; +} + +.custom-range::-webkit-slider-runnable-track { + width: 100%; + height: 0.5rem; + color: transparent; + cursor: pointer; + background-color: #dee2e6; + border-color: transparent; + border-radius: 1rem; +} + +.custom-range::-moz-range-thumb { + width: 1rem; + height: 1rem; + background-color: #007bff; + border: 0; + border-radius: 1rem; + -moz-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + -moz-appearance: none; + appearance: none; +} + +@media (prefers-reduced-motion: reduce) { + .custom-range::-moz-range-thumb { + -moz-transition: none; + transition: none; + } +} + +.custom-range::-moz-range-thumb:active { + background-color: #b3d7ff; +} + +.custom-range::-moz-range-track { + width: 100%; + height: 0.5rem; + color: transparent; + cursor: pointer; + background-color: #dee2e6; + border-color: transparent; + border-radius: 1rem; +} + +.custom-range::-ms-thumb { + width: 1rem; + height: 1rem; + margin-top: 0; + margin-right: 0.2rem; + margin-left: 0.2rem; + background-color: #007bff; + border: 0; + border-radius: 1rem; + -ms-transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; + appearance: none; +} + +@media (prefers-reduced-motion: reduce) { + .custom-range::-ms-thumb { + -ms-transition: none; + transition: none; + } +} + +.custom-range::-ms-thumb:active { + background-color: #b3d7ff; +} + +.custom-range::-ms-track { + width: 100%; + height: 0.5rem; + color: transparent; + cursor: pointer; + background-color: transparent; + border-color: transparent; + border-width: 0.5rem; +} + +.custom-range::-ms-fill-lower { + background-color: #dee2e6; + border-radius: 1rem; +} + +.custom-range::-ms-fill-upper { + margin-right: 15px; + background-color: #dee2e6; + border-radius: 1rem; +} + +.custom-range:disabled::-webkit-slider-thumb { + background-color: #adb5bd; +} + +.custom-range:disabled::-webkit-slider-runnable-track { + cursor: default; +} + +.custom-range:disabled::-moz-range-thumb { + background-color: #adb5bd; +} + +.custom-range:disabled::-moz-range-track { + cursor: default; +} + +.custom-range:disabled::-ms-thumb { + background-color: #adb5bd; +} + +.custom-control-label::before, +.custom-file-label, +.custom-select { + transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} + +@media (prefers-reduced-motion: reduce) { + .custom-control-label::before, + .custom-file-label, + .custom-select { + transition: none; + } +} + +.nav { + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + padding-left: 0; + margin-bottom: 0; + list-style: none; +} + +.nav-link { + display: block; + padding: 0.5rem 1rem; +} + +.nav-link:hover, .nav-link:focus { + text-decoration: none; +} + +.nav-link.disabled { + color: #6c757d; + pointer-events: none; + cursor: default; +} + +.nav-tabs { + border-bottom: 1px solid #dee2e6; +} + +.nav-tabs .nav-item { + margin-bottom: -1px; +} + +.nav-tabs .nav-link { + border: 1px solid transparent; + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; +} + +.nav-tabs .nav-link:hover, .nav-tabs .nav-link:focus { + border-color: #e9ecef #e9ecef #dee2e6; +} + +.nav-tabs .nav-link.disabled { + color: #6c757d; + background-color: transparent; + border-color: transparent; +} + +.nav-tabs .nav-link.active, +.nav-tabs .nav-item.show .nav-link { + color: #495057; + background-color: #fff; + border-color: #dee2e6 #dee2e6 #fff; +} + +.nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.nav-pills .nav-link { + border-radius: 0.25rem; +} + +.nav-pills .nav-link.active, +.nav-pills .show > .nav-link { + color: #fff; + background-color: #007bff; +} + +.nav-fill .nav-item { + -ms-flex: 1 1 auto; + flex: 1 1 auto; + text-align: center; +} + +.nav-justified .nav-item { + -ms-flex-preferred-size: 0; + flex-basis: 0; + -ms-flex-positive: 1; + flex-grow: 1; + text-align: center; +} + +.tab-content > .tab-pane { + display: none; +} + +.tab-content > .active { + display: block; +} + +.navbar { + position: relative; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -ms-flex-align: center; + align-items: center; + -ms-flex-pack: justify; + justify-content: space-between; + padding: 0.5rem 1rem; +} + +.navbar .container, +.navbar .container-fluid, .navbar .container-sm, .navbar .container-md, .navbar .container-lg, .navbar .container-xl { + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -ms-flex-align: center; + align-items: center; + -ms-flex-pack: justify; + justify-content: space-between; +} + +.navbar-brand { + display: inline-block; + padding-top: 0.3125rem; + padding-bottom: 0.3125rem; + margin-right: 1rem; + font-size: 1.25rem; + line-height: inherit; + white-space: nowrap; +} + +.navbar-brand:hover, .navbar-brand:focus { + text-decoration: none; +} + +.navbar-nav { + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; + list-style: none; +} + +.navbar-nav .nav-link { + padding-right: 0; + padding-left: 0; +} + +.navbar-nav .dropdown-menu { + position: static; + float: none; +} + +.navbar-text { + display: inline-block; + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.navbar-collapse { + -ms-flex-preferred-size: 100%; + flex-basis: 100%; + -ms-flex-positive: 1; + flex-grow: 1; + -ms-flex-align: center; + align-items: center; +} + +.navbar-toggler { + padding: 0.25rem 0.75rem; + font-size: 1.25rem; + line-height: 1; + background-color: transparent; + border: 1px solid transparent; + border-radius: 0.25rem; +} + +.navbar-toggler:hover, .navbar-toggler:focus { + text-decoration: none; +} + +.navbar-toggler-icon { + display: inline-block; + width: 1.5em; + height: 1.5em; + vertical-align: middle; + content: ""; + background: no-repeat center center; + background-size: 100% 100%; +} + +@media (max-width: 575.98px) { + .navbar-expand-sm > .container, + .navbar-expand-sm > .container-fluid, .navbar-expand-sm > .container-sm, .navbar-expand-sm > .container-md, .navbar-expand-sm > .container-lg, .navbar-expand-sm > .container-xl { + padding-right: 0; + padding-left: 0; + } +} + +@media (min-width: 576px) { + .navbar-expand-sm { + -ms-flex-flow: row nowrap; + flex-flow: row nowrap; + -ms-flex-pack: start; + justify-content: flex-start; + } + .navbar-expand-sm .navbar-nav { + -ms-flex-direction: row; + flex-direction: row; + } + .navbar-expand-sm .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-sm .navbar-nav .nav-link { + padding-right: 0.5rem; + padding-left: 0.5rem; + } + .navbar-expand-sm > .container, + .navbar-expand-sm > .container-fluid, .navbar-expand-sm > .container-sm, .navbar-expand-sm > .container-md, .navbar-expand-sm > .container-lg, .navbar-expand-sm > .container-xl { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + } + .navbar-expand-sm .navbar-collapse { + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto; + } + .navbar-expand-sm .navbar-toggler { + display: none; + } +} + +@media (max-width: 767.98px) { + .navbar-expand-md > .container, + .navbar-expand-md > .container-fluid, .navbar-expand-md > .container-sm, .navbar-expand-md > .container-md, .navbar-expand-md > .container-lg, .navbar-expand-md > .container-xl { + padding-right: 0; + padding-left: 0; + } +} + +@media (min-width: 768px) { + .navbar-expand-md { + -ms-flex-flow: row nowrap; + flex-flow: row nowrap; + -ms-flex-pack: start; + justify-content: flex-start; + } + .navbar-expand-md .navbar-nav { + -ms-flex-direction: row; + flex-direction: row; + } + .navbar-expand-md .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-md .navbar-nav .nav-link { + padding-right: 0.5rem; + padding-left: 0.5rem; + } + .navbar-expand-md > .container, + .navbar-expand-md > .container-fluid, .navbar-expand-md > .container-sm, .navbar-expand-md > .container-md, .navbar-expand-md > .container-lg, .navbar-expand-md > .container-xl { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + } + .navbar-expand-md .navbar-collapse { + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto; + } + .navbar-expand-md .navbar-toggler { + display: none; + } +} + +@media (max-width: 991.98px) { + .navbar-expand-lg > .container, + .navbar-expand-lg > .container-fluid, .navbar-expand-lg > .container-sm, .navbar-expand-lg > .container-md, .navbar-expand-lg > .container-lg, .navbar-expand-lg > .container-xl { + padding-right: 0; + padding-left: 0; + } +} + +@media (min-width: 992px) { + .navbar-expand-lg { + -ms-flex-flow: row nowrap; + flex-flow: row nowrap; + -ms-flex-pack: start; + justify-content: flex-start; + } + .navbar-expand-lg .navbar-nav { + -ms-flex-direction: row; + flex-direction: row; + } + .navbar-expand-lg .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-lg .navbar-nav .nav-link { + padding-right: 0.5rem; + padding-left: 0.5rem; + } + .navbar-expand-lg > .container, + .navbar-expand-lg > .container-fluid, .navbar-expand-lg > .container-sm, .navbar-expand-lg > .container-md, .navbar-expand-lg > .container-lg, .navbar-expand-lg > .container-xl { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + } + .navbar-expand-lg .navbar-collapse { + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto; + } + .navbar-expand-lg .navbar-toggler { + display: none; + } +} + +@media (max-width: 1199.98px) { + .navbar-expand-xl > .container, + .navbar-expand-xl > .container-fluid, .navbar-expand-xl > .container-sm, .navbar-expand-xl > .container-md, .navbar-expand-xl > .container-lg, .navbar-expand-xl > .container-xl { + padding-right: 0; + padding-left: 0; + } +} + +@media (min-width: 1200px) { + .navbar-expand-xl { + -ms-flex-flow: row nowrap; + flex-flow: row nowrap; + -ms-flex-pack: start; + justify-content: flex-start; + } + .navbar-expand-xl .navbar-nav { + -ms-flex-direction: row; + flex-direction: row; + } + .navbar-expand-xl .navbar-nav .dropdown-menu { + position: absolute; + } + .navbar-expand-xl .navbar-nav .nav-link { + padding-right: 0.5rem; + padding-left: 0.5rem; + } + .navbar-expand-xl > .container, + .navbar-expand-xl > .container-fluid, .navbar-expand-xl > .container-sm, .navbar-expand-xl > .container-md, .navbar-expand-xl > .container-lg, .navbar-expand-xl > .container-xl { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; + } + .navbar-expand-xl .navbar-collapse { + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto; + } + .navbar-expand-xl .navbar-toggler { + display: none; + } +} + +.navbar-expand { + -ms-flex-flow: row nowrap; + flex-flow: row nowrap; + -ms-flex-pack: start; + justify-content: flex-start; +} + +.navbar-expand > .container, +.navbar-expand > .container-fluid, .navbar-expand > .container-sm, .navbar-expand > .container-md, .navbar-expand > .container-lg, .navbar-expand > .container-xl { + padding-right: 0; + padding-left: 0; +} + +.navbar-expand .navbar-nav { + -ms-flex-direction: row; + flex-direction: row; +} + +.navbar-expand .navbar-nav .dropdown-menu { + position: absolute; +} + +.navbar-expand .navbar-nav .nav-link { + padding-right: 0.5rem; + padding-left: 0.5rem; +} + +.navbar-expand > .container, +.navbar-expand > .container-fluid, .navbar-expand > .container-sm, .navbar-expand > .container-md, .navbar-expand > .container-lg, .navbar-expand > .container-xl { + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; +} + +.navbar-expand .navbar-collapse { + display: -ms-flexbox !important; + display: flex !important; + -ms-flex-preferred-size: auto; + flex-basis: auto; +} + +.navbar-expand .navbar-toggler { + display: none; +} + +.navbar-light .navbar-brand { + color: rgba(0, 0, 0, 0.9); +} + +.navbar-light .navbar-brand:hover, .navbar-light .navbar-brand:focus { + color: rgba(0, 0, 0, 0.9); +} + +.navbar-light .navbar-nav .nav-link { + color: rgba(0, 0, 0, 0.5); +} + +.navbar-light .navbar-nav .nav-link:hover, .navbar-light .navbar-nav .nav-link:focus { + color: rgba(0, 0, 0, 0.7); +} + +.navbar-light .navbar-nav .nav-link.disabled { + color: rgba(0, 0, 0, 0.3); +} + +.navbar-light .navbar-nav .show > .nav-link, +.navbar-light .navbar-nav .active > .nav-link, +.navbar-light .navbar-nav .nav-link.show, +.navbar-light .navbar-nav .nav-link.active { + color: rgba(0, 0, 0, 0.9); +} + +.navbar-light .navbar-toggler { + color: rgba(0, 0, 0, 0.5); + border-color: rgba(0, 0, 0, 0.1); +} + +.navbar-light .navbar-toggler-icon { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(0, 0, 0, 0.5)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); +} + +.navbar-light .navbar-text { + color: rgba(0, 0, 0, 0.5); +} + +.navbar-light .navbar-text a { + color: rgba(0, 0, 0, 0.9); +} + +.navbar-light .navbar-text a:hover, .navbar-light .navbar-text a:focus { + color: rgba(0, 0, 0, 0.9); +} + +.navbar-dark .navbar-brand { + color: #fff; +} + +.navbar-dark .navbar-brand:hover, .navbar-dark .navbar-brand:focus { + color: #fff; +} + +.navbar-dark .navbar-nav .nav-link { + color: rgba(255, 255, 255, 0.5); +} + +.navbar-dark .navbar-nav .nav-link:hover, .navbar-dark .navbar-nav .nav-link:focus { + color: rgba(255, 255, 255, 0.75); +} + +.navbar-dark .navbar-nav .nav-link.disabled { + color: rgba(255, 255, 255, 0.25); +} + +.navbar-dark .navbar-nav .show > .nav-link, +.navbar-dark .navbar-nav .active > .nav-link, +.navbar-dark .navbar-nav .nav-link.show, +.navbar-dark .navbar-nav .nav-link.active { + color: #fff; +} + +.navbar-dark .navbar-toggler { + color: rgba(255, 255, 255, 0.5); + border-color: rgba(255, 255, 255, 0.1); +} + +.navbar-dark .navbar-toggler-icon { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30' viewBox='0 0 30 30'%3e%3cpath stroke='rgba(255, 255, 255, 0.5)' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e"); +} + +.navbar-dark .navbar-text { + color: rgba(255, 255, 255, 0.5); +} + +.navbar-dark .navbar-text a { + color: #fff; +} + +.navbar-dark .navbar-text a:hover, .navbar-dark .navbar-text a:focus { + color: #fff; +} + +.card { + position: relative; + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + min-width: 0; + word-wrap: break-word; + background-color: #fff; + background-clip: border-box; + border: 1px solid rgba(0, 0, 0, 0.125); + border-radius: 0.25rem; +} + +.card > hr { + margin-right: 0; + margin-left: 0; +} + +.card > .list-group:first-child .list-group-item:first-child { + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; +} + +.card > .list-group:last-child .list-group-item:last-child { + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; +} + +.card-body { + -ms-flex: 1 1 auto; + flex: 1 1 auto; + min-height: 1px; + padding: 1.25rem; +} + +.card-title { + margin-bottom: 0.75rem; +} + +.card-subtitle { + margin-top: -0.375rem; + margin-bottom: 0; +} + +.card-text:last-child { + margin-bottom: 0; +} + +.card-link:hover { + text-decoration: none; +} + +.card-link + .card-link { + margin-left: 1.25rem; +} + +.card-header { + padding: 0.75rem 1.25rem; + margin-bottom: 0; + background-color: rgba(0, 0, 0, 0.03); + border-bottom: 1px solid rgba(0, 0, 0, 0.125); +} + +.card-header:first-child { + border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0; +} + +.card-header + .list-group .list-group-item:first-child { + border-top: 0; +} + +.card-footer { + padding: 0.75rem 1.25rem; + background-color: rgba(0, 0, 0, 0.03); + border-top: 1px solid rgba(0, 0, 0, 0.125); +} + +.card-footer:last-child { + border-radius: 0 0 calc(0.25rem - 1px) calc(0.25rem - 1px); +} + +.card-header-tabs { + margin-right: -0.625rem; + margin-bottom: -0.75rem; + margin-left: -0.625rem; + border-bottom: 0; +} + +.card-header-pills { + margin-right: -0.625rem; + margin-left: -0.625rem; +} + +.card-img-overlay { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + padding: 1.25rem; +} + +.card-img, +.card-img-top, +.card-img-bottom { + -ms-flex-negative: 0; + flex-shrink: 0; + width: 100%; +} + +.card-img, +.card-img-top { + border-top-left-radius: calc(0.25rem - 1px); + border-top-right-radius: calc(0.25rem - 1px); +} + +.card-img, +.card-img-bottom { + border-bottom-right-radius: calc(0.25rem - 1px); + border-bottom-left-radius: calc(0.25rem - 1px); +} + +.card-deck .card { + margin-bottom: 15px; +} + +@media (min-width: 576px) { + .card-deck { + display: -ms-flexbox; + display: flex; + -ms-flex-flow: row wrap; + flex-flow: row wrap; + margin-right: -15px; + margin-left: -15px; + } + .card-deck .card { + -ms-flex: 1 0 0%; + flex: 1 0 0%; + margin-right: 15px; + margin-bottom: 0; + margin-left: 15px; + } +} + +.card-group > .card { + margin-bottom: 15px; +} + +@media (min-width: 576px) { + .card-group { + display: -ms-flexbox; + display: flex; + -ms-flex-flow: row wrap; + flex-flow: row wrap; + } + .card-group > .card { + -ms-flex: 1 0 0%; + flex: 1 0 0%; + margin-bottom: 0; + } + .card-group > .card + .card { + margin-left: 0; + border-left: 0; + } + .card-group > .card:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + .card-group > .card:not(:last-child) .card-img-top, + .card-group > .card:not(:last-child) .card-header { + border-top-right-radius: 0; + } + .card-group > .card:not(:last-child) .card-img-bottom, + .card-group > .card:not(:last-child) .card-footer { + border-bottom-right-radius: 0; + } + .card-group > .card:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + .card-group > .card:not(:first-child) .card-img-top, + .card-group > .card:not(:first-child) .card-header { + border-top-left-radius: 0; + } + .card-group > .card:not(:first-child) .card-img-bottom, + .card-group > .card:not(:first-child) .card-footer { + border-bottom-left-radius: 0; + } +} + +.card-columns .card { + margin-bottom: 0.75rem; +} + +@media (min-width: 576px) { + .card-columns { + -webkit-column-count: 3; + -moz-column-count: 3; + column-count: 3; + -webkit-column-gap: 1.25rem; + -moz-column-gap: 1.25rem; + column-gap: 1.25rem; + orphans: 1; + widows: 1; + } + .card-columns .card { + display: inline-block; + width: 100%; + } +} + +.accordion > .card { + overflow: hidden; +} + +.accordion > .card:not(:last-of-type) { + border-bottom: 0; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} + +.accordion > .card:not(:first-of-type) { + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.accordion > .card > .card-header { + border-radius: 0; + margin-bottom: -1px; +} + +.breadcrumb { + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + padding: 0.75rem 1rem; + margin-bottom: 1rem; + list-style: none; + background-color: #e9ecef; + border-radius: 0.25rem; +} + +.breadcrumb-item + .breadcrumb-item { + padding-left: 0.5rem; +} + +.breadcrumb-item + .breadcrumb-item::before { + display: inline-block; + padding-right: 0.5rem; + color: #6c757d; + content: "/"; +} + +.breadcrumb-item + .breadcrumb-item:hover::before { + text-decoration: underline; +} + +.breadcrumb-item + .breadcrumb-item:hover::before { + text-decoration: none; +} + +.breadcrumb-item.active { + color: #6c757d; +} + +.pagination { + display: -ms-flexbox; + display: flex; + padding-left: 0; + list-style: none; + border-radius: 0.25rem; +} + +.page-link { + position: relative; + display: block; + padding: 0.5rem 0.75rem; + margin-left: -1px; + line-height: 1.25; + color: #007bff; + background-color: #fff; + border: 1px solid #dee2e6; +} + +.page-link:hover { + z-index: 2; + color: #0056b3; + text-decoration: none; + background-color: #e9ecef; + border-color: #dee2e6; +} + +.page-link:focus { + z-index: 3; + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.25); +} + +.page-item:first-child .page-link { + margin-left: 0; + border-top-left-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; +} + +.page-item:last-child .page-link { + border-top-right-radius: 0.25rem; + border-bottom-right-radius: 0.25rem; +} + +.page-item.active .page-link { + z-index: 3; + color: #fff; + background-color: #007bff; + border-color: #007bff; +} + +.page-item.disabled .page-link { + color: #6c757d; + pointer-events: none; + cursor: auto; + background-color: #fff; + border-color: #dee2e6; +} + +.pagination-lg .page-link { + padding: 0.75rem 1.5rem; + font-size: 1.25rem; + line-height: 1.5; +} + +.pagination-lg .page-item:first-child .page-link { + border-top-left-radius: 0.3rem; + border-bottom-left-radius: 0.3rem; +} + +.pagination-lg .page-item:last-child .page-link { + border-top-right-radius: 0.3rem; + border-bottom-right-radius: 0.3rem; +} + +.pagination-sm .page-link { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + line-height: 1.5; +} + +.pagination-sm .page-item:first-child .page-link { + border-top-left-radius: 0.2rem; + border-bottom-left-radius: 0.2rem; +} + +.pagination-sm .page-item:last-child .page-link { + border-top-right-radius: 0.2rem; + border-bottom-right-radius: 0.2rem; +} + +.badge { + display: inline-block; + padding: 0.25em 0.4em; + font-size: 75%; + font-weight: 700; + line-height: 1; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: 0.25rem; + transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out; +} + +@media (prefers-reduced-motion: reduce) { + .badge { + transition: none; + } +} + +a.badge:hover, a.badge:focus { + text-decoration: none; +} + +.badge:empty { + display: none; +} + +.btn .badge { + position: relative; + top: -1px; +} + +.badge-pill { + padding-right: 0.6em; + padding-left: 0.6em; + border-radius: 10rem; +} + +.badge-primary { + color: #fff; + background-color: #007bff; +} + +a.badge-primary:hover, a.badge-primary:focus { + color: #fff; + background-color: #0062cc; +} + +a.badge-primary:focus, a.badge-primary.focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(0, 123, 255, 0.5); +} + +.badge-secondary { + color: #fff; + background-color: #6c757d; +} + +a.badge-secondary:hover, a.badge-secondary:focus { + color: #fff; + background-color: #545b62; +} + +a.badge-secondary:focus, a.badge-secondary.focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(108, 117, 125, 0.5); +} + +.badge-success { + color: #fff; + background-color: #28a745; +} + +a.badge-success:hover, a.badge-success:focus { + color: #fff; + background-color: #1e7e34; +} + +a.badge-success:focus, a.badge-success.focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); +} + +.badge-info { + color: #fff; + background-color: #17a2b8; +} + +a.badge-info:hover, a.badge-info:focus { + color: #fff; + background-color: #117a8b; +} + +a.badge-info:focus, a.badge-info.focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(23, 162, 184, 0.5); +} + +.badge-warning { + color: #212529; + background-color: #ffc107; +} + +a.badge-warning:hover, a.badge-warning:focus { + color: #212529; + background-color: #d39e00; +} + +a.badge-warning:focus, a.badge-warning.focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(255, 193, 7, 0.5); +} + +.badge-danger { + color: #fff; + background-color: #dc3545; +} + +a.badge-danger:hover, a.badge-danger:focus { + color: #fff; + background-color: #bd2130; +} + +a.badge-danger:focus, a.badge-danger.focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); +} + +.badge-light { + color: #212529; + background-color: #f8f9fa; +} + +a.badge-light:hover, a.badge-light:focus { + color: #212529; + background-color: #dae0e5; +} + +a.badge-light:focus, a.badge-light.focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(248, 249, 250, 0.5); +} + +.badge-dark { + color: #fff; + background-color: #343a40; +} + +a.badge-dark:hover, a.badge-dark:focus { + color: #fff; + background-color: #1d2124; +} + +a.badge-dark:focus, a.badge-dark.focus { + outline: 0; + box-shadow: 0 0 0 0.2rem rgba(52, 58, 64, 0.5); +} + +.jumbotron { + padding: 2rem 1rem; + margin-bottom: 2rem; + background-color: #e9ecef; + border-radius: 0.3rem; +} + +@media (min-width: 576px) { + .jumbotron { + padding: 4rem 2rem; + } +} + +.jumbotron-fluid { + padding-right: 0; + padding-left: 0; + border-radius: 0; +} + +.alert { + position: relative; + padding: 0.75rem 1.25rem; + margin-bottom: 1rem; + border: 1px solid transparent; + border-radius: 0.25rem; +} + +.alert-heading { + color: inherit; +} + +.alert-link { + font-weight: 700; +} + +.alert-dismissible { + padding-right: 4rem; +} + +.alert-dismissible .close { + position: absolute; + top: 0; + right: 0; + padding: 0.75rem 1.25rem; + color: inherit; +} + +.alert-primary { + color: #004085; + background-color: #cce5ff; + border-color: #b8daff; +} + +.alert-primary hr { + border-top-color: #9fcdff; +} + +.alert-primary .alert-link { + color: #002752; +} + +.alert-secondary { + color: #383d41; + background-color: #e2e3e5; + border-color: #d6d8db; +} + +.alert-secondary hr { + border-top-color: #c8cbcf; +} + +.alert-secondary .alert-link { + color: #202326; +} + +.alert-success { + color: #155724; + background-color: #d4edda; + border-color: #c3e6cb; +} + +.alert-success hr { + border-top-color: #b1dfbb; +} + +.alert-success .alert-link { + color: #0b2e13; +} + +.alert-info { + color: #0c5460; + background-color: #d1ecf1; + border-color: #bee5eb; +} + +.alert-info hr { + border-top-color: #abdde5; +} + +.alert-info .alert-link { + color: #062c33; +} + +.alert-warning { + color: #856404; + background-color: #fff3cd; + border-color: #ffeeba; +} + +.alert-warning hr { + border-top-color: #ffe8a1; +} + +.alert-warning .alert-link { + color: #533f03; +} + +.alert-danger { + color: #721c24; + background-color: #f8d7da; + border-color: #f5c6cb; +} + +.alert-danger hr { + border-top-color: #f1b0b7; +} + +.alert-danger .alert-link { + color: #491217; +} + +.alert-light { + color: #818182; + background-color: #fefefe; + border-color: #fdfdfe; +} + +.alert-light hr { + border-top-color: #ececf6; +} + +.alert-light .alert-link { + color: #686868; +} + +.alert-dark { + color: #1b1e21; + background-color: #d6d8d9; + border-color: #c6c8ca; +} + +.alert-dark hr { + border-top-color: #b9bbbe; +} + +.alert-dark .alert-link { + color: #040505; +} + +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 1rem 0; + } + to { + background-position: 0 0; + } +} + +@keyframes progress-bar-stripes { + from { + background-position: 1rem 0; + } + to { + background-position: 0 0; + } +} + +.progress { + display: -ms-flexbox; + display: flex; + height: 1rem; + overflow: hidden; + font-size: 0.75rem; + background-color: #e9ecef; + border-radius: 0.25rem; +} + +.progress-bar { + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + -ms-flex-pack: center; + justify-content: center; + overflow: hidden; + color: #fff; + text-align: center; + white-space: nowrap; + background-color: #007bff; + transition: width 0.6s ease; +} + +@media (prefers-reduced-motion: reduce) { + .progress-bar { + transition: none; + } +} + +.progress-bar-striped { + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-size: 1rem 1rem; +} + +.progress-bar-animated { + -webkit-animation: progress-bar-stripes 1s linear infinite; + animation: progress-bar-stripes 1s linear infinite; +} + +@media (prefers-reduced-motion: reduce) { + .progress-bar-animated { + -webkit-animation: none; + animation: none; + } +} + +.media { + display: -ms-flexbox; + display: flex; + -ms-flex-align: start; + align-items: flex-start; +} + +.media-body { + -ms-flex: 1; + flex: 1; +} + +.list-group { + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + padding-left: 0; + margin-bottom: 0; +} + +.list-group-item-action { + width: 100%; + color: #495057; + text-align: inherit; +} + +.list-group-item-action:hover, .list-group-item-action:focus { + z-index: 1; + color: #495057; + text-decoration: none; + background-color: #f8f9fa; +} + +.list-group-item-action:active { + color: #212529; + background-color: #e9ecef; +} + +.list-group-item { + position: relative; + display: block; + padding: 0.75rem 1.25rem; + background-color: #fff; + border: 1px solid rgba(0, 0, 0, 0.125); +} + +.list-group-item:first-child { + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; +} + +.list-group-item:last-child { + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; +} + +.list-group-item.disabled, .list-group-item:disabled { + color: #6c757d; + pointer-events: none; + background-color: #fff; +} + +.list-group-item.active { + z-index: 2; + color: #fff; + background-color: #007bff; + border-color: #007bff; +} + +.list-group-item + .list-group-item { + border-top-width: 0; +} + +.list-group-item + .list-group-item.active { + margin-top: -1px; + border-top-width: 1px; +} + +.list-group-horizontal { + -ms-flex-direction: row; + flex-direction: row; +} + +.list-group-horizontal .list-group-item:first-child { + border-bottom-left-radius: 0.25rem; + border-top-right-radius: 0; +} + +.list-group-horizontal .list-group-item:last-child { + border-top-right-radius: 0.25rem; + border-bottom-left-radius: 0; +} + +.list-group-horizontal .list-group-item.active { + margin-top: 0; +} + +.list-group-horizontal .list-group-item + .list-group-item { + border-top-width: 1px; + border-left-width: 0; +} + +.list-group-horizontal .list-group-item + .list-group-item.active { + margin-left: -1px; + border-left-width: 1px; +} + +@media (min-width: 576px) { + .list-group-horizontal-sm { + -ms-flex-direction: row; + flex-direction: row; + } + .list-group-horizontal-sm .list-group-item:first-child { + border-bottom-left-radius: 0.25rem; + border-top-right-radius: 0; + } + .list-group-horizontal-sm .list-group-item:last-child { + border-top-right-radius: 0.25rem; + border-bottom-left-radius: 0; + } + .list-group-horizontal-sm .list-group-item.active { + margin-top: 0; + } + .list-group-horizontal-sm .list-group-item + .list-group-item { + border-top-width: 1px; + border-left-width: 0; + } + .list-group-horizontal-sm .list-group-item + .list-group-item.active { + margin-left: -1px; + border-left-width: 1px; + } +} + +@media (min-width: 768px) { + .list-group-horizontal-md { + -ms-flex-direction: row; + flex-direction: row; + } + .list-group-horizontal-md .list-group-item:first-child { + border-bottom-left-radius: 0.25rem; + border-top-right-radius: 0; + } + .list-group-horizontal-md .list-group-item:last-child { + border-top-right-radius: 0.25rem; + border-bottom-left-radius: 0; + } + .list-group-horizontal-md .list-group-item.active { + margin-top: 0; + } + .list-group-horizontal-md .list-group-item + .list-group-item { + border-top-width: 1px; + border-left-width: 0; + } + .list-group-horizontal-md .list-group-item + .list-group-item.active { + margin-left: -1px; + border-left-width: 1px; + } +} + +@media (min-width: 992px) { + .list-group-horizontal-lg { + -ms-flex-direction: row; + flex-direction: row; + } + .list-group-horizontal-lg .list-group-item:first-child { + border-bottom-left-radius: 0.25rem; + border-top-right-radius: 0; + } + .list-group-horizontal-lg .list-group-item:last-child { + border-top-right-radius: 0.25rem; + border-bottom-left-radius: 0; + } + .list-group-horizontal-lg .list-group-item.active { + margin-top: 0; + } + .list-group-horizontal-lg .list-group-item + .list-group-item { + border-top-width: 1px; + border-left-width: 0; + } + .list-group-horizontal-lg .list-group-item + .list-group-item.active { + margin-left: -1px; + border-left-width: 1px; + } +} + +@media (min-width: 1200px) { + .list-group-horizontal-xl { + -ms-flex-direction: row; + flex-direction: row; + } + .list-group-horizontal-xl .list-group-item:first-child { + border-bottom-left-radius: 0.25rem; + border-top-right-radius: 0; + } + .list-group-horizontal-xl .list-group-item:last-child { + border-top-right-radius: 0.25rem; + border-bottom-left-radius: 0; + } + .list-group-horizontal-xl .list-group-item.active { + margin-top: 0; + } + .list-group-horizontal-xl .list-group-item + .list-group-item { + border-top-width: 1px; + border-left-width: 0; + } + .list-group-horizontal-xl .list-group-item + .list-group-item.active { + margin-left: -1px; + border-left-width: 1px; + } +} + +.list-group-flush .list-group-item { + border-right-width: 0; + border-left-width: 0; + border-radius: 0; +} + +.list-group-flush .list-group-item:first-child { + border-top-width: 0; +} + +.list-group-flush:last-child .list-group-item:last-child { + border-bottom-width: 0; +} + +.list-group-item-primary { + color: #004085; + background-color: #b8daff; +} + +.list-group-item-primary.list-group-item-action:hover, .list-group-item-primary.list-group-item-action:focus { + color: #004085; + background-color: #9fcdff; +} + +.list-group-item-primary.list-group-item-action.active { + color: #fff; + background-color: #004085; + border-color: #004085; +} + +.list-group-item-secondary { + color: #383d41; + background-color: #d6d8db; +} + +.list-group-item-secondary.list-group-item-action:hover, .list-group-item-secondary.list-group-item-action:focus { + color: #383d41; + background-color: #c8cbcf; +} + +.list-group-item-secondary.list-group-item-action.active { + color: #fff; + background-color: #383d41; + border-color: #383d41; +} + +.list-group-item-success { + color: #155724; + background-color: #c3e6cb; +} + +.list-group-item-success.list-group-item-action:hover, .list-group-item-success.list-group-item-action:focus { + color: #155724; + background-color: #b1dfbb; +} + +.list-group-item-success.list-group-item-action.active { + color: #fff; + background-color: #155724; + border-color: #155724; +} + +.list-group-item-info { + color: #0c5460; + background-color: #bee5eb; +} + +.list-group-item-info.list-group-item-action:hover, .list-group-item-info.list-group-item-action:focus { + color: #0c5460; + background-color: #abdde5; +} + +.list-group-item-info.list-group-item-action.active { + color: #fff; + background-color: #0c5460; + border-color: #0c5460; +} + +.list-group-item-warning { + color: #856404; + background-color: #ffeeba; +} + +.list-group-item-warning.list-group-item-action:hover, .list-group-item-warning.list-group-item-action:focus { + color: #856404; + background-color: #ffe8a1; +} + +.list-group-item-warning.list-group-item-action.active { + color: #fff; + background-color: #856404; + border-color: #856404; +} + +.list-group-item-danger { + color: #721c24; + background-color: #f5c6cb; +} + +.list-group-item-danger.list-group-item-action:hover, .list-group-item-danger.list-group-item-action:focus { + color: #721c24; + background-color: #f1b0b7; +} + +.list-group-item-danger.list-group-item-action.active { + color: #fff; + background-color: #721c24; + border-color: #721c24; +} + +.list-group-item-light { + color: #818182; + background-color: #fdfdfe; +} + +.list-group-item-light.list-group-item-action:hover, .list-group-item-light.list-group-item-action:focus { + color: #818182; + background-color: #ececf6; +} + +.list-group-item-light.list-group-item-action.active { + color: #fff; + background-color: #818182; + border-color: #818182; +} + +.list-group-item-dark { + color: #1b1e21; + background-color: #c6c8ca; +} + +.list-group-item-dark.list-group-item-action:hover, .list-group-item-dark.list-group-item-action:focus { + color: #1b1e21; + background-color: #b9bbbe; +} + +.list-group-item-dark.list-group-item-action.active { + color: #fff; + background-color: #1b1e21; + border-color: #1b1e21; +} + +.close { + float: right; + font-size: 1.5rem; + font-weight: 700; + line-height: 1; + color: #000; + text-shadow: 0 1px 0 #fff; + opacity: .5; +} + +.close:hover { + color: #000; + text-decoration: none; +} + +.close:not(:disabled):not(.disabled):hover, .close:not(:disabled):not(.disabled):focus { + opacity: .75; +} + +button.close { + padding: 0; + background-color: transparent; + border: 0; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; +} + +a.close.disabled { + pointer-events: none; +} + +.toast { + max-width: 350px; + overflow: hidden; + font-size: 0.875rem; + background-color: rgba(255, 255, 255, 0.85); + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.1); + box-shadow: 0 0.25rem 0.75rem rgba(0, 0, 0, 0.1); + -webkit-backdrop-filter: blur(10px); + backdrop-filter: blur(10px); + opacity: 0; + border-radius: 0.25rem; +} + +.toast:not(:last-child) { + margin-bottom: 0.75rem; +} + +.toast.showing { + opacity: 1; +} + +.toast.show { + display: block; + opacity: 1; +} + +.toast.hide { + display: none; +} + +.toast-header { + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; + padding: 0.25rem 0.75rem; + color: #6c757d; + background-color: rgba(255, 255, 255, 0.85); + background-clip: padding-box; + border-bottom: 1px solid rgba(0, 0, 0, 0.05); +} + +.toast-body { + padding: 0.75rem; +} + +.modal-open { + overflow: hidden; +} + +.modal-open .modal { + overflow-x: hidden; + overflow-y: auto; +} + +.modal { + position: fixed; + top: 0; + left: 0; + z-index: 1050; + display: none; + width: 100%; + height: 100%; + overflow: hidden; + outline: 0; +} + +.modal-dialog { + position: relative; + width: auto; + margin: 0.5rem; + pointer-events: none; +} + +.modal.fade .modal-dialog { + transition: -webkit-transform 0.3s ease-out; + transition: transform 0.3s ease-out; + transition: transform 0.3s ease-out, -webkit-transform 0.3s ease-out; + -webkit-transform: translate(0, -50px); + transform: translate(0, -50px); +} + +@media (prefers-reduced-motion: reduce) { + .modal.fade .modal-dialog { + transition: none; + } +} + +.modal.show .modal-dialog { + -webkit-transform: none; + transform: none; +} + +.modal.modal-static .modal-dialog { + -webkit-transform: scale(1.02); + transform: scale(1.02); +} + +.modal-dialog-scrollable { + display: -ms-flexbox; + display: flex; + max-height: calc(100% - 1rem); +} + +.modal-dialog-scrollable .modal-content { + max-height: calc(100vh - 1rem); + overflow: hidden; +} + +.modal-dialog-scrollable .modal-header, +.modal-dialog-scrollable .modal-footer { + -ms-flex-negative: 0; + flex-shrink: 0; +} + +.modal-dialog-scrollable .modal-body { + overflow-y: auto; +} + +.modal-dialog-centered { + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; + min-height: calc(100% - 1rem); +} + +.modal-dialog-centered::before { + display: block; + height: calc(100vh - 1rem); + content: ""; +} + +.modal-dialog-centered.modal-dialog-scrollable { + -ms-flex-direction: column; + flex-direction: column; + -ms-flex-pack: center; + justify-content: center; + height: 100%; +} + +.modal-dialog-centered.modal-dialog-scrollable .modal-content { + max-height: none; +} + +.modal-dialog-centered.modal-dialog-scrollable::before { + content: none; +} + +.modal-content { + position: relative; + display: -ms-flexbox; + display: flex; + -ms-flex-direction: column; + flex-direction: column; + width: 100%; + pointer-events: auto; + background-color: #fff; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 0.3rem; + outline: 0; +} + +.modal-backdrop { + position: fixed; + top: 0; + left: 0; + z-index: 1040; + width: 100vw; + height: 100vh; + background-color: #000; +} + +.modal-backdrop.fade { + opacity: 0; +} + +.modal-backdrop.show { + opacity: 0.5; +} + +.modal-header { + display: -ms-flexbox; + display: flex; + -ms-flex-align: start; + align-items: flex-start; + -ms-flex-pack: justify; + justify-content: space-between; + padding: 1rem 1rem; + border-bottom: 1px solid #dee2e6; + border-top-left-radius: calc(0.3rem - 1px); + border-top-right-radius: calc(0.3rem - 1px); +} + +.modal-header .close { + padding: 1rem 1rem; + margin: -1rem -1rem -1rem auto; +} + +.modal-title { + margin-bottom: 0; + line-height: 1.5; +} + +.modal-body { + position: relative; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + padding: 1rem; +} + +.modal-footer { + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: wrap; + flex-wrap: wrap; + -ms-flex-align: center; + align-items: center; + -ms-flex-pack: end; + justify-content: flex-end; + padding: 0.75rem; + border-top: 1px solid #dee2e6; + border-bottom-right-radius: calc(0.3rem - 1px); + border-bottom-left-radius: calc(0.3rem - 1px); +} + +.modal-footer > * { + margin: 0.25rem; +} + +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; +} + +@media (min-width: 576px) { + .modal-dialog { + max-width: 500px; + margin: 1.75rem auto; + } + .modal-dialog-scrollable { + max-height: calc(100% - 3.5rem); + } + .modal-dialog-scrollable .modal-content { + max-height: calc(100vh - 3.5rem); + } + .modal-dialog-centered { + min-height: calc(100% - 3.5rem); + } + .modal-dialog-centered::before { + height: calc(100vh - 3.5rem); + } + .modal-sm { + max-width: 300px; + } +} + +@media (min-width: 992px) { + .modal-lg, + .modal-xl { + max-width: 800px; + } +} + +@media (min-width: 1200px) { + .modal-xl { + max-width: 1140px; + } +} + +.tooltip { + position: absolute; + z-index: 1070; + display: block; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + font-style: normal; + font-weight: 400; + line-height: 1.5; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + white-space: normal; + line-break: auto; + font-size: 0.875rem; + word-wrap: break-word; + opacity: 0; +} + +.tooltip.show { + opacity: 0.9; +} + +.tooltip .arrow { + position: absolute; + display: block; + width: 0.8rem; + height: 0.4rem; +} + +.tooltip .arrow::before { + position: absolute; + content: ""; + border-color: transparent; + border-style: solid; +} + +.bs-tooltip-top, .bs-tooltip-auto[x-placement^="top"] { + padding: 0.4rem 0; +} + +.bs-tooltip-top .arrow, .bs-tooltip-auto[x-placement^="top"] .arrow { + bottom: 0; +} + +.bs-tooltip-top .arrow::before, .bs-tooltip-auto[x-placement^="top"] .arrow::before { + top: 0; + border-width: 0.4rem 0.4rem 0; + border-top-color: #000; +} + +.bs-tooltip-right, .bs-tooltip-auto[x-placement^="right"] { + padding: 0 0.4rem; +} + +.bs-tooltip-right .arrow, .bs-tooltip-auto[x-placement^="right"] .arrow { + left: 0; + width: 0.4rem; + height: 0.8rem; +} + +.bs-tooltip-right .arrow::before, .bs-tooltip-auto[x-placement^="right"] .arrow::before { + right: 0; + border-width: 0.4rem 0.4rem 0.4rem 0; + border-right-color: #000; +} + +.bs-tooltip-bottom, .bs-tooltip-auto[x-placement^="bottom"] { + padding: 0.4rem 0; +} + +.bs-tooltip-bottom .arrow, .bs-tooltip-auto[x-placement^="bottom"] .arrow { + top: 0; +} + +.bs-tooltip-bottom .arrow::before, .bs-tooltip-auto[x-placement^="bottom"] .arrow::before { + bottom: 0; + border-width: 0 0.4rem 0.4rem; + border-bottom-color: #000; +} + +.bs-tooltip-left, .bs-tooltip-auto[x-placement^="left"] { + padding: 0 0.4rem; +} + +.bs-tooltip-left .arrow, .bs-tooltip-auto[x-placement^="left"] .arrow { + right: 0; + width: 0.4rem; + height: 0.8rem; +} + +.bs-tooltip-left .arrow::before, .bs-tooltip-auto[x-placement^="left"] .arrow::before { + left: 0; + border-width: 0.4rem 0 0.4rem 0.4rem; + border-left-color: #000; +} + +.tooltip-inner { + max-width: 200px; + padding: 0.25rem 0.5rem; + color: #fff; + text-align: center; + background-color: #000; + border-radius: 0.25rem; +} + +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1060; + display: block; + max-width: 276px; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + font-style: normal; + font-weight: 400; + line-height: 1.5; + text-align: left; + text-align: start; + text-decoration: none; + text-shadow: none; + text-transform: none; + letter-spacing: normal; + word-break: normal; + word-spacing: normal; + white-space: normal; + line-break: auto; + font-size: 0.875rem; + word-wrap: break-word; + background-color: #fff; + background-clip: padding-box; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 0.3rem; +} + +.popover .arrow { + position: absolute; + display: block; + width: 1rem; + height: 0.5rem; + margin: 0 0.3rem; +} + +.popover .arrow::before, .popover .arrow::after { + position: absolute; + display: block; + content: ""; + border-color: transparent; + border-style: solid; +} + +.bs-popover-top, .bs-popover-auto[x-placement^="top"] { + margin-bottom: 0.5rem; +} + +.bs-popover-top > .arrow, .bs-popover-auto[x-placement^="top"] > .arrow { + bottom: calc(-0.5rem - 1px); +} + +.bs-popover-top > .arrow::before, .bs-popover-auto[x-placement^="top"] > .arrow::before { + bottom: 0; + border-width: 0.5rem 0.5rem 0; + border-top-color: rgba(0, 0, 0, 0.25); +} + +.bs-popover-top > .arrow::after, .bs-popover-auto[x-placement^="top"] > .arrow::after { + bottom: 1px; + border-width: 0.5rem 0.5rem 0; + border-top-color: #fff; +} + +.bs-popover-right, .bs-popover-auto[x-placement^="right"] { + margin-left: 0.5rem; +} + +.bs-popover-right > .arrow, .bs-popover-auto[x-placement^="right"] > .arrow { + left: calc(-0.5rem - 1px); + width: 0.5rem; + height: 1rem; + margin: 0.3rem 0; +} + +.bs-popover-right > .arrow::before, .bs-popover-auto[x-placement^="right"] > .arrow::before { + left: 0; + border-width: 0.5rem 0.5rem 0.5rem 0; + border-right-color: rgba(0, 0, 0, 0.25); +} + +.bs-popover-right > .arrow::after, .bs-popover-auto[x-placement^="right"] > .arrow::after { + left: 1px; + border-width: 0.5rem 0.5rem 0.5rem 0; + border-right-color: #fff; +} + +.bs-popover-bottom, .bs-popover-auto[x-placement^="bottom"] { + margin-top: 0.5rem; +} + +.bs-popover-bottom > .arrow, .bs-popover-auto[x-placement^="bottom"] > .arrow { + top: calc(-0.5rem - 1px); +} + +.bs-popover-bottom > .arrow::before, .bs-popover-auto[x-placement^="bottom"] > .arrow::before { + top: 0; + border-width: 0 0.5rem 0.5rem 0.5rem; + border-bottom-color: rgba(0, 0, 0, 0.25); +} + +.bs-popover-bottom > .arrow::after, .bs-popover-auto[x-placement^="bottom"] > .arrow::after { + top: 1px; + border-width: 0 0.5rem 0.5rem 0.5rem; + border-bottom-color: #fff; +} + +.bs-popover-bottom .popover-header::before, .bs-popover-auto[x-placement^="bottom"] .popover-header::before { + position: absolute; + top: 0; + left: 50%; + display: block; + width: 1rem; + margin-left: -0.5rem; + content: ""; + border-bottom: 1px solid #f7f7f7; +} + +.bs-popover-left, .bs-popover-auto[x-placement^="left"] { + margin-right: 0.5rem; +} + +.bs-popover-left > .arrow, .bs-popover-auto[x-placement^="left"] > .arrow { + right: calc(-0.5rem - 1px); + width: 0.5rem; + height: 1rem; + margin: 0.3rem 0; +} + +.bs-popover-left > .arrow::before, .bs-popover-auto[x-placement^="left"] > .arrow::before { + right: 0; + border-width: 0.5rem 0 0.5rem 0.5rem; + border-left-color: rgba(0, 0, 0, 0.25); +} + +.bs-popover-left > .arrow::after, .bs-popover-auto[x-placement^="left"] > .arrow::after { + right: 1px; + border-width: 0.5rem 0 0.5rem 0.5rem; + border-left-color: #fff; +} + +.popover-header { + padding: 0.5rem 0.75rem; + margin-bottom: 0; + font-size: 1rem; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-top-left-radius: calc(0.3rem - 1px); + border-top-right-radius: calc(0.3rem - 1px); +} + +.popover-header:empty { + display: none; +} + +.popover-body { + padding: 0.5rem 0.75rem; + color: #212529; +} + +.carousel { + position: relative; +} + +.carousel.pointer-event { + -ms-touch-action: pan-y; + touch-action: pan-y; +} + +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; +} + +.carousel-inner::after { + display: block; + clear: both; + content: ""; +} + +.carousel-item { + position: relative; + display: none; + float: left; + width: 100%; + margin-right: -100%; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + transition: -webkit-transform 0.6s ease-in-out; + transition: transform 0.6s ease-in-out; + transition: transform 0.6s ease-in-out, -webkit-transform 0.6s ease-in-out; +} + +@media (prefers-reduced-motion: reduce) { + .carousel-item { + transition: none; + } +} + +.carousel-item.active, +.carousel-item-next, +.carousel-item-prev { + display: block; +} + +.carousel-item-next:not(.carousel-item-left), +.active.carousel-item-right { + -webkit-transform: translateX(100%); + transform: translateX(100%); +} + +.carousel-item-prev:not(.carousel-item-right), +.active.carousel-item-left { + -webkit-transform: translateX(-100%); + transform: translateX(-100%); +} + +.carousel-fade .carousel-item { + opacity: 0; + transition-property: opacity; + -webkit-transform: none; + transform: none; +} + +.carousel-fade .carousel-item.active, +.carousel-fade .carousel-item-next.carousel-item-left, +.carousel-fade .carousel-item-prev.carousel-item-right { + z-index: 1; + opacity: 1; +} + +.carousel-fade .active.carousel-item-left, +.carousel-fade .active.carousel-item-right { + z-index: 0; + opacity: 0; + transition: opacity 0s 0.6s; +} + +@media (prefers-reduced-motion: reduce) { + .carousel-fade .active.carousel-item-left, + .carousel-fade .active.carousel-item-right { + transition: none; + } +} + +.carousel-control-prev, +.carousel-control-next { + position: absolute; + top: 0; + bottom: 0; + z-index: 1; + display: -ms-flexbox; + display: flex; + -ms-flex-align: center; + align-items: center; + -ms-flex-pack: center; + justify-content: center; + width: 15%; + color: #fff; + text-align: center; + opacity: 0.5; + transition: opacity 0.15s ease; +} + +@media (prefers-reduced-motion: reduce) { + .carousel-control-prev, + .carousel-control-next { + transition: none; + } +} + +.carousel-control-prev:hover, .carousel-control-prev:focus, +.carousel-control-next:hover, +.carousel-control-next:focus { + color: #fff; + text-decoration: none; + outline: 0; + opacity: 0.9; +} + +.carousel-control-prev { + left: 0; +} + +.carousel-control-next { + right: 0; +} + +.carousel-control-prev-icon, +.carousel-control-next-icon { + display: inline-block; + width: 20px; + height: 20px; + background: no-repeat 50% / 100% 100%; +} + +.carousel-control-prev-icon { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M5.25 0l-4 4 4 4 1.5-1.5L4.25 4l2.5-2.5L5.25 0z'/%3e%3c/svg%3e"); +} + +.carousel-control-next-icon { + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='%23fff' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath d='M2.75 0l-1.5 1.5L3.75 4l-2.5 2.5L2.75 8l4-4-4-4z'/%3e%3c/svg%3e"); +} + +.carousel-indicators { + position: absolute; + right: 0; + bottom: 0; + left: 0; + z-index: 15; + display: -ms-flexbox; + display: flex; + -ms-flex-pack: center; + justify-content: center; + padding-left: 0; + margin-right: 15%; + margin-left: 15%; + list-style: none; +} + +.carousel-indicators li { + box-sizing: content-box; + -ms-flex: 0 1 auto; + flex: 0 1 auto; + width: 30px; + height: 3px; + margin-right: 3px; + margin-left: 3px; + text-indent: -999px; + cursor: pointer; + background-color: #fff; + background-clip: padding-box; + border-top: 10px solid transparent; + border-bottom: 10px solid transparent; + opacity: .5; + transition: opacity 0.6s ease; +} + +@media (prefers-reduced-motion: reduce) { + .carousel-indicators li { + transition: none; + } +} + +.carousel-indicators .active { + opacity: 1; +} + +.carousel-caption { + position: absolute; + right: 15%; + bottom: 20px; + left: 15%; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: #fff; + text-align: center; +} + +@-webkit-keyframes spinner-border { + to { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +@keyframes spinner-border { + to { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } +} + +.spinner-border { + display: inline-block; + width: 2rem; + height: 2rem; + vertical-align: text-bottom; + border: 0.25em solid currentColor; + border-right-color: transparent; + border-radius: 50%; + -webkit-animation: spinner-border .75s linear infinite; + animation: spinner-border .75s linear infinite; +} + +.spinner-border-sm { + width: 1rem; + height: 1rem; + border-width: 0.2em; +} + +@-webkit-keyframes spinner-grow { + 0% { + -webkit-transform: scale(0); + transform: scale(0); + } + 50% { + opacity: 1; + } +} + +@keyframes spinner-grow { + 0% { + -webkit-transform: scale(0); + transform: scale(0); + } + 50% { + opacity: 1; + } +} + +.spinner-grow { + display: inline-block; + width: 2rem; + height: 2rem; + vertical-align: text-bottom; + background-color: currentColor; + border-radius: 50%; + opacity: 0; + -webkit-animation: spinner-grow .75s linear infinite; + animation: spinner-grow .75s linear infinite; +} + +.spinner-grow-sm { + width: 1rem; + height: 1rem; +} + +.align-baseline { + vertical-align: baseline !important; +} + +.align-top { + vertical-align: top !important; +} + +.align-middle { + vertical-align: middle !important; +} + +.align-bottom { + vertical-align: bottom !important; +} + +.align-text-bottom { + vertical-align: text-bottom !important; +} + +.align-text-top { + vertical-align: text-top !important; +} + +.bg-primary { + background-color: #007bff !important; +} + +a.bg-primary:hover, a.bg-primary:focus, +button.bg-primary:hover, +button.bg-primary:focus { + background-color: #0062cc !important; +} + +.bg-secondary { + background-color: #6c757d !important; +} + +a.bg-secondary:hover, a.bg-secondary:focus, +button.bg-secondary:hover, +button.bg-secondary:focus { + background-color: #545b62 !important; +} + +.bg-success { + background-color: #28a745 !important; +} + +a.bg-success:hover, a.bg-success:focus, +button.bg-success:hover, +button.bg-success:focus { + background-color: #1e7e34 !important; +} + +.bg-info { + background-color: #17a2b8 !important; +} + +a.bg-info:hover, a.bg-info:focus, +button.bg-info:hover, +button.bg-info:focus { + background-color: #117a8b !important; +} + +.bg-warning { + background-color: #ffc107 !important; +} + +a.bg-warning:hover, a.bg-warning:focus, +button.bg-warning:hover, +button.bg-warning:focus { + background-color: #d39e00 !important; +} + +.bg-danger { + background-color: #dc3545 !important; +} + +a.bg-danger:hover, a.bg-danger:focus, +button.bg-danger:hover, +button.bg-danger:focus { + background-color: #bd2130 !important; +} + +.bg-light { + background-color: #f8f9fa !important; +} + +a.bg-light:hover, a.bg-light:focus, +button.bg-light:hover, +button.bg-light:focus { + background-color: #dae0e5 !important; +} + +.bg-dark { + background-color: #343a40 !important; +} + +a.bg-dark:hover, a.bg-dark:focus, +button.bg-dark:hover, +button.bg-dark:focus { + background-color: #1d2124 !important; +} + +.bg-white { + background-color: #fff !important; +} + +.bg-transparent { + background-color: transparent !important; +} + +.border { + border: 1px solid #dee2e6 !important; +} + +.border-top { + border-top: 1px solid #dee2e6 !important; +} + +.border-right { + border-right: 1px solid #dee2e6 !important; +} + +.border-bottom { + border-bottom: 1px solid #dee2e6 !important; +} + +.border-left { + border-left: 1px solid #dee2e6 !important; +} + +.border-0 { + border: 0 !important; +} + +.border-top-0 { + border-top: 0 !important; +} + +.border-right-0 { + border-right: 0 !important; +} + +.border-bottom-0 { + border-bottom: 0 !important; +} + +.border-left-0 { + border-left: 0 !important; +} + +.border-primary { + border-color: #007bff !important; +} + +.border-secondary { + border-color: #6c757d !important; +} + +.border-success { + border-color: #28a745 !important; +} + +.border-info { + border-color: #17a2b8 !important; +} + +.border-warning { + border-color: #ffc107 !important; +} + +.border-danger { + border-color: #dc3545 !important; +} + +.border-light { + border-color: #f8f9fa !important; +} + +.border-dark { + border-color: #343a40 !important; +} + +.border-white { + border-color: #fff !important; +} + +.rounded-sm { + border-radius: 0.2rem !important; +} + +.rounded { + border-radius: 0.25rem !important; +} + +.rounded-top { + border-top-left-radius: 0.25rem !important; + border-top-right-radius: 0.25rem !important; +} + +.rounded-right { + border-top-right-radius: 0.25rem !important; + border-bottom-right-radius: 0.25rem !important; +} + +.rounded-bottom { + border-bottom-right-radius: 0.25rem !important; + border-bottom-left-radius: 0.25rem !important; +} + +.rounded-left { + border-top-left-radius: 0.25rem !important; + border-bottom-left-radius: 0.25rem !important; +} + +.rounded-lg { + border-radius: 0.3rem !important; +} + +.rounded-circle { + border-radius: 50% !important; +} + +.rounded-pill { + border-radius: 50rem !important; +} + +.rounded-0 { + border-radius: 0 !important; +} + +.clearfix::after { + display: block; + clear: both; + content: ""; +} + +.d-none { + display: none !important; +} + +.d-inline { + display: inline !important; +} + +.d-inline-block { + display: inline-block !important; +} + +.d-block { + display: block !important; +} + +.d-table { + display: table !important; +} + +.d-table-row { + display: table-row !important; +} + +.d-table-cell { + display: table-cell !important; +} + +.d-flex { + display: -ms-flexbox !important; + display: flex !important; +} + +.d-inline-flex { + display: -ms-inline-flexbox !important; + display: inline-flex !important; +} + +@media (min-width: 576px) { + .d-sm-none { + display: none !important; + } + .d-sm-inline { + display: inline !important; + } + .d-sm-inline-block { + display: inline-block !important; + } + .d-sm-block { + display: block !important; + } + .d-sm-table { + display: table !important; + } + .d-sm-table-row { + display: table-row !important; + } + .d-sm-table-cell { + display: table-cell !important; + } + .d-sm-flex { + display: -ms-flexbox !important; + display: flex !important; + } + .d-sm-inline-flex { + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} + +@media (min-width: 768px) { + .d-md-none { + display: none !important; + } + .d-md-inline { + display: inline !important; + } + .d-md-inline-block { + display: inline-block !important; + } + .d-md-block { + display: block !important; + } + .d-md-table { + display: table !important; + } + .d-md-table-row { + display: table-row !important; + } + .d-md-table-cell { + display: table-cell !important; + } + .d-md-flex { + display: -ms-flexbox !important; + display: flex !important; + } + .d-md-inline-flex { + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} + +@media (min-width: 992px) { + .d-lg-none { + display: none !important; + } + .d-lg-inline { + display: inline !important; + } + .d-lg-inline-block { + display: inline-block !important; + } + .d-lg-block { + display: block !important; + } + .d-lg-table { + display: table !important; + } + .d-lg-table-row { + display: table-row !important; + } + .d-lg-table-cell { + display: table-cell !important; + } + .d-lg-flex { + display: -ms-flexbox !important; + display: flex !important; + } + .d-lg-inline-flex { + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} + +@media (min-width: 1200px) { + .d-xl-none { + display: none !important; + } + .d-xl-inline { + display: inline !important; + } + .d-xl-inline-block { + display: inline-block !important; + } + .d-xl-block { + display: block !important; + } + .d-xl-table { + display: table !important; + } + .d-xl-table-row { + display: table-row !important; + } + .d-xl-table-cell { + display: table-cell !important; + } + .d-xl-flex { + display: -ms-flexbox !important; + display: flex !important; + } + .d-xl-inline-flex { + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} + +@media print { + .d-print-none { + display: none !important; + } + .d-print-inline { + display: inline !important; + } + .d-print-inline-block { + display: inline-block !important; + } + .d-print-block { + display: block !important; + } + .d-print-table { + display: table !important; + } + .d-print-table-row { + display: table-row !important; + } + .d-print-table-cell { + display: table-cell !important; + } + .d-print-flex { + display: -ms-flexbox !important; + display: flex !important; + } + .d-print-inline-flex { + display: -ms-inline-flexbox !important; + display: inline-flex !important; + } +} + +.embed-responsive { + position: relative; + display: block; + width: 100%; + padding: 0; + overflow: hidden; +} + +.embed-responsive::before { + display: block; + content: ""; +} + +.embed-responsive .embed-responsive-item, +.embed-responsive iframe, +.embed-responsive embed, +.embed-responsive object, +.embed-responsive video { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; +} + +.embed-responsive-21by9::before { + padding-top: 42.857143%; +} + +.embed-responsive-16by9::before { + padding-top: 56.25%; +} + +.embed-responsive-4by3::before { + padding-top: 75%; +} + +.embed-responsive-1by1::before { + padding-top: 100%; +} + +.flex-row { + -ms-flex-direction: row !important; + flex-direction: row !important; +} + +.flex-column { + -ms-flex-direction: column !important; + flex-direction: column !important; +} + +.flex-row-reverse { + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; +} + +.flex-column-reverse { + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; +} + +.flex-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; +} + +.flex-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; +} + +.flex-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; +} + +.flex-fill { + -ms-flex: 1 1 auto !important; + flex: 1 1 auto !important; +} + +.flex-grow-0 { + -ms-flex-positive: 0 !important; + flex-grow: 0 !important; +} + +.flex-grow-1 { + -ms-flex-positive: 1 !important; + flex-grow: 1 !important; +} + +.flex-shrink-0 { + -ms-flex-negative: 0 !important; + flex-shrink: 0 !important; +} + +.flex-shrink-1 { + -ms-flex-negative: 1 !important; + flex-shrink: 1 !important; +} + +.justify-content-start { + -ms-flex-pack: start !important; + justify-content: flex-start !important; +} + +.justify-content-end { + -ms-flex-pack: end !important; + justify-content: flex-end !important; +} + +.justify-content-center { + -ms-flex-pack: center !important; + justify-content: center !important; +} + +.justify-content-between { + -ms-flex-pack: justify !important; + justify-content: space-between !important; +} + +.justify-content-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; +} + +.align-items-start { + -ms-flex-align: start !important; + align-items: flex-start !important; +} + +.align-items-end { + -ms-flex-align: end !important; + align-items: flex-end !important; +} + +.align-items-center { + -ms-flex-align: center !important; + align-items: center !important; +} + +.align-items-baseline { + -ms-flex-align: baseline !important; + align-items: baseline !important; +} + +.align-items-stretch { + -ms-flex-align: stretch !important; + align-items: stretch !important; +} + +.align-content-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; +} + +.align-content-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; +} + +.align-content-center { + -ms-flex-line-pack: center !important; + align-content: center !important; +} + +.align-content-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; +} + +.align-content-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; +} + +.align-content-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; +} + +.align-self-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; +} + +.align-self-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; +} + +.align-self-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; +} + +.align-self-center { + -ms-flex-item-align: center !important; + align-self: center !important; +} + +.align-self-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; +} + +.align-self-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; +} + +@media (min-width: 576px) { + .flex-sm-row { + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-sm-column { + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-sm-row-reverse { + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-sm-column-reverse { + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-sm-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-sm-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-sm-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .flex-sm-fill { + -ms-flex: 1 1 auto !important; + flex: 1 1 auto !important; + } + .flex-sm-grow-0 { + -ms-flex-positive: 0 !important; + flex-grow: 0 !important; + } + .flex-sm-grow-1 { + -ms-flex-positive: 1 !important; + flex-grow: 1 !important; + } + .flex-sm-shrink-0 { + -ms-flex-negative: 0 !important; + flex-shrink: 0 !important; + } + .flex-sm-shrink-1 { + -ms-flex-negative: 1 !important; + flex-shrink: 1 !important; + } + .justify-content-sm-start { + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-sm-end { + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-sm-center { + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-sm-between { + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-sm-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-sm-start { + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-sm-end { + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-sm-center { + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-sm-baseline { + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-sm-stretch { + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-sm-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-sm-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-sm-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-sm-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-sm-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-sm-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-sm-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-sm-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-sm-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-sm-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-sm-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-sm-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} + +@media (min-width: 768px) { + .flex-md-row { + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-md-column { + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-md-row-reverse { + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-md-column-reverse { + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-md-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-md-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-md-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .flex-md-fill { + -ms-flex: 1 1 auto !important; + flex: 1 1 auto !important; + } + .flex-md-grow-0 { + -ms-flex-positive: 0 !important; + flex-grow: 0 !important; + } + .flex-md-grow-1 { + -ms-flex-positive: 1 !important; + flex-grow: 1 !important; + } + .flex-md-shrink-0 { + -ms-flex-negative: 0 !important; + flex-shrink: 0 !important; + } + .flex-md-shrink-1 { + -ms-flex-negative: 1 !important; + flex-shrink: 1 !important; + } + .justify-content-md-start { + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-md-end { + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-md-center { + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-md-between { + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-md-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-md-start { + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-md-end { + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-md-center { + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-md-baseline { + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-md-stretch { + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-md-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-md-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-md-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-md-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-md-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-md-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-md-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-md-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-md-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-md-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-md-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-md-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} + +@media (min-width: 992px) { + .flex-lg-row { + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-lg-column { + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-lg-row-reverse { + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-lg-column-reverse { + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-lg-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-lg-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-lg-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .flex-lg-fill { + -ms-flex: 1 1 auto !important; + flex: 1 1 auto !important; + } + .flex-lg-grow-0 { + -ms-flex-positive: 0 !important; + flex-grow: 0 !important; + } + .flex-lg-grow-1 { + -ms-flex-positive: 1 !important; + flex-grow: 1 !important; + } + .flex-lg-shrink-0 { + -ms-flex-negative: 0 !important; + flex-shrink: 0 !important; + } + .flex-lg-shrink-1 { + -ms-flex-negative: 1 !important; + flex-shrink: 1 !important; + } + .justify-content-lg-start { + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-lg-end { + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-lg-center { + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-lg-between { + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-lg-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-lg-start { + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-lg-end { + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-lg-center { + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-lg-baseline { + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-lg-stretch { + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-lg-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-lg-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-lg-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-lg-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-lg-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-lg-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-lg-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-lg-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-lg-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-lg-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-lg-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-lg-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} + +@media (min-width: 1200px) { + .flex-xl-row { + -ms-flex-direction: row !important; + flex-direction: row !important; + } + .flex-xl-column { + -ms-flex-direction: column !important; + flex-direction: column !important; + } + .flex-xl-row-reverse { + -ms-flex-direction: row-reverse !important; + flex-direction: row-reverse !important; + } + .flex-xl-column-reverse { + -ms-flex-direction: column-reverse !important; + flex-direction: column-reverse !important; + } + .flex-xl-wrap { + -ms-flex-wrap: wrap !important; + flex-wrap: wrap !important; + } + .flex-xl-nowrap { + -ms-flex-wrap: nowrap !important; + flex-wrap: nowrap !important; + } + .flex-xl-wrap-reverse { + -ms-flex-wrap: wrap-reverse !important; + flex-wrap: wrap-reverse !important; + } + .flex-xl-fill { + -ms-flex: 1 1 auto !important; + flex: 1 1 auto !important; + } + .flex-xl-grow-0 { + -ms-flex-positive: 0 !important; + flex-grow: 0 !important; + } + .flex-xl-grow-1 { + -ms-flex-positive: 1 !important; + flex-grow: 1 !important; + } + .flex-xl-shrink-0 { + -ms-flex-negative: 0 !important; + flex-shrink: 0 !important; + } + .flex-xl-shrink-1 { + -ms-flex-negative: 1 !important; + flex-shrink: 1 !important; + } + .justify-content-xl-start { + -ms-flex-pack: start !important; + justify-content: flex-start !important; + } + .justify-content-xl-end { + -ms-flex-pack: end !important; + justify-content: flex-end !important; + } + .justify-content-xl-center { + -ms-flex-pack: center !important; + justify-content: center !important; + } + .justify-content-xl-between { + -ms-flex-pack: justify !important; + justify-content: space-between !important; + } + .justify-content-xl-around { + -ms-flex-pack: distribute !important; + justify-content: space-around !important; + } + .align-items-xl-start { + -ms-flex-align: start !important; + align-items: flex-start !important; + } + .align-items-xl-end { + -ms-flex-align: end !important; + align-items: flex-end !important; + } + .align-items-xl-center { + -ms-flex-align: center !important; + align-items: center !important; + } + .align-items-xl-baseline { + -ms-flex-align: baseline !important; + align-items: baseline !important; + } + .align-items-xl-stretch { + -ms-flex-align: stretch !important; + align-items: stretch !important; + } + .align-content-xl-start { + -ms-flex-line-pack: start !important; + align-content: flex-start !important; + } + .align-content-xl-end { + -ms-flex-line-pack: end !important; + align-content: flex-end !important; + } + .align-content-xl-center { + -ms-flex-line-pack: center !important; + align-content: center !important; + } + .align-content-xl-between { + -ms-flex-line-pack: justify !important; + align-content: space-between !important; + } + .align-content-xl-around { + -ms-flex-line-pack: distribute !important; + align-content: space-around !important; + } + .align-content-xl-stretch { + -ms-flex-line-pack: stretch !important; + align-content: stretch !important; + } + .align-self-xl-auto { + -ms-flex-item-align: auto !important; + align-self: auto !important; + } + .align-self-xl-start { + -ms-flex-item-align: start !important; + align-self: flex-start !important; + } + .align-self-xl-end { + -ms-flex-item-align: end !important; + align-self: flex-end !important; + } + .align-self-xl-center { + -ms-flex-item-align: center !important; + align-self: center !important; + } + .align-self-xl-baseline { + -ms-flex-item-align: baseline !important; + align-self: baseline !important; + } + .align-self-xl-stretch { + -ms-flex-item-align: stretch !important; + align-self: stretch !important; + } +} + +.float-left { + float: left !important; +} + +.float-right { + float: right !important; +} + +.float-none { + float: none !important; +} + +@media (min-width: 576px) { + .float-sm-left { + float: left !important; + } + .float-sm-right { + float: right !important; + } + .float-sm-none { + float: none !important; + } +} + +@media (min-width: 768px) { + .float-md-left { + float: left !important; + } + .float-md-right { + float: right !important; + } + .float-md-none { + float: none !important; + } +} + +@media (min-width: 992px) { + .float-lg-left { + float: left !important; + } + .float-lg-right { + float: right !important; + } + .float-lg-none { + float: none !important; + } +} + +@media (min-width: 1200px) { + .float-xl-left { + float: left !important; + } + .float-xl-right { + float: right !important; + } + .float-xl-none { + float: none !important; + } +} + +.overflow-auto { + overflow: auto !important; +} + +.overflow-hidden { + overflow: hidden !important; +} + +.position-static { + position: static !important; +} + +.position-relative { + position: relative !important; +} + +.position-absolute { + position: absolute !important; +} + +.position-fixed { + position: fixed !important; +} + +.position-sticky { + position: -webkit-sticky !important; + position: sticky !important; +} + +.fixed-top { + position: fixed; + top: 0; + right: 0; + left: 0; + z-index: 1030; +} + +.fixed-bottom { + position: fixed; + right: 0; + bottom: 0; + left: 0; + z-index: 1030; +} + +@supports ((position: -webkit-sticky) or (position: sticky)) { + .sticky-top { + position: -webkit-sticky; + position: sticky; + top: 0; + z-index: 1020; + } +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.sr-only-focusable:active, .sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + overflow: visible; + clip: auto; + white-space: normal; +} + +.shadow-sm { + box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075) !important; +} + +.shadow { + box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15) !important; +} + +.shadow-lg { + box-shadow: 0 1rem 3rem rgba(0, 0, 0, 0.175) !important; +} + +.shadow-none { + box-shadow: none !important; +} + +.w-25 { + width: 25% !important; +} + +.w-50 { + width: 50% !important; +} + +.w-75 { + width: 75% !important; +} + +.w-100 { + width: 100% !important; +} + +.w-auto { + width: auto !important; +} + +.h-25 { + height: 25% !important; +} + +.h-50 { + height: 50% !important; +} + +.h-75 { + height: 75% !important; +} + +.h-100 { + height: 100% !important; +} + +.h-auto { + height: auto !important; +} + +.mw-100 { + max-width: 100% !important; +} + +.mh-100 { + max-height: 100% !important; +} + +.min-vw-100 { + min-width: 100vw !important; +} + +.min-vh-100 { + min-height: 100vh !important; +} + +.vw-100 { + width: 100vw !important; +} + +.vh-100 { + height: 100vh !important; +} + +.stretched-link::after { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1; + pointer-events: auto; + content: ""; + background-color: rgba(0, 0, 0, 0); +} + +.m-0 { + margin: 0 !important; +} + +.mt-0, +.my-0 { + margin-top: 0 !important; +} + +.mr-0, +.mx-0 { + margin-right: 0 !important; +} + +.mb-0, +.my-0 { + margin-bottom: 0 !important; +} + +.ml-0, +.mx-0 { + margin-left: 0 !important; +} + +.m-1 { + margin: 0.25rem !important; +} + +.mt-1, +.my-1 { + margin-top: 0.25rem !important; +} + +.mr-1, +.mx-1 { + margin-right: 0.25rem !important; +} + +.mb-1, +.my-1 { + margin-bottom: 0.25rem !important; +} + +.ml-1, +.mx-1 { + margin-left: 0.25rem !important; +} + +.m-2 { + margin: 0.5rem !important; +} + +.mt-2, +.my-2 { + margin-top: 0.5rem !important; +} + +.mr-2, +.mx-2 { + margin-right: 0.5rem !important; +} + +.mb-2, +.my-2 { + margin-bottom: 0.5rem !important; +} + +.ml-2, +.mx-2 { + margin-left: 0.5rem !important; +} + +.m-3 { + margin: 1rem !important; +} + +.mt-3, +.my-3 { + margin-top: 1rem !important; +} + +.mr-3, +.mx-3 { + margin-right: 1rem !important; +} + +.mb-3, +.my-3 { + margin-bottom: 1rem !important; +} + +.ml-3, +.mx-3 { + margin-left: 1rem !important; +} + +.m-4 { + margin: 1.5rem !important; +} + +.mt-4, +.my-4 { + margin-top: 1.5rem !important; +} + +.mr-4, +.mx-4 { + margin-right: 1.5rem !important; +} + +.mb-4, +.my-4 { + margin-bottom: 1.5rem !important; +} + +.ml-4, +.mx-4 { + margin-left: 1.5rem !important; +} + +.m-5 { + margin: 3rem !important; +} + +.mt-5, +.my-5 { + margin-top: 3rem !important; +} + +.mr-5, +.mx-5 { + margin-right: 3rem !important; +} + +.mb-5, +.my-5 { + margin-bottom: 3rem !important; +} + +.ml-5, +.mx-5 { + margin-left: 3rem !important; +} + +.p-0 { + padding: 0 !important; +} + +.pt-0, +.py-0 { + padding-top: 0 !important; +} + +.pr-0, +.px-0 { + padding-right: 0 !important; +} + +.pb-0, +.py-0 { + padding-bottom: 0 !important; +} + +.pl-0, +.px-0 { + padding-left: 0 !important; +} + +.p-1 { + padding: 0.25rem !important; +} + +.pt-1, +.py-1 { + padding-top: 0.25rem !important; +} + +.pr-1, +.px-1 { + padding-right: 0.25rem !important; +} + +.pb-1, +.py-1 { + padding-bottom: 0.25rem !important; +} + +.pl-1, +.px-1 { + padding-left: 0.25rem !important; +} + +.p-2 { + padding: 0.5rem !important; +} + +.pt-2, +.py-2 { + padding-top: 0.5rem !important; +} + +.pr-2, +.px-2 { + padding-right: 0.5rem !important; +} + +.pb-2, +.py-2 { + padding-bottom: 0.5rem !important; +} + +.pl-2, +.px-2 { + padding-left: 0.5rem !important; +} + +.p-3 { + padding: 1rem !important; +} + +.pt-3, +.py-3 { + padding-top: 1rem !important; +} + +.pr-3, +.px-3 { + padding-right: 1rem !important; +} + +.pb-3, +.py-3 { + padding-bottom: 1rem !important; +} + +.pl-3, +.px-3 { + padding-left: 1rem !important; +} + +.p-4 { + padding: 1.5rem !important; +} + +.pt-4, +.py-4 { + padding-top: 1.5rem !important; +} + +.pr-4, +.px-4 { + padding-right: 1.5rem !important; +} + +.pb-4, +.py-4 { + padding-bottom: 1.5rem !important; +} + +.pl-4, +.px-4 { + padding-left: 1.5rem !important; +} + +.p-5 { + padding: 3rem !important; +} + +.pt-5, +.py-5 { + padding-top: 3rem !important; +} + +.pr-5, +.px-5 { + padding-right: 3rem !important; +} + +.pb-5, +.py-5 { + padding-bottom: 3rem !important; +} + +.pl-5, +.px-5 { + padding-left: 3rem !important; +} + +.m-n1 { + margin: -0.25rem !important; +} + +.mt-n1, +.my-n1 { + margin-top: -0.25rem !important; +} + +.mr-n1, +.mx-n1 { + margin-right: -0.25rem !important; +} + +.mb-n1, +.my-n1 { + margin-bottom: -0.25rem !important; +} + +.ml-n1, +.mx-n1 { + margin-left: -0.25rem !important; +} + +.m-n2 { + margin: -0.5rem !important; +} + +.mt-n2, +.my-n2 { + margin-top: -0.5rem !important; +} + +.mr-n2, +.mx-n2 { + margin-right: -0.5rem !important; +} + +.mb-n2, +.my-n2 { + margin-bottom: -0.5rem !important; +} + +.ml-n2, +.mx-n2 { + margin-left: -0.5rem !important; +} + +.m-n3 { + margin: -1rem !important; +} + +.mt-n3, +.my-n3 { + margin-top: -1rem !important; +} + +.mr-n3, +.mx-n3 { + margin-right: -1rem !important; +} + +.mb-n3, +.my-n3 { + margin-bottom: -1rem !important; +} + +.ml-n3, +.mx-n3 { + margin-left: -1rem !important; +} + +.m-n4 { + margin: -1.5rem !important; +} + +.mt-n4, +.my-n4 { + margin-top: -1.5rem !important; +} + +.mr-n4, +.mx-n4 { + margin-right: -1.5rem !important; +} + +.mb-n4, +.my-n4 { + margin-bottom: -1.5rem !important; +} + +.ml-n4, +.mx-n4 { + margin-left: -1.5rem !important; +} + +.m-n5 { + margin: -3rem !important; +} + +.mt-n5, +.my-n5 { + margin-top: -3rem !important; +} + +.mr-n5, +.mx-n5 { + margin-right: -3rem !important; +} + +.mb-n5, +.my-n5 { + margin-bottom: -3rem !important; +} + +.ml-n5, +.mx-n5 { + margin-left: -3rem !important; +} + +.m-auto { + margin: auto !important; +} + +.mt-auto, +.my-auto { + margin-top: auto !important; +} + +.mr-auto, +.mx-auto { + margin-right: auto !important; +} + +.mb-auto, +.my-auto { + margin-bottom: auto !important; +} + +.ml-auto, +.mx-auto { + margin-left: auto !important; +} + + +@media (min-width: 576px) { + .m-sm-0 { + margin: 0 !important; + } + .mt-sm-0, + .my-sm-0 { + margin-top: 0 !important; + } + .mr-sm-0, + .mx-sm-0 { + margin-right: 0 !important; + } + .mb-sm-0, + .my-sm-0 { + margin-bottom: 0 !important; + } + .ml-sm-0, + .mx-sm-0 { + margin-left: 0 !important; + } + .m-sm-1 { + margin: 0.25rem !important; + } + .mt-sm-1, + .my-sm-1 { + margin-top: 0.25rem !important; + } + .mr-sm-1, + .mx-sm-1 { + margin-right: 0.25rem !important; + } + .mb-sm-1, + .my-sm-1 { + margin-bottom: 0.25rem !important; + } + .ml-sm-1, + .mx-sm-1 { + margin-left: 0.25rem !important; + } + .m-sm-2 { + margin: 0.5rem !important; + } + .mt-sm-2, + .my-sm-2 { + margin-top: 0.5rem !important; + } + .mr-sm-2, + .mx-sm-2 { + margin-right: 0.5rem !important; + } + .mb-sm-2, + .my-sm-2 { + margin-bottom: 0.5rem !important; + } + .ml-sm-2, + .mx-sm-2 { + margin-left: 0.5rem !important; + } + .m-sm-3 { + margin: 1rem !important; + } + .mt-sm-3, + .my-sm-3 { + margin-top: 1rem !important; + } + .mr-sm-3, + .mx-sm-3 { + margin-right: 1rem !important; + } + .mb-sm-3, + .my-sm-3 { + margin-bottom: 1rem !important; + } + .ml-sm-3, + .mx-sm-3 { + margin-left: 1rem !important; + } + .m-sm-4 { + margin: 1.5rem !important; + } + .mt-sm-4, + .my-sm-4 { + margin-top: 1.5rem !important; + } + .mr-sm-4, + .mx-sm-4 { + margin-right: 1.5rem !important; + } + .mb-sm-4, + .my-sm-4 { + margin-bottom: 1.5rem !important; + } + .ml-sm-4, + .mx-sm-4 { + margin-left: 1.5rem !important; + } + .m-sm-5 { + margin: 3rem !important; + } + .mt-sm-5, + .my-sm-5 { + margin-top: 3rem !important; + } + .mr-sm-5, + .mx-sm-5 { + margin-right: 3rem !important; + } + .mb-sm-5, + .my-sm-5 { + margin-bottom: 3rem !important; + } + .ml-sm-5, + .mx-sm-5 { + margin-left: 3rem !important; + } + .p-sm-0 { + padding: 0 !important; + } + .pt-sm-0, + .py-sm-0 { + padding-top: 0 !important; + } + .pr-sm-0, + .px-sm-0 { + padding-right: 0 !important; + } + .pb-sm-0, + .py-sm-0 { + padding-bottom: 0 !important; + } + .pl-sm-0, + .px-sm-0 { + padding-left: 0 !important; + } + .p-sm-1 { + padding: 0.25rem !important; + } + .pt-sm-1, + .py-sm-1 { + padding-top: 0.25rem !important; + } + .pr-sm-1, + .px-sm-1 { + padding-right: 0.25rem !important; + } + .pb-sm-1, + .py-sm-1 { + padding-bottom: 0.25rem !important; + } + .pl-sm-1, + .px-sm-1 { + padding-left: 0.25rem !important; + } + .p-sm-2 { + padding: 0.5rem !important; + } + .pt-sm-2, + .py-sm-2 { + padding-top: 0.5rem !important; + } + .pr-sm-2, + .px-sm-2 { + padding-right: 0.5rem !important; + } + .pb-sm-2, + .py-sm-2 { + padding-bottom: 0.5rem !important; + } + .pl-sm-2, + .px-sm-2 { + padding-left: 0.5rem !important; + } + .p-sm-3 { + padding: 1rem !important; + } + .pt-sm-3, + .py-sm-3 { + padding-top: 1rem !important; + } + .pr-sm-3, + .px-sm-3 { + padding-right: 1rem !important; + } + .pb-sm-3, + .py-sm-3 { + padding-bottom: 1rem !important; + } + .pl-sm-3, + .px-sm-3 { + padding-left: 1rem !important; + } + .p-sm-4 { + padding: 1.5rem !important; + } + .pt-sm-4, + .py-sm-4 { + padding-top: 1.5rem !important; + } + .pr-sm-4, + .px-sm-4 { + padding-right: 1.5rem !important; + } + .pb-sm-4, + .py-sm-4 { + padding-bottom: 1.5rem !important; + } + .pl-sm-4, + .px-sm-4 { + padding-left: 1.5rem !important; + } + .p-sm-5 { + padding: 3rem !important; + } + .pt-sm-5, + .py-sm-5 { + padding-top: 3rem !important; + } + .pr-sm-5, + .px-sm-5 { + padding-right: 3rem !important; + } + .pb-sm-5, + .py-sm-5 { + padding-bottom: 3rem !important; + } + .pl-sm-5, + .px-sm-5 { + padding-left: 3rem !important; + } + .m-sm-n1 { + margin: -0.25rem !important; + } + .mt-sm-n1, + .my-sm-n1 { + margin-top: -0.25rem !important; + } + .mr-sm-n1, + .mx-sm-n1 { + margin-right: -0.25rem !important; + } + .mb-sm-n1, + .my-sm-n1 { + margin-bottom: -0.25rem !important; + } + .ml-sm-n1, + .mx-sm-n1 { + margin-left: -0.25rem !important; + } + .m-sm-n2 { + margin: -0.5rem !important; + } + .mt-sm-n2, + .my-sm-n2 { + margin-top: -0.5rem !important; + } + .mr-sm-n2, + .mx-sm-n2 { + margin-right: -0.5rem !important; + } + .mb-sm-n2, + .my-sm-n2 { + margin-bottom: -0.5rem !important; + } + .ml-sm-n2, + .mx-sm-n2 { + margin-left: -0.5rem !important; + } + .m-sm-n3 { + margin: -1rem !important; + } + .mt-sm-n3, + .my-sm-n3 { + margin-top: -1rem !important; + } + .mr-sm-n3, + .mx-sm-n3 { + margin-right: -1rem !important; + } + .mb-sm-n3, + .my-sm-n3 { + margin-bottom: -1rem !important; + } + .ml-sm-n3, + .mx-sm-n3 { + margin-left: -1rem !important; + } + .m-sm-n4 { + margin: -1.5rem !important; + } + .mt-sm-n4, + .my-sm-n4 { + margin-top: -1.5rem !important; + } + .mr-sm-n4, + .mx-sm-n4 { + margin-right: -1.5rem !important; + } + .mb-sm-n4, + .my-sm-n4 { + margin-bottom: -1.5rem !important; + } + .ml-sm-n4, + .mx-sm-n4 { + margin-left: -1.5rem !important; + } + .m-sm-n5 { + margin: -3rem !important; + } + .mt-sm-n5, + .my-sm-n5 { + margin-top: -3rem !important; + } + .mr-sm-n5, + .mx-sm-n5 { + margin-right: -3rem !important; + } + .mb-sm-n5, + .my-sm-n5 { + margin-bottom: -3rem !important; + } + .ml-sm-n5, + .mx-sm-n5 { + margin-left: -3rem !important; + } + .m-sm-auto { + margin: auto !important; + } + .mt-sm-auto, + .my-sm-auto { + margin-top: auto !important; + } + .mr-sm-auto, + .mx-sm-auto { + margin-right: auto !important; + } + .mb-sm-auto, + .my-sm-auto { + margin-bottom: auto !important; + } + .ml-sm-auto, + .mx-sm-auto { + margin-left: auto !important; + } +} + +@media (min-width: 768px) { + .m-md-0 { + margin: 0 !important; + } + .mt-md-0, + .my-md-0 { + margin-top: 0 !important; + } + .mr-md-0, + .mx-md-0 { + margin-right: 0 !important; + } + .mb-md-0, + .my-md-0 { + margin-bottom: 0 !important; + } + .ml-md-0, + .mx-md-0 { + margin-left: 0 !important; + } + .m-md-1 { + margin: 0.25rem !important; + } + .mt-md-1, + .my-md-1 { + margin-top: 0.25rem !important; + } + .mr-md-1, + .mx-md-1 { + margin-right: 0.25rem !important; + } + .mb-md-1, + .my-md-1 { + margin-bottom: 0.25rem !important; + } + .ml-md-1, + .mx-md-1 { + margin-left: 0.25rem !important; + } + .m-md-2 { + margin: 0.5rem !important; + } + .mt-md-2, + .my-md-2 { + margin-top: 0.5rem !important; + } + .mr-md-2, + .mx-md-2 { + margin-right: 0.5rem !important; + } + .mb-md-2, + .my-md-2 { + margin-bottom: 0.5rem !important; + } + .ml-md-2, + .mx-md-2 { + margin-left: 0.5rem !important; + } + .m-md-3 { + margin: 1rem !important; + } + .mt-md-3, + .my-md-3 { + margin-top: 1rem !important; + } + .mr-md-3, + .mx-md-3 { + margin-right: 1rem !important; + } + .mb-md-3, + .my-md-3 { + margin-bottom: 1rem !important; + } + .ml-md-3, + .mx-md-3 { + margin-left: 1rem !important; + } + .m-md-4 { + margin: 1.5rem !important; + } + .mt-md-4, + .my-md-4 { + margin-top: 1.5rem !important; + } + .mr-md-4, + .mx-md-4 { + margin-right: 1.5rem !important; + } + .mb-md-4, + .my-md-4 { + margin-bottom: 1.5rem !important; + } + .ml-md-4, + .mx-md-4 { + margin-left: 1.5rem !important; + } + .m-md-5 { + margin: 3rem !important; + } + .mt-md-5, + .my-md-5 { + margin-top: 3rem !important; + } + .mr-md-5, + .mx-md-5 { + margin-right: 3rem !important; + } + .mb-md-5, + .my-md-5 { + margin-bottom: 3rem !important; + } + .ml-md-5, + .mx-md-5 { + margin-left: 3rem !important; + } + .p-md-0 { + padding: 0 !important; + } + .pt-md-0, + .py-md-0 { + padding-top: 0 !important; + } + .pr-md-0, + .px-md-0 { + padding-right: 0 !important; + } + .pb-md-0, + .py-md-0 { + padding-bottom: 0 !important; + } + .pl-md-0, + .px-md-0 { + padding-left: 0 !important; + } + .p-md-1 { + padding: 0.25rem !important; + } + .pt-md-1, + .py-md-1 { + padding-top: 0.25rem !important; + } + .pr-md-1, + .px-md-1 { + padding-right: 0.25rem !important; + } + .pb-md-1, + .py-md-1 { + padding-bottom: 0.25rem !important; + } + .pl-md-1, + .px-md-1 { + padding-left: 0.25rem !important; + } + .p-md-2 { + padding: 0.5rem !important; + } + .pt-md-2, + .py-md-2 { + padding-top: 0.5rem !important; + } + .pr-md-2, + .px-md-2 { + padding-right: 0.5rem !important; + } + .pb-md-2, + .py-md-2 { + padding-bottom: 0.5rem !important; + } + .pl-md-2, + .px-md-2 { + padding-left: 0.5rem !important; + } + .p-md-3 { + padding: 1rem !important; + } + .pt-md-3, + .py-md-3 { + padding-top: 1rem !important; + } + .pr-md-3, + .px-md-3 { + padding-right: 1rem !important; + } + .pb-md-3, + .py-md-3 { + padding-bottom: 1rem !important; + } + .pl-md-3, + .px-md-3 { + padding-left: 1rem !important; + } + .p-md-4 { + padding: 1.5rem !important; + } + .pt-md-4, + .py-md-4 { + padding-top: 1.5rem !important; + } + .pr-md-4, + .px-md-4 { + padding-right: 1.5rem !important; + } + .pb-md-4, + .py-md-4 { + padding-bottom: 1.5rem !important; + } + .pl-md-4, + .px-md-4 { + padding-left: 1.5rem !important; + } + .p-md-5 { + padding: 3rem !important; + } + .pt-md-5, + .py-md-5 { + padding-top: 3rem !important; + } + .pr-md-5, + .px-md-5 { + padding-right: 3rem !important; + } + .pb-md-5, + .py-md-5 { + padding-bottom: 3rem !important; + } + .pl-md-5, + .px-md-5 { + padding-left: 3rem !important; + } + .m-md-n1 { + margin: -0.25rem !important; + } + .mt-md-n1, + .my-md-n1 { + margin-top: -0.25rem !important; + } + .mr-md-n1, + .mx-md-n1 { + margin-right: -0.25rem !important; + } + .mb-md-n1, + .my-md-n1 { + margin-bottom: -0.25rem !important; + } + .ml-md-n1, + .mx-md-n1 { + margin-left: -0.25rem !important; + } + .m-md-n2 { + margin: -0.5rem !important; + } + .mt-md-n2, + .my-md-n2 { + margin-top: -0.5rem !important; + } + .mr-md-n2, + .mx-md-n2 { + margin-right: -0.5rem !important; + } + .mb-md-n2, + .my-md-n2 { + margin-bottom: -0.5rem !important; + } + .ml-md-n2, + .mx-md-n2 { + margin-left: -0.5rem !important; + } + .m-md-n3 { + margin: -1rem !important; + } + .mt-md-n3, + .my-md-n3 { + margin-top: -1rem !important; + } + .mr-md-n3, + .mx-md-n3 { + margin-right: -1rem !important; + } + .mb-md-n3, + .my-md-n3 { + margin-bottom: -1rem !important; + } + .ml-md-n3, + .mx-md-n3 { + margin-left: -1rem !important; + } + .m-md-n4 { + margin: -1.5rem !important; + } + .mt-md-n4, + .my-md-n4 { + margin-top: -1.5rem !important; + } + .mr-md-n4, + .mx-md-n4 { + margin-right: -1.5rem !important; + } + .mb-md-n4, + .my-md-n4 { + margin-bottom: -1.5rem !important; + } + .ml-md-n4, + .mx-md-n4 { + margin-left: -1.5rem !important; + } + .m-md-n5 { + margin: -3rem !important; + } + .mt-md-n5, + .my-md-n5 { + margin-top: -3rem !important; + } + .mr-md-n5, + .mx-md-n5 { + margin-right: -3rem !important; + } + .mb-md-n5, + .my-md-n5 { + margin-bottom: -3rem !important; + } + .ml-md-n5, + .mx-md-n5 { + margin-left: -3rem !important; + } + .m-md-auto { + margin: auto !important; + } + .mt-md-auto, + .my-md-auto { + margin-top: auto !important; + } + .mr-md-auto, + .mx-md-auto { + margin-right: auto !important; + } + .mb-md-auto, + .my-md-auto { + margin-bottom: auto !important; + } + .ml-md-auto, + .mx-md-auto { + margin-left: auto !important; + } +} + +@media (min-width: 992px) { + .m-lg-0 { + margin: 0 !important; + } + .mt-lg-0, + .my-lg-0 { + margin-top: 0 !important; + } + .mr-lg-0, + .mx-lg-0 { + margin-right: 0 !important; + } + .mb-lg-0, + .my-lg-0 { + margin-bottom: 0 !important; + } + .ml-lg-0, + .mx-lg-0 { + margin-left: 0 !important; + } + .m-lg-1 { + margin: 0.25rem !important; + } + .mt-lg-1, + .my-lg-1 { + margin-top: 0.25rem !important; + } + .mr-lg-1, + .mx-lg-1 { + margin-right: 0.25rem !important; + } + .mb-lg-1, + .my-lg-1 { + margin-bottom: 0.25rem !important; + } + .ml-lg-1, + .mx-lg-1 { + margin-left: 0.25rem !important; + } + .m-lg-2 { + margin: 0.5rem !important; + } + .mt-lg-2, + .my-lg-2 { + margin-top: 0.5rem !important; + } + .mr-lg-2, + .mx-lg-2 { + margin-right: 0.5rem !important; + } + .mb-lg-2, + .my-lg-2 { + margin-bottom: 0.5rem !important; + } + .ml-lg-2, + .mx-lg-2 { + margin-left: 0.5rem !important; + } + .m-lg-3 { + margin: 1rem !important; + } + .mt-lg-3, + .my-lg-3 { + margin-top: 1rem !important; + } + .mr-lg-3, + .mx-lg-3 { + margin-right: 1rem !important; + } + .mb-lg-3, + .my-lg-3 { + margin-bottom: 1rem !important; + } + .ml-lg-3, + .mx-lg-3 { + margin-left: 1rem !important; + } + .m-lg-4 { + margin: 1.5rem !important; + } + .mt-lg-4, + .my-lg-4 { + margin-top: 1.5rem !important; + } + .mr-lg-4, + .mx-lg-4 { + margin-right: 1.5rem !important; + } + .mb-lg-4, + .my-lg-4 { + margin-bottom: 1.5rem !important; + } + .ml-lg-4, + .mx-lg-4 { + margin-left: 1.5rem !important; + } + .m-lg-5 { + margin: 3rem !important; + } + .mt-lg-5, + .my-lg-5 { + margin-top: 3rem !important; + } + .mr-lg-5, + .mx-lg-5 { + margin-right: 3rem !important; + } + .mb-lg-5, + .my-lg-5 { + margin-bottom: 3rem !important; + } + .ml-lg-5, + .mx-lg-5 { + margin-left: 3rem !important; + } + .p-lg-0 { + padding: 0 !important; + } + .pt-lg-0, + .py-lg-0 { + padding-top: 0 !important; + } + .pr-lg-0, + .px-lg-0 { + padding-right: 0 !important; + } + .pb-lg-0, + .py-lg-0 { + padding-bottom: 0 !important; + } + .pl-lg-0, + .px-lg-0 { + padding-left: 0 !important; + } + .p-lg-1 { + padding: 0.25rem !important; + } + .pt-lg-1, + .py-lg-1 { + padding-top: 0.25rem !important; + } + .pr-lg-1, + .px-lg-1 { + padding-right: 0.25rem !important; + } + .pb-lg-1, + .py-lg-1 { + padding-bottom: 0.25rem !important; + } + .pl-lg-1, + .px-lg-1 { + padding-left: 0.25rem !important; + } + .p-lg-2 { + padding: 0.5rem !important; + } + .pt-lg-2, + .py-lg-2 { + padding-top: 0.5rem !important; + } + .pr-lg-2, + .px-lg-2 { + padding-right: 0.5rem !important; + } + .pb-lg-2, + .py-lg-2 { + padding-bottom: 0.5rem !important; + } + .pl-lg-2, + .px-lg-2 { + padding-left: 0.5rem !important; + } + .p-lg-3 { + padding: 1rem !important; + } + .pt-lg-3, + .py-lg-3 { + padding-top: 1rem !important; + } + .pr-lg-3, + .px-lg-3 { + padding-right: 1rem !important; + } + .pb-lg-3, + .py-lg-3 { + padding-bottom: 1rem !important; + } + .pl-lg-3, + .px-lg-3 { + padding-left: 1rem !important; + } + .p-lg-4 { + padding: 1.5rem !important; + } + .pt-lg-4, + .py-lg-4 { + padding-top: 1.5rem !important; + } + .pr-lg-4, + .px-lg-4 { + padding-right: 1.5rem !important; + } + .pb-lg-4, + .py-lg-4 { + padding-bottom: 1.5rem !important; + } + .pl-lg-4, + .px-lg-4 { + padding-left: 1.5rem !important; + } + .p-lg-5 { + padding: 3rem !important; + } + .pt-lg-5, + .py-lg-5 { + padding-top: 3rem !important; + } + .pr-lg-5, + .px-lg-5 { + padding-right: 3rem !important; + } + .pb-lg-5, + .py-lg-5 { + padding-bottom: 3rem !important; + } + .pl-lg-5, + .px-lg-5 { + padding-left: 3rem !important; + } + .m-lg-n1 { + margin: -0.25rem !important; + } + .mt-lg-n1, + .my-lg-n1 { + margin-top: -0.25rem !important; + } + .mr-lg-n1, + .mx-lg-n1 { + margin-right: -0.25rem !important; + } + .mb-lg-n1, + .my-lg-n1 { + margin-bottom: -0.25rem !important; + } + .ml-lg-n1, + .mx-lg-n1 { + margin-left: -0.25rem !important; + } + .m-lg-n2 { + margin: -0.5rem !important; + } + .mt-lg-n2, + .my-lg-n2 { + margin-top: -0.5rem !important; + } + .mr-lg-n2, + .mx-lg-n2 { + margin-right: -0.5rem !important; + } + .mb-lg-n2, + .my-lg-n2 { + margin-bottom: -0.5rem !important; + } + .ml-lg-n2, + .mx-lg-n2 { + margin-left: -0.5rem !important; + } + .m-lg-n3 { + margin: -1rem !important; + } + .mt-lg-n3, + .my-lg-n3 { + margin-top: -1rem !important; + } + .mr-lg-n3, + .mx-lg-n3 { + margin-right: -1rem !important; + } + .mb-lg-n3, + .my-lg-n3 { + margin-bottom: -1rem !important; + } + .ml-lg-n3, + .mx-lg-n3 { + margin-left: -1rem !important; + } + .m-lg-n4 { + margin: -1.5rem !important; + } + .mt-lg-n4, + .my-lg-n4 { + margin-top: -1.5rem !important; + } + .mr-lg-n4, + .mx-lg-n4 { + margin-right: -1.5rem !important; + } + .mb-lg-n4, + .my-lg-n4 { + margin-bottom: -1.5rem !important; + } + .ml-lg-n4, + .mx-lg-n4 { + margin-left: -1.5rem !important; + } + .m-lg-n5 { + margin: -3rem !important; + } + .mt-lg-n5, + .my-lg-n5 { + margin-top: -3rem !important; + } + .mr-lg-n5, + .mx-lg-n5 { + margin-right: -3rem !important; + } + .mb-lg-n5, + .my-lg-n5 { + margin-bottom: -3rem !important; + } + .ml-lg-n5, + .mx-lg-n5 { + margin-left: -3rem !important; + } + .m-lg-auto { + margin: auto !important; + } + .mt-lg-auto, + .my-lg-auto { + margin-top: auto !important; + } + .mr-lg-auto, + .mx-lg-auto { + margin-right: auto !important; + } + .mb-lg-auto, + .my-lg-auto { + margin-bottom: auto !important; + } + .ml-lg-auto, + .mx-lg-auto { + margin-left: auto !important; + } +} + +@media (min-width: 1200px) { + .m-xl-0 { + margin: 0 !important; + } + .mt-xl-0, + .my-xl-0 { + margin-top: 0 !important; + } + .mr-xl-0, + .mx-xl-0 { + margin-right: 0 !important; + } + .mb-xl-0, + .my-xl-0 { + margin-bottom: 0 !important; + } + .ml-xl-0, + .mx-xl-0 { + margin-left: 0 !important; + } + .m-xl-1 { + margin: 0.25rem !important; + } + .mt-xl-1, + .my-xl-1 { + margin-top: 0.25rem !important; + } + .mr-xl-1, + .mx-xl-1 { + margin-right: 0.25rem !important; + } + .mb-xl-1, + .my-xl-1 { + margin-bottom: 0.25rem !important; + } + .ml-xl-1, + .mx-xl-1 { + margin-left: 0.25rem !important; + } + .m-xl-2 { + margin: 0.5rem !important; + } + .mt-xl-2, + .my-xl-2 { + margin-top: 0.5rem !important; + } + .mr-xl-2, + .mx-xl-2 { + margin-right: 0.5rem !important; + } + .mb-xl-2, + .my-xl-2 { + margin-bottom: 0.5rem !important; + } + .ml-xl-2, + .mx-xl-2 { + margin-left: 0.5rem !important; + } + .m-xl-3 { + margin: 1rem !important; + } + .mt-xl-3, + .my-xl-3 { + margin-top: 1rem !important; + } + .mr-xl-3, + .mx-xl-3 { + margin-right: 1rem !important; + } + .mb-xl-3, + .my-xl-3 { + margin-bottom: 1rem !important; + } + .ml-xl-3, + .mx-xl-3 { + margin-left: 1rem !important; + } + .m-xl-4 { + margin: 1.5rem !important; + } + .mt-xl-4, + .my-xl-4 { + margin-top: 1.5rem !important; + } + .mr-xl-4, + .mx-xl-4 { + margin-right: 1.5rem !important; + } + .mb-xl-4, + .my-xl-4 { + margin-bottom: 1.5rem !important; + } + .ml-xl-4, + .mx-xl-4 { + margin-left: 1.5rem !important; + } + .m-xl-5 { + margin: 3rem !important; + } + .mt-xl-5, + .my-xl-5 { + margin-top: 3rem !important; + } + .mr-xl-5, + .mx-xl-5 { + margin-right: 3rem !important; + } + .mb-xl-5, + .my-xl-5 { + margin-bottom: 3rem !important; + } + .ml-xl-5, + .mx-xl-5 { + margin-left: 3rem !important; + } + .p-xl-0 { + padding: 0 !important; + } + .pt-xl-0, + .py-xl-0 { + padding-top: 0 !important; + } + .pr-xl-0, + .px-xl-0 { + padding-right: 0 !important; + } + .pb-xl-0, + .py-xl-0 { + padding-bottom: 0 !important; + } + .pl-xl-0, + .px-xl-0 { + padding-left: 0 !important; + } + .p-xl-1 { + padding: 0.25rem !important; + } + .pt-xl-1, + .py-xl-1 { + padding-top: 0.25rem !important; + } + .pr-xl-1, + .px-xl-1 { + padding-right: 0.25rem !important; + } + .pb-xl-1, + .py-xl-1 { + padding-bottom: 0.25rem !important; + } + .pl-xl-1, + .px-xl-1 { + padding-left: 0.25rem !important; + } + .p-xl-2 { + padding: 0.5rem !important; + } + .pt-xl-2, + .py-xl-2 { + padding-top: 0.5rem !important; + } + .pr-xl-2, + .px-xl-2 { + padding-right: 0.5rem !important; + } + .pb-xl-2, + .py-xl-2 { + padding-bottom: 0.5rem !important; + } + .pl-xl-2, + .px-xl-2 { + padding-left: 0.5rem !important; + } + .p-xl-3 { + padding: 1rem !important; + } + .pt-xl-3, + .py-xl-3 { + padding-top: 1rem !important; + } + .pr-xl-3, + .px-xl-3 { + padding-right: 1rem !important; + } + .pb-xl-3, + .py-xl-3 { + padding-bottom: 1rem !important; + } + .pl-xl-3, + .px-xl-3 { + padding-left: 1rem !important; + } + .p-xl-4 { + padding: 1.5rem !important; + } + .pt-xl-4, + .py-xl-4 { + padding-top: 1.5rem !important; + } + .pr-xl-4, + .px-xl-4 { + padding-right: 1.5rem !important; + } + .pb-xl-4, + .py-xl-4 { + padding-bottom: 1.5rem !important; + } + .pl-xl-4, + .px-xl-4 { + padding-left: 1.5rem !important; + } + .p-xl-5 { + padding: 3rem !important; + } + .pt-xl-5, + .py-xl-5 { + padding-top: 3rem !important; + } + .pr-xl-5, + .px-xl-5 { + padding-right: 3rem !important; + } + .pb-xl-5, + .py-xl-5 { + padding-bottom: 3rem !important; + } + .pl-xl-5, + .px-xl-5 { + padding-left: 3rem !important; + } + .m-xl-n1 { + margin: -0.25rem !important; + } + .mt-xl-n1, + .my-xl-n1 { + margin-top: -0.25rem !important; + } + .mr-xl-n1, + .mx-xl-n1 { + margin-right: -0.25rem !important; + } + .mb-xl-n1, + .my-xl-n1 { + margin-bottom: -0.25rem !important; + } + .ml-xl-n1, + .mx-xl-n1 { + margin-left: -0.25rem !important; + } + .m-xl-n2 { + margin: -0.5rem !important; + } + .mt-xl-n2, + .my-xl-n2 { + margin-top: -0.5rem !important; + } + .mr-xl-n2, + .mx-xl-n2 { + margin-right: -0.5rem !important; + } + .mb-xl-n2, + .my-xl-n2 { + margin-bottom: -0.5rem !important; + } + .ml-xl-n2, + .mx-xl-n2 { + margin-left: -0.5rem !important; + } + .m-xl-n3 { + margin: -1rem !important; + } + .mt-xl-n3, + .my-xl-n3 { + margin-top: -1rem !important; + } + .mr-xl-n3, + .mx-xl-n3 { + margin-right: -1rem !important; + } + .mb-xl-n3, + .my-xl-n3 { + margin-bottom: -1rem !important; + } + .ml-xl-n3, + .mx-xl-n3 { + margin-left: -1rem !important; + } + .m-xl-n4 { + margin: -1.5rem !important; + } + .mt-xl-n4, + .my-xl-n4 { + margin-top: -1.5rem !important; + } + .mr-xl-n4, + .mx-xl-n4 { + margin-right: -1.5rem !important; + } + .mb-xl-n4, + .my-xl-n4 { + margin-bottom: -1.5rem !important; + } + .ml-xl-n4, + .mx-xl-n4 { + margin-left: -1.5rem !important; + } + .m-xl-n5 { + margin: -3rem !important; + } + .mt-xl-n5, + .my-xl-n5 { + margin-top: -3rem !important; + } + .mr-xl-n5, + .mx-xl-n5 { + margin-right: -3rem !important; + } + .mb-xl-n5, + .my-xl-n5 { + margin-bottom: -3rem !important; + } + .ml-xl-n5, + .mx-xl-n5 { + margin-left: -3rem !important; + } + .m-xl-auto { + margin: auto !important; + } + .mt-xl-auto, + .my-xl-auto { + margin-top: auto !important; + } + .mr-xl-auto, + .mx-xl-auto { + margin-right: auto !important; + } + .mb-xl-auto, + .my-xl-auto { + margin-bottom: auto !important; + } + .ml-xl-auto, + .mx-xl-auto { + margin-left: auto !important; + } +} + +.text-monospace { + font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !important; +} + +.text-justify { + text-align: justify !important; +} + +.text-wrap { + white-space: normal !important; +} + +.text-nowrap { + white-space: nowrap !important; +} + +.text-truncate { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.text-left { + text-align: left !important; +} + +.text-right { + text-align: right !important; +} + +.text-center { + text-align: center !important; +} + +@media (min-width: 576px) { + .text-sm-left { + text-align: left !important; + } + .text-sm-right { + text-align: right !important; + } + .text-sm-center { + text-align: center !important; + } +} + +@media (min-width: 768px) { + .text-md-left { + text-align: left !important; + } + .text-md-right { + text-align: right !important; + } + .text-md-center { + text-align: center !important; + } +} + +@media (min-width: 992px) { + .text-lg-left { + text-align: left !important; + } + .text-lg-right { + text-align: right !important; + } + .text-lg-center { + text-align: center !important; + } +} + +@media (min-width: 1200px) { + .text-xl-left { + text-align: left !important; + } + .text-xl-right { + text-align: right !important; + } + .text-xl-center { + text-align: center !important; + } +} + +.text-lowercase { + text-transform: lowercase !important; +} + +.text-uppercase { + text-transform: uppercase !important; +} + +.text-capitalize { + text-transform: capitalize !important; +} + +.font-weight-light { + font-weight: 300 !important; +} + +.font-weight-lighter { + font-weight: lighter !important; +} + +.font-weight-normal { + font-weight: 400 !important; +} + +.font-weight-bold { + font-weight: 700 !important; +} + +.font-weight-bolder { + font-weight: bolder !important; +} + +.font-italic { + font-style: italic !important; +} + +.text-white { + color: #fff !important; +} + +.text-primary { + color: #007bff !important; +} + +a.text-primary:hover, a.text-primary:focus { + color: #0056b3 !important; +} + +.text-secondary { + color: #6c757d !important; +} + +a.text-secondary:hover, a.text-secondary:focus { + color: #494f54 !important; +} + +.text-success { + color: #28a745 !important; +} + +a.text-success:hover, a.text-success:focus { + color: #19692c !important; +} + +.text-info { + color: #17a2b8 !important; +} + +a.text-info:hover, a.text-info:focus { + color: #0f6674 !important; +} + +.text-warning { + color: #ffc107 !important; +} + +a.text-warning:hover, a.text-warning:focus { + color: #ba8b00 !important; +} + +.text-danger { + color: #dc3545 !important; +} + +a.text-danger:hover, a.text-danger:focus { + color: #a71d2a !important; +} + +.text-light { + color: #f8f9fa !important; +} + +a.text-light:hover, a.text-light:focus { + color: #cbd3da !important; +} + +.text-dark { + color: #343a40 !important; +} + +a.text-dark:hover, a.text-dark:focus { + color: #121416 !important; +} + +.text-body { + color: #212529 !important; +} + +.text-muted { + color: #6c757d !important; +} + +.text-black-50 { + color: rgba(0, 0, 0, 0.5) !important; +} + +.text-white-50 { + color: rgba(255, 255, 255, 0.5) !important; +} + +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} + +.text-decoration-none { + text-decoration: none !important; +} + +.text-break { + word-break: break-word !important; + overflow-wrap: break-word !important; +} + +.text-reset { + color: inherit !important; +} + +.visible { + visibility: visible !important; +} + +.invisible { + visibility: hidden !important; +} + +@media print { + *, + *::before, + *::after { + text-shadow: none !important; + box-shadow: none !important; + } + a:not(.btn) { + text-decoration: underline; + } + abbr[title]::after { + content: " (" attr(title) ")"; + } + pre { + white-space: pre-wrap !important; + } + pre, + blockquote { + border: 1px solid #adb5bd; + page-break-inside: avoid; + } + thead { + display: table-header-group; + } + tr, + img { + page-break-inside: avoid; + } + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + h2, + h3 { + page-break-after: avoid; + } + @page { + size: a3; + } + body { + min-width: 992px !important; + } + .container { + min-width: 992px !important; + } + .navbar { + display: none; + } + .badge { + border: 1px solid #000; + } + .table { + border-collapse: collapse !important; + } + .table td, + .table th { + background-color: #fff !important; + } + .table-bordered th, + .table-bordered td { + border: 1px solid #dee2e6 !important; + } + .table-dark { + color: inherit; + } + .table-dark th, + .table-dark td, + .table-dark thead th, + .table-dark tbody + tbody { + border-color: #dee2e6; + } + .table .thead-dark th { + color: inherit; + border-color: #dee2e6; + } +} +/*# sourceMappingURL=bootstrap.css.map */ \ No newline at end of file diff --git a/webgui/legacy/css/custom.css b/webgui/legacy/css/custom.css new file mode 100644 index 0000000..aee007d --- /dev/null +++ b/webgui/legacy/css/custom.css @@ -0,0 +1,268 @@ +body { + max-width: 1500px; + margin: auto; +} + +#content { + display: flex; + flex-direction: column; + width: 100%; + min-height: 100vh; + padding: 0.5em 1.5em; +} + +.top-layer-center { + z-index: 99; + width: 100%; + flex-grow: 1; + background-color: white; + display: flex; + justify-content: center; + align-items: center; + text-align: center; +} + +.top-layer-content { + margin: 1.5em; +} + +div[data-tabs] { + margin-bottom: 3em; +} + +*:focus { + outline: none !important; +} + +a[data-tab] { + width: 100%; + font-size: 1em; +} + +button, +.button { + font-size: 1em; + width: 100%; + white-space: normal; + color: black; + border-color: gray; + background-color: #ebf2f5; +} + +button:disabled, +button:disabled div, +label.disabled { + border-color: lightgray; + color: gray; + background-color: whitesmoke; +} + +button:active, +.button:active { + filter: brightness(1.5); + box-shadow: 0 0 10px 2px #004562 !important; +} + +button:disabled img, +label.disabled img { + filter: invert(0.5); +} + +.special-key { + width: 7em; +} + +.act-category-btn { + min-width: 7em; + width: 10vw; + display: inline-block; +} + +br { + line-height: 250%; +} + +.space-bottom { + margin-bottom: 0.5em; +} + +.space-bottom-2x { + margin-bottom: 1em; +} + +.headerConnectIndicator { + float: right; + font-size: 1.5em; + margin-top: -3em; +} + +.green { + color: green; +} + +.red { + color: red; +} + +.text-center { + text-align: center; +} + +.text-normal { + font-style: normal; + font-weight: normal !important; +} + +.value-bar { + background-color: #9be7ff; + height: 100%; + position: absolute; + left: 0; + z-index: -10; +} + +.back-layer { + position: absolute; + z-index: -1; +} + +.full-height { + height: 100%; +} + +.full-width { + width: 100%; +} + +.half-width { + width: 48% !important; + float: left; +} + +.tbody li { + list-style-type: none; + margin: 0 !important; +} + +.thead div { + font-style: italic; +} + +.relative { + position: relative; +} + +.float-left { + float: left; +} + +.border-right-blue { + border-right: dotted blue; +} + +.border-right-red { + border-right: dotted red; +} + +.border-right-gray { + border-right: medium dotted darkgray; +} + +#tab-puff-container span, +#tab-puff-container label { + background-color: white; + white-space: normal; +} + +label { + display: inline-block; +} + +.cursorPosWrapper { + outline: medium solid; + background-color: transparent; + margin-bottom: 2em; +} + +.full-vis-size { + min-height: 200px; + min-width: 200px; + height: 20vw; + width: 20vw; + max-width: 350px; + max-height: 350px; +} + +@media (prefers-color-scheme: dark) { + + /* This makes it so that within posVis it is inverted. */ + /* ASK: Whether the colour is okay, in both modes (used to be blue). 1% is the best option for both, but the back-layer is not really visible then.*/ + #posVis { + filter: invert(30%); + } +} + +@media (prefers-color-scheme: dark) { + #tab-puff-container { + filter: invert(1%); + } + + #tabVisBtnSipVis { + filter: invert(1%); + } + + #fabi-logo {} + +} + +.color-lightcyan { + background-color: #9be7ff; +} + +.color-lightred { + background-color: #FF7B61; +} + +.color-lightercyan { + background-color: #cceff9; +} + +.center-div { + margin-right: auto; + margin-left: auto; +} + +.hidden { + display: none; +} + +button.selected, +.custom-radio:checked~label { + border-width: 0.2em; + border-color: #33C3F0; + background-color: #cceff9; +} + +.button-primary.selected { + border-width: 0.25em; + border-color: #33C3F0; + background-color: #0D5F77; + box-shadow: black; +} + +.custom-radio { + position: absolute; + z-index: -1; + opacity: 0; +} + +.onlyscreenreader { + display: block !important; + position: absolute; + left: -9999px; +} + +.btnTransparent { + background-color: transparent; + border: 1px solid #bbb; +} \ No newline at end of file diff --git a/webgui/legacy/css/custom_fabi.css b/webgui/legacy/css/custom_fabi.css new file mode 100644 index 0000000..bc58424 --- /dev/null +++ b/webgui/legacy/css/custom_fabi.css @@ -0,0 +1,10 @@ +button.button-primary, button.button-primary:hover, button.button-primary:focus { + color: black; + background-color: #19BCF1; + border-color: #19BCF1; +} + +button.button-primary.selected { + color: black; + background-color: #19BCF1; +} \ No newline at end of file diff --git a/webgui/legacy/css/custom_fm.css b/webgui/legacy/css/custom_fm.css new file mode 100644 index 0000000..0dd013c --- /dev/null +++ b/webgui/legacy/css/custom_fm.css @@ -0,0 +1,10 @@ +button.button-primary, button.button-primary:hover, button.button-primary:focus { + color: black; + background-color: #CE6727; + border-color: #CE6727; +} + +button.button-primary.selected { + color: black; + background-color: #CE6727; +} \ No newline at end of file diff --git a/webgui/legacy/css/custom_pad.css b/webgui/legacy/css/custom_pad.css new file mode 100644 index 0000000..5bfb3c6 --- /dev/null +++ b/webgui/legacy/css/custom_pad.css @@ -0,0 +1,10 @@ +button.button-primary, button.button-primary:hover, button.button-primary:focus { + color: white; + background-color: #000080; + border-color: #000080; +} + +button.button-primary.selected { + color: white; + background-color: #000080; +} \ No newline at end of file diff --git a/webgui/legacy/css/modal.css b/webgui/legacy/css/modal.css new file mode 100644 index 0000000..77ff3a5 --- /dev/null +++ b/webgui/legacy/css/modal.css @@ -0,0 +1,59 @@ +body.modal-open { + overflow: hidden; +} + +.modal-mask { + position: fixed; + z-index: 9998; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, .5); + display: table; + transition: opacity .3s ease; + overscroll-behavior: none; +} + +.modal-wrapper { + display: table-cell; + vertical-align: middle; + overscroll-behavior: none; +} + +.modal-container { + overscroll-behavior: none; + max-width: 920px; + max-height: 75vh; + margin: 0px auto; + padding: 2em 4em; + background-color: #fff; + border-radius: 2px; + box-shadow: 0 2px 8px rgba(0, 0, 0, .33); + transition: all .3s ease; + font-family: Helvetica, Arial, sans-serif; + + overflow-y: auto; + overflow-x: hidden; +} + +@media (max-width: 850px) { + .modal-container { + padding: 2em; + } +} + +.close-button, .close-button:focus { + float: right; + padding: 0.5em 1em; + color: black; + text-decoration: none; +} + +.modal-wrapper h1 { + font-size: 2em; +} + +.modal-wrapper h2 { + font-size: 1.4em; +} \ No newline at end of file diff --git a/webgui/legacy/css/normal.css b/webgui/legacy/css/normal.css new file mode 100644 index 0000000..4e37747 --- /dev/null +++ b/webgui/legacy/css/normal.css @@ -0,0 +1,428 @@ +/*! normalize.css v3.0.2 | MIT License | git.io/normalize */ + +/** + * 1. Set default font family to sans-serif. + * 2. Prevent iOS text size adjust after orientation change, without disabling + * user zoom. + */ + +html { + font-family: sans-serif; /* 1 */ + font-size: 2em; + -ms-text-size-adjust: 100%; /* 2 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/** + * Remove default margin. + */ + +body { + margin: 0; +} + +/* HTML5 display definitions + ========================================================================== */ + +/** + * Correct `block` display not defined for any HTML5 element in IE 8/9. + * Correct `block` display not defined for `details` or `summary` in IE 10/11 + * and Firefox. + * Correct `block` display not defined for `main` in IE 11. + */ + +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} + +/** + * 1. Correct `inline-block` display not defined in IE 8/9. + * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. + */ + +audio, +canvas, +progress, +video { + display: inline-block; /* 1 */ + vertical-align: baseline; /* 2 */ +} + +/** + * Prevent modern browsers from displaying `audio` without controls. + * Remove excess height in iOS 5 devices. + */ + +audio:not([controls]) { + display: none; + height: 0; +} + +/** + * Address `[hidden]` styling not present in IE 8/9/10. + * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. + */ + +[hidden], +template { + display: none; +} + +/* Links + ========================================================================== */ + +/** + * Remove the gray background color from active links in IE 10. + */ + +a { + background-color: transparent; +} + +/** + * Improve readability when focused and also mouse hovered in all browsers. + */ + +a:active, +a:hover { + outline: 0; +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Address styling not present in IE 8/9/10/11, Safari, and Chrome. + */ + +abbr[title] { + border-bottom: 1px dotted; +} + +/** + * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. + */ + +b, +strong { + font-weight: bold; +} + +/** + * Address styling not present in Safari and Chrome. + */ + +dfn { + font-style: italic; +} + +/** + * Address variable `h1` font-size and margin within `section` and `article` + * contexts in Firefox 4+, Safari, and Chrome. + */ + +h1 { + font-size: 1.5em; + margin: 0.67em 0; +} + +/** + * Address styling not present in IE 8/9. + */ + +mark { + background: #ff0; + color: #000; +} + +/** + * Address inconsistent and variable font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` affecting `line-height` in all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sup { + top: -0.5em; +} + +sub { + bottom: -0.25em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove border when inside `a` element in IE 8/9/10. + */ + +img { + border: 0; +} + +/** + * Correct overflow not hidden in IE 9/10/11. + */ + +svg:not(:root) { + overflow: hidden; +} + +/* Grouping content + ========================================================================== */ + +/** + * Address margin not present in IE 8/9 and Safari. + */ + +figure { + margin: 1em 40px; +} + +/** + * Address differences between Firefox and other browsers. + */ + +hr { + -moz-box-sizing: content-box; + box-sizing: content-box; + height: 0; +} + +/** + * Contain overflow in all browsers. + */ + +pre { + overflow: auto; +} + +/** + * Address odd `em`-unit font size rendering in all browsers. + */ + +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +/* Forms + ========================================================================== */ + +/** + * Known limitation: by default, Chrome and Safari on OS X allow very limited + * styling of `select`, unless a `border` property is set. + */ + +/** + * 1. Correct color not being inherited. + * Known issue: affects color of disabled elements. + * 2. Correct font properties not being inherited. + * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. + */ + +button, +input, +optgroup, +select, +textarea { + color: inherit; /* 1 */ + font: inherit; /* 2 */ + margin: 0; /* 3 */ +} + +/** + * Address `overflow` set to `hidden` in IE 8/9/10/11. + */ + +button { + overflow: visible; +} + +/** + * Address inconsistent `text-transform` inheritance for `button` and `select`. + * All other form control elements do not inherit `text-transform` values. + * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. + * Correct `select` style inheritance in Firefox. + */ + +button, +select { + text-transform: none; +} + +/** + * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` + * and `video` controls. + * 2. Correct inability to style clickable `input` types in iOS. + * 3. Improve usability and consistency of cursor style between image-type + * `input` and others. + */ + +button, +html input[type="button"], /* 1 */ +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; /* 2 */ + cursor: pointer; /* 3 */ +} + +/** + * Re-set default cursor for disabled elements. + */ + +button[disabled], +html input[disabled] { + cursor: default; +} + +/** + * Remove inner padding and border in Firefox 4+. + */ + +button::-moz-focus-inner, +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +/** + * Address Firefox 4+ setting `line-height` on `input` using `!important` in + * the UA stylesheet. + */ + +input { + line-height: normal; +} + +/** + * It's recommended that you don't attempt to style these elements. + * Firefox's implementation doesn't respect box-sizing, padding, or width. + * + * 1. Address box sizing set to `content-box` in IE 8/9/10. + * 2. Remove excess padding in IE 8/9/10. + */ + +input[type="checkbox"], +input[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Fix the cursor style for Chrome's increment/decrement buttons. For certain + * `font-size` values of the `input`, it causes the cursor style of the + * decrement button to change from `default` to `text`. + */ + +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Address `appearance` set to `searchfield` in Safari and Chrome. + * 2. Address `box-sizing` set to `border-box` in Safari and Chrome + * (include `-moz` to future-proof). + */ + +input[type="search"] { + -webkit-appearance: textfield; /* 1 */ + -moz-box-sizing: content-box; + -webkit-box-sizing: content-box; /* 2 */ + box-sizing: content-box; +} + +/** + * Remove inner padding and search cancel button in Safari and Chrome on OS X. + * Safari (but not Chrome) clips the cancel button when the search input has + * padding (and `textfield` appearance). + */ + +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * Define consistent border, margin, and padding. + */ + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; +} + +/** + * 1. Correct `color` not being inherited in IE 8/9/10/11. + * 2. Remove padding so people aren't caught out if they zero out fieldsets. + */ + +legend { + border: 0; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Remove default vertical scrollbar in IE 8/9/10/11. + */ + +textarea { + overflow: auto; +} + +/** + * Don't inherit the `font-weight` (applied by a rule above). + * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. + */ + +optgroup { + font-weight: bold; +} + +/* Tables + ========================================================================== */ + +/** + * Remove most spacing between table cells. + */ + +table { + border-collapse: collapse; + border-spacing: 0; +} + +td, +th { + padding: 0; +} \ No newline at end of file diff --git a/webgui/legacy/css/skeleton.css b/webgui/legacy/css/skeleton.css new file mode 100644 index 0000000..d77ffcf --- /dev/null +++ b/webgui/legacy/css/skeleton.css @@ -0,0 +1,478 @@ +/* +* Skeleton V2.0.4 +* Copyright 2014, Dave Gamache +* www.getskeleton.com +* Free to use under the MIT license. +* http://www.opensource.org/licenses/mit-license.php +* 12/29/2014 +*/ + + +/* Table of contents +–––––––––––––––––––––––––––––––––––––––––––––––––– +- Grid +- Base Styles +- Typography +- Links +- Buttons +- Forms +- Lists +- Code +- Tables +- Spacing +- Utilities +- Clearing +- Media Queries +*/ + + +/* Grid +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +.container { + position: relative; + width: 100%; + max-width: 960px; + margin: 0 auto; + padding: 0 20px; + box-sizing: border-box; } +.column, +.columns { + width: 100%; + float: left; + box-sizing: border-box; } + +/* For devices larger than 400px */ +@media (min-width: 400px) { + .container { + width: 85%; + padding: 0; } +} + +/* For devices larger than 550px */ +@media (min-width: 550px) { + .container { + width: 80%; } + .column, + .columns { + margin-left: 4%; } + .column:first-child, + .columns:first-child { + margin-left: 0; } + + .one.column, + .one.columns { width: 4.66666666667%; } + .two.columns { width: 13.3333333333%; } + .three.columns { width: 22%; } + .four.columns { width: 30.6666666667%; } + .five.columns { width: 39.3333333333%; } + .six.columns { width: 48%; } + .seven.columns { width: 56.6666666667%; } + .eight.columns { width: 65.3333333333%; } + .nine.columns { width: 74.0%; } + .ten.columns { width: 82.6666666667%; } + .eleven.columns { width: 91.3333333333%; } + .twelve.columns { width: 100%; margin-left: 0; } + + .one-third.column { width: 30.6666666667%; } + .two-thirds.column { width: 65.3333333333%; } + + .one-half.column { width: 48%; } + + /* Offsets */ + .offset-by-one.column, + .offset-by-one.columns { margin-left: 8.66666666667%; } + .offset-by-two.column, + .offset-by-two.columns { margin-left: 17.3333333333%; } + .offset-by-three.column, + .offset-by-three.columns { margin-left: 26%; } + .offset-by-four.column, + .offset-by-four.columns { margin-left: 34.6666666667%; } + .offset-by-five.column, + .offset-by-five.columns { margin-left: 43.3333333333%; } + .offset-by-six.column, + .offset-by-six.columns { margin-left: 52%; } + .offset-by-seven.column, + .offset-by-seven.columns { margin-left: 60.6666666667%; } + .offset-by-eight.column, + .offset-by-eight.columns { margin-left: 69.3333333333%; } + .offset-by-nine.column, + .offset-by-nine.columns { margin-left: 78.0%; } + .offset-by-ten.column, + .offset-by-ten.columns { margin-left: 86.6666666667%; } + .offset-by-eleven.column, + .offset-by-eleven.columns { margin-left: 95.3333333333%; } + + .offset-by-one-third.column, + .offset-by-one-third.columns { margin-left: 34.6666666667%; } + .offset-by-two-thirds.column, + .offset-by-two-thirds.columns { margin-left: 69.3333333333%; } + + .offset-by-one-half.column, + .offset-by-one-half.columns { margin-left: 52%; } + +} + + +/* Base Styles +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +/* NOTE +html is set to 62.5% so that all the REM measurements throughout Skeleton +are based on 10px sizing. So basically 1.5rem = 15px :) */ +html { + font-size: 62.5%; } +body { + font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */ + line-height: 1.6; + font-weight: 400; + font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; + color: #222; } + + +/* Typography +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +h1, h2, h3, h4, h5, h6 { + margin-top: 1rem; + margin-bottom: 2rem; + font-weight: 300; } +h1 { font-size: 3.6rem; line-height: 1.2; letter-spacing: -.1rem;} +h2 { font-size: 3.2rem; line-height: 1.25; letter-spacing: -.1rem; } +h3 { font-size: 2.4rem; line-height: 1.3; letter-spacing: -.1rem; } +h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; } +h5 { font-size: 1.8rem; line-height: 1.5; letter-spacing: -.05rem; } +h6 { font-size: 1.5rem; line-height: 1.6; letter-spacing: 0; } + +/* Larger than phablet */ +@media (min-width: 550px) { + h1 { font-size: 5.0rem; } + h2 { font-size: 3.2rem; } + h3 { font-size: 2.0rem; } + h4 { font-size: 3.0rem; } + h5 { font-size: 2.4rem; } + h6 { font-size: 1.5rem; } +} + +p { + margin-top: 0; } + + +/* Links +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +a { + color: #0D5F77; } +a:hover { + color: #0FA0CE; } + + +/* Buttons +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +.button, +button, +input[type="submit"], +input[type="reset"], +input[type="button"] { + display: inline-block; + /*height: 38px;*/ + padding: 0 30px; + color: #555; + text-align: center; + font-size: 11px; + font-weight: 600; + line-height: 38px; + letter-spacing: .1rem; + text-transform: uppercase; + text-decoration: none; + white-space: nowrap; + background-color: rgba(158, 181, 190, 0.2);; + border-radius: 4px; + border: 2px solid #bbb; + cursor: pointer; + box-sizing: border-box; } +.button:hover, +button:hover, +input[type="submit"]:hover, +input[type="reset"]:hover, +input[type="button"]:hover, +.button:focus, +button:focus, +input[type="submit"]:focus, +input[type="color"]:focus, +input[type="reset"]:focus, +input[type="button"]:focus, +input[type="checkbox"]:focus, +.custom-radio:focus ~ label{ + color: #333; + box-shadow: 0 0 10px 2px #33C3F0; + outline: 0; } +.button.button-primary, +button.button-primary, +input[type="submit"].button-primary, +input[type="reset"].button-primary, +input[type="button"].button-primary { + color: #FFF; + background-color: #0D5F77; + border-color: #0D5F77; } +.button.button-primary:hover, +button.button-primary:hover, +input[type="submit"].button-primary:hover, +input[type="reset"].button-primary:hover, +input[type="button"].button-primary:hover, +.button.button-primary:focus, +button.button-primary:focus, +input[type="submit"].button-primary:focus, +input[type="reset"].button-primary:focus, +input[type="button"].button-primary:focus { + color: #FFF; + background-color: #0D5F77; + box-shadow: 0 0 10px 2px #33C3F0; } + + +/* Forms +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +input[type="email"], +input[type="number"], +input[type="search"], +input[type="text"], +input[type="tel"], +input[type="url"], +input[type="password"], +textarea, +select { + height: 38px; + padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */ + background-color: #fff; + border: 1px solid #D1D1D1; + border-radius: 4px; + box-shadow: none; + box-sizing: border-box; } +/* Removes awkward default styles on some inputs for iOS */ +input[type="email"], +input[type="number"], +input[type="search"], +input[type="text"], +input[type="tel"], +input[type="url"], +input[type="password"], +textarea { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; } +textarea { + min-height: 65px; + padding-top: 6px; + padding-bottom: 6px; } +input[type="email"]:focus, +input[type="number"]:focus, +input[type="search"]:focus, +input[type="text"]:focus, +input[type="tel"]:focus, +input[type="url"]:focus, +input[type="password"]:focus, +textarea:focus, +select:focus, a:focus { + box-shadow: 0 0 10px 2px #33C3F0; + outline: 0; } +label, +legend { + display: block; + margin-bottom: .5rem; + font-weight: 600; } +fieldset { + padding: 0; + border-width: 0; } +input[type="checkbox"], +input[type="radio"] { + display: inline; } +label > .label-body { + display: inline-block; + margin-left: .5rem; + font-weight: normal; } + + +/* Lists +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +ul { + list-style: circle inside; } +ol { + list-style: decimal inside; } +ol, ul { + padding-left: 0; + margin-top: 0; } +ul ul, +ul ol, +ol ol, +ol ul { + margin: 1.5rem 0 1.5rem 3rem; + font-size: 90%; } + + + +/* Code +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +code { + padding: .2rem .5rem; + margin: 0 .2rem; + font-size: 90%; + white-space: nowrap; + background: #F1F1F1; + border: 1px solid #E1E1E1; + border-radius: 4px; } +pre > code { + display: block; + padding: 1rem 1.5rem; + white-space: pre; } + + +/* Tables +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +th, +td { + padding: 12px 15px; + text-align: left; + border-bottom: 1px solid #E1E1E1; } +th:first-child, +td:first-child { + padding-left: 0; } +th:last-child, +td:last-child { + padding-right: 0; } + + +/* Spacing +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +button, +.button { + margin-bottom: 1rem; } +input, +textarea, +select, +fieldset { + margin-bottom: 1.5rem; } +pre, +blockquote, +dl, +figure, +table, +p, +ul, +ol, +form { + margin-bottom: 2.5rem; } + + +/* Utilities +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +.u-full-width { + width: 100%; + box-sizing: border-box; } +.u-max-full-width { + max-width: 100%; + box-sizing: border-box; } +.u-pull-right { + float: right; } +.u-pull-left { + float: left; } + + +/* Misc +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +hr { + margin-top: 3rem; + margin-bottom: 3.5rem; + border-width: 0; + border-top: 1px solid #E1E1E1; } + + +/* Clearing +–––––––––––––––––––––––––––––––––––––––––––––––––– */ + +/* Self Clearing Goodness */ +.container:after, +.row:after, +.u-cf { + content: ""; + display: table; + clear: both; } + + +/* Media Queries +–––––––––––––––––––––––––––––––––––––––––––––––––– */ +/* +Note: The best way to structure the use of media queries is to create the queries +near the relevant code. For example, if you wanted to change the styles for buttons +on small devices, paste the mobile query code up in the buttons section and style it +there. +*/ + +/* mobile */ +@media (min-width: 0px) { + .show-mobile { + display: block; + } + .show-tablet-inline { + display: inline; + } + .hide-mobile, .hide-mobile-inline, .show-desktop, .hide-tablet, .hide-tablet-inline { + display: none; + } + .hide-mobile.showscreenreader { + display: block !important; + position: absolute; + left:-9999px; + } + .text-right { + text-align: left; + } +} + +/* Larger than mobile */ +@media (min-width: 400px) { + .show-mobile { + display: block; + } + .hide-mobile { + display: none; + } +} + +/* Larger than phablet (also point when grid becomes active) */ +@media (min-width: 550px) { + .show-mobile { + display: none; + } + .hide-mobile { + display: block; + } + .hide-mobile.showscreenreader { + position: inherit; + left: inherit; + } + .hide-mobile-inline{ + display: inline; + } + .text-right { + text-align: right; + } +} + +/* Larger than tablet */ +@media (min-width: 750px) { + .hide-tablet-inline { + display: inline; + } + .hide-tablet { + display: block; + } + .show-tablet-inline { + display: none; + } +} + +/* Larger than desktop */ +@media (min-width: 1000px) { + .show-desktop { + display: inline; + } +} + +/* Larger than Desktop HD */ +@media (min-width: 1200px) {} diff --git a/webgui/legacy/css/slider.css b/webgui/legacy/css/slider.css new file mode 100644 index 0000000..5af24c6 --- /dev/null +++ b/webgui/legacy/css/slider.css @@ -0,0 +1,124 @@ +/* from http://danielstern.ca/range.css/#/ */ +/* all */ +input[type=range] { + -webkit-appearance: none; + width: 100%; + margin-top: 1em; +} +input[type=range]:focus { + box-shadow: 0 0 10px 2px #33C3F0; + outline: none; +} +input[type=range]::-webkit-slider-runnable-track { + width: 100%; + height: 11.4px; + cursor: pointer; + box-shadow: 0px 0px 0px rgba(0, 0, 0, 0), 0px 0px 0px rgba(13, 13, 13, 0); + background: rgba(204, 239, 249, 0.78); + border-radius: 3.7px; + border: 1px solid #b6b6b7; +} +input[type=range]::-webkit-slider-thumb { + box-shadow: 0px 0px 1px #000031, 0px 0px 0px #00004b; + border: 2px solid #5a5a5a; + height: 40px; + width: 40px; + border-radius: 26px; + background: #ffffff; + cursor: pointer; + -webkit-appearance: none; + margin-top: -15.3px; +} +input[type=range]:focus::-webkit-slider-runnable-track { + background: rgba(227, 246, 252, 0.78); +} +input[type=range]::-moz-range-track { + width: 100%; + height: 11.4px; + cursor: pointer; + box-shadow: 0px 0px 0px rgba(0, 0, 0, 0), 0px 0px 0px rgba(13, 13, 13, 0); + background: rgba(204, 239, 249, 0.78); + border-radius: 3.7px; + border: 1px solid #b6b6b7; +} +input[type=range]::-moz-range-thumb { + box-shadow: 0px 0px 1px #000031, 0px 0px 0px #00004b; + border: 2px solid #5a5a5a; + height: 40px; + width: 40px; + border-radius: 26px; + background: #ffffff; + cursor: pointer; +} +input[type=range]::-ms-track { + width: 100%; + height: 11.4px; + cursor: pointer; + background: transparent; + border-color: transparent; + color: transparent; + padding: 10px 0; +} +input[type=range]::-ms-fill-lower { + background: rgba(181, 232, 246, 0.78); + border: 1px solid #b6b6b7; + border-radius: 7.4px; + box-shadow: 0px 0px 0px rgba(0, 0, 0, 0), 0px 0px 0px rgba(13, 13, 13, 0); +} +input[type=range]::-ms-fill-upper { + background: rgba(204, 239, 249, 0.78); + border: 1px solid #b6b6b7; + border-radius: 7.4px; + box-shadow: 0px 0px 0px rgba(0, 0, 0, 0), 0px 0px 0px rgba(13, 13, 13, 0); +} +input[type=range]::-ms-thumb { + box-shadow: 0px 0px 1px #000031, 0px 0px 0px #00004b; + border: 2px solid #5a5a5a; + height: 40px; + width: 40px; + border-radius: 40px; + background: #ffffff; + cursor: pointer; + margin-top: 0px; +} +input[type=range]:focus::-ms-fill-lower { + background: rgba(204, 239, 249, 0.78); +} +input[type=range]:focus::-ms-fill-upper { + background: rgba(227, 246, 252, 0.78); +} + + +/* Larger than tablet */ +@media (min-width: 750px) { + input[type=range]::-webkit-slider-thumb { + margin-top: -7.8px !important; + height: 25px !important; + width: 25px !important; + } + + input[type=range]::-moz-range-thumb { + height: 25px !important; + width: 25px !important; + } + + input[type=range]::-ms-thumb { + height: 25px !important; + width: 25px !important; + margin-top: 2.5px !important; + } + + input[type=range]::-ms-track { + padding: 10px 0 !important; + } +} + +.colored-thumb input[type=range]::-webkit-slider-thumb { + background: #9be7ff !important; +} +.colored-thumb input[type=range]::-moz-range-thumb { + background: #9be7ff !important; +} +.colored-thumb input[type=range]::-ms-thumb { + background: #9be7ff !important; +} \ No newline at end of file diff --git a/webgui/legacy/favicon_fabi.ico b/webgui/legacy/favicon_fabi.ico new file mode 100644 index 0000000..a31f17d Binary files /dev/null and b/webgui/legacy/favicon_fabi.ico differ diff --git a/webgui/legacy/favicon_fm.ico b/webgui/legacy/favicon_fm.ico new file mode 100644 index 0000000..3ba2fc5 Binary files /dev/null and b/webgui/legacy/favicon_fm.ico differ diff --git a/webgui/legacy/favicon_pad.ico b/webgui/legacy/favicon_pad.ico new file mode 100644 index 0000000..37fffc5 Binary files /dev/null and b/webgui/legacy/favicon_pad.ico differ diff --git a/webgui/legacy/img/FABI_logo.svg b/webgui/legacy/img/FABI_logo.svg new file mode 100644 index 0000000..883a0a8 --- /dev/null +++ b/webgui/legacy/img/FABI_logo.svg @@ -0,0 +1,112 @@ + + + + + + + +image/svg+xml + + + + + + + + + + + diff --git a/webgui/legacy/img/FABI_lowres.png b/webgui/legacy/img/FABI_lowres.png new file mode 100644 index 0000000..7c476ca Binary files /dev/null and b/webgui/legacy/img/FABI_lowres.png differ diff --git a/webgui/legacy/img/FLipMouse_logo.svg b/webgui/legacy/img/FLipMouse_logo.svg new file mode 100644 index 0000000..fd12f2d --- /dev/null +++ b/webgui/legacy/img/FLipMouse_logo.svg @@ -0,0 +1,97 @@ + +image/svg+xml diff --git a/webgui/legacy/img/FLipPad_favicon.svg b/webgui/legacy/img/FLipPad_favicon.svg new file mode 100644 index 0000000..7c8a4e6 --- /dev/null +++ b/webgui/legacy/img/FLipPad_favicon.svg @@ -0,0 +1,61 @@ + +image/svg+xml diff --git a/webgui/legacy/img/FLipPad_logo.svg b/webgui/legacy/img/FLipPad_logo.svg new file mode 100644 index 0000000..462df4e --- /dev/null +++ b/webgui/legacy/img/FLipPad_logo.svg @@ -0,0 +1,89 @@ + +image/svg+xml diff --git a/webgui/legacy/img/bt.svg b/webgui/legacy/img/bt.svg new file mode 100644 index 0000000..3002b0b --- /dev/null +++ b/webgui/legacy/img/bt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/webgui/legacy/img/fm_lowres.png b/webgui/legacy/img/fm_lowres.png new file mode 100644 index 0000000..b7567af Binary files /dev/null and b/webgui/legacy/img/fm_lowres.png differ diff --git a/webgui/legacy/index.html b/webgui/legacy/index.html new file mode 100644 index 0000000..b5ea090 --- /dev/null +++ b/webgui/legacy/index.html @@ -0,0 +1,19 @@ + + + + FLipMouse/FABI Configuration + + + + + \ No newline at end of file diff --git a/webgui/index_fabi.htm b/webgui/legacy/index_fabi.htm similarity index 96% rename from webgui/index_fabi.htm rename to webgui/legacy/index_fabi.htm index 82e33aa..1b4e2e3 100644 --- a/webgui/index_fabi.htm +++ b/webgui/legacy/index_fabi.htm @@ -1,5 +1,6 @@  + FABI Configuration @@ -38,6 +39,7 @@ +
@@ -50,8 +52,10 @@ } }); } - import {MainView} from "./js/ui/views/MainView.js"; + import { MainView } from "./js/ui/views/MainView.js"; MainView.init(); + - + + \ No newline at end of file diff --git a/webgui/index_fm.htm b/webgui/legacy/index_fm.htm similarity index 100% rename from webgui/index_fm.htm rename to webgui/legacy/index_fm.htm diff --git a/webgui/index_pad.htm b/webgui/legacy/index_pad.htm similarity index 71% rename from webgui/index_pad.htm rename to webgui/legacy/index_pad.htm index 1ef60d3..2abf612 100644 --- a/webgui/index_pad.htm +++ b/webgui/legacy/index_pad.htm @@ -28,17 +28,17 @@ C.CURRENT_DEVICE = "FLipPad"; - - - - - - - - - - - + + + + + + + + + + +
@@ -52,7 +52,7 @@ } }); } - import {MainView} from "./js/ui/views/MainView.js"; + import {MainView} from ".//js/ui/views/MainView.js"; MainView.init(); diff --git a/webgui/init.html b/webgui/legacy/init.html similarity index 87% rename from webgui/init.html rename to webgui/legacy/init.html index 8c5656b..9bce8fd 100644 --- a/webgui/init.html +++ b/webgui/legacy/init.html @@ -25,20 +25,25 @@ if (C.DEVICE_IS_FM) { constantsPath = './js_fm/constantsFM.js'; C.CURRENT_DEVICE = "FLipMouse"; + } else if (C.DEVICE_IS_FABI) { constantsPath = './js_fabi/constantsFabi.js'; C.CURRENT_DEVICE = "FABI"; + } else if (C.DEVICE_IS_FLIPPAD) { constantsPath = './js_pad/constantsPad.js'; C.CURRENT_DEVICE = "FLipPad"; } import('./js/constantsGeneric.js').then(() => { - return Promise.resolve(); - }).then(() => { + return Promise.resolve(); // The Promise is returned once the module has been loaded. + + }).then(() => { // then acts like if, except that all get execeuted, as long as the promise is valid. Otherwise it would go in the catch block. return import(constantsPath); + }).then(() => { return import('./js/ui/views/InitDeviceView.js'); + }).then(module => { document.title = C.CURRENT_DEVICE + L.translate(" Initialization // Initialisierung"); module.InitDeviceView.init(); diff --git a/webgui/legacy/js/adapter/arecomm.js b/webgui/legacy/js/adapter/arecomm.js new file mode 100644 index 0000000..1ddfa60 --- /dev/null +++ b/webgui/legacy/js/adapter/arecomm.js @@ -0,0 +1,38 @@ +function ARECommunicator(socket) { + var _restURI = 'http://' + window.location.hostname + ':8091/rest/'; + var _websocket = socket; + var _valueHandler = null; + var _component = 'LipMouse.1'; + var _inputPort = 'send'; + + //encodes PathParametes + function encodeParam(text) { + var encoded = ""; + for (var i = 0; i < text.length; i++) { + encoded += text.charCodeAt(i).toString() + '-'; + } + return encoded; + } + + this.setValueHandler = function (handler) { + _valueHandler = handler; + }; + + this.sendData = function (value, timeout) { + if (!value) return; + + //use GET sendDataToInputPort() for legacy reasons (phones that do only support GET requests) + var url = _restURI + "runtime/model/components/" + encodeParam(_component) + "/ports/" + encodeParam(_inputPort) + "/data/" + encodeParam(value); + var xmlHttp = new XMLHttpRequest(); + xmlHttp.open("GET", url); + xmlHttp.send(value); + return new Promise(function (resolve, reject) { + ws.initWebsocket(C.ARE_WEBSOCKET_URL, _websocket).then(function (socket) { + _websocket = socket; + ws.handleData(_websocket, _valueHandler, timeout).then(resolve); + }, function () { + reject(); + }); + }); + }; +} \ No newline at end of file diff --git a/webgui/js/adapter/mockcomm.js b/webgui/legacy/js/adapter/mockcomm.js similarity index 100% rename from webgui/js/adapter/mockcomm.js rename to webgui/legacy/js/adapter/mockcomm.js diff --git a/webgui/js/adapter/sercomm.js b/webgui/legacy/js/adapter/sercomm.js similarity index 100% rename from webgui/js/adapter/sercomm.js rename to webgui/legacy/js/adapter/sercomm.js diff --git a/webgui/legacy/js/adapter/wscomm.js b/webgui/legacy/js/adapter/wscomm.js new file mode 100644 index 0000000..829d40b --- /dev/null +++ b/webgui/legacy/js/adapter/wscomm.js @@ -0,0 +1,21 @@ +function WsCommunicator(wsUrl, socket) { + var _websocket = socket; + var _valueHandler = null; + + this.setValueHandler = function (handler) { + _valueHandler = handler; + }; + + this.sendData = function (value, timeout) { + if (!value) return; + return new Promise(function(resolve, reject) { + ws.initWebsocket(wsUrl, _websocket).then(function (socket) { + socket.send(value); + _websocket = socket; + ws.handleData(_websocket, _valueHandler, timeout).then(resolve); + }, function () { + reject(); + }); + }); + }; +} \ No newline at end of file diff --git a/webgui/legacy/js/adapter/wsutil.js b/webgui/legacy/js/adapter/wsutil.js new file mode 100644 index 0000000..7277801 --- /dev/null +++ b/webgui/legacy/js/adapter/wsutil.js @@ -0,0 +1,94 @@ +window.ws = {}; + +/** + * inits a websocket by a given url, returned promise resolves with initialized websocket, rejects after failure/timeout. + * + * @param url the websocket url to init + * @param existingWebsocket if passed and this passed websocket is already open, this existingWebsocket is resolved, no additional websocket is opened + * @param timeoutMs the timeout in milliseconds for opening the websocket + * @param numberOfRetries the number of times initializing the socket should be retried, if not specified or 0, no retries are made + * and a failure/timeout causes rejection of the returned promise + * @return {Promise} + */ +ws.initWebsocket = function(url, existingWebsocket, timeoutMs, numberOfRetries) { + timeoutMs = timeoutMs ? timeoutMs : 1500; + numberOfRetries = numberOfRetries ? numberOfRetries : 0; + var hasReturned = false; + var promise = new Promise(function(resolve, reject) { + setTimeout(function () { + if(!hasReturned) { + console.info('opening websocket timed out: ' + url); + rejectInternal(); + } + }, timeoutMs); + if (!existingWebsocket || existingWebsocket.readyState != existingWebsocket.OPEN) { + if (existingWebsocket) { + existingWebsocket.close(); + } + var websocket = new WebSocket(url); + websocket.onopen = function () { + if(hasReturned) { + websocket.close(); + } else { + console.info('websocket to opened! url: ' + url); + resolve(websocket); + } + }; + websocket.onclose = function () { + console.info('websocket closed! url: ' + url); + rejectInternal(); + }; + websocket.onerror = function () { + console.info('websocket error! url: ' + url); + rejectInternal(); + }; + } else { + resolve(existingWebsocket); + } + + function rejectInternal() { + if(numberOfRetries <= 0) { + reject(); + } else if(!hasReturned) { + hasReturned = true; + console.info('retrying connection to websocket! url: ' + url + ', remaining retries: ' + (numberOfRetries-1)); + ws.initWebsocket(url, null, timeoutMs, numberOfRetries-1).then(resolve, reject); + } + } + }); + promise.then(function () {hasReturned = true;}, function () {hasReturned = true;}); + return promise; +}; + +/** + * handles got from flipmouse over a websocket. Data containing live values are passed to valueHandler function, other data resolves + * the returned promise. + * @param socket + * @param valueHandler + * @param timeout maximum time after the promise is resolved (optional). Default 3000ms. + * @return {Promise} + */ +ws.handleData = function (socket, valueHandler, timeout) { + timeout = timeout || 3000; + return new Promise(function(resolve) { + var result = ''; + var timeoutHandler = setTimeout(function () { + //console.log("timeout von command: " + value); + resolve(result); + }, timeout); + socket.onmessage = function (evt) { + if (evt.data && evt.data.indexOf(C.LIVE_VALUE_CONSTANT) > -1) { + if (L.isFunction(valueHandler)) { + valueHandler(evt.data); + } + return; + } + clearTimeout(timeoutHandler); + result += evt.data + '\n'; + timeoutHandler = setTimeout(function () { + console.log("got result: " + result); + resolve(result) + }, 200); + }; + }); +}; \ No newline at end of file diff --git a/webgui/js/communication/ATDevice.js b/webgui/legacy/js/communication/ATDevice.js similarity index 96% rename from webgui/js/communication/ATDevice.js rename to webgui/legacy/js/communication/ATDevice.js index f35a085..7b566c9 100644 --- a/webgui/js/communication/ATDevice.js +++ b/webgui/legacy/js/communication/ATDevice.js @@ -98,6 +98,15 @@ ATDevice.init = function (dontGetLiveValues) { if (_communicator.close) _communicator.close(); return Promise.reject(C.ERROR_FIRMWARE_OUTDATED); } + //console.log("ATDevice.init: version is " + versionString + ", unified min version is " + C.UNIFIED_GUI_MIN_FIRMWARE_VERSION); + //console.log("ATDevice.init: isVersionNewer: " + L.isVersionNewer(C.UNIFIED_GUI_MIN_FIRMWARE_VERSION, versionString)); + if (L.isVersionNewer(C.UNIFIED_GUI_MIN_FIRMWARE_VERSION, versionString) || L.isVersionEqual(C.UNIFIED_GUI_MIN_FIRMWARE_VERSION, versionString)) { + if (_communicator.close) _communicator.close(); + // newer than UNIFIED_GUI_MIN_FIRMWARE_VERSION -> switch to new generic version + alert(L.translate("You connected a device with a firmware version newer than " + C.UNIFIED_GUI_MIN_FIRMWARE_VERSION + ". Switching to new WebGUI, please press Connect again. // Sie haben ein Gerät mit einer Firmware verbunden, die neuer ist als Version " + C.UNIFIED_GUI_MIN_FIRMWARE_VERSION + ". Um zur neuen WebGUI zu wechseln, bitte erneut Verbinden drücken.")); + return Promise.reject(C.ERROR_NON_LEGACY_FIRMWARE); + } + return ATDevice.refreshConfig(); }).then(() => { if (!_dontGetLiveValues) { diff --git a/webgui/js/constantsGeneric.js b/webgui/legacy/js/constantsGeneric.js similarity index 99% rename from webgui/js/constantsGeneric.js rename to webgui/legacy/js/constantsGeneric.js index 752d016..239a5da 100644 --- a/webgui/js/constantsGeneric.js +++ b/webgui/legacy/js/constantsGeneric.js @@ -2,6 +2,8 @@ import {ATDevice} from "./communication/ATDevice.js"; window.C = window.C || {}; +C.UNIFIED_GUI_MIN_FIRMWARE_VERSION = '3.7.0'; + C.AT_DEVICE_FLIPMOUSE = 'FLipMouse'; C.AT_DEVICE_FLIPPAD = 'FLipPad'; C.AT_DEVICE_FABI = 'FABI'; @@ -369,6 +371,7 @@ for (var i = 0; i < 400; i++) { } C.ERROR_FIRMWARE_OUTDATED = 'ERROR_FIRMWARE_OUTDATED'; +C.ERROR_NON_LEGACY_FIRMWARE = 'ERROR_NON_LEGACY_FIRMWARE'; C.ERROR_WRONG_DEVICE = 'ERROR_WRONG_DEVICE'; C.ERROR_SERIAL_DENIED = 'ERROR_SERIAL_DENIED'; C.ERROR_SERIAL_BUSY = 'ERROR_SERIAL_BUSY'; diff --git a/webgui/js/constantsGenericIndependent.js b/webgui/legacy/js/constantsGenericIndependent.js similarity index 100% rename from webgui/js/constantsGenericIndependent.js rename to webgui/legacy/js/constantsGenericIndependent.js diff --git a/webgui/js/localStorageService.js b/webgui/legacy/js/localStorageService.js similarity index 100% rename from webgui/js/localStorageService.js rename to webgui/legacy/js/localStorageService.js diff --git a/webgui/js/lquery.js b/webgui/legacy/js/lquery.js similarity index 100% rename from webgui/js/lquery.js rename to webgui/legacy/js/lquery.js diff --git a/webgui/js/shortcut.js b/webgui/legacy/js/shortcut.js similarity index 100% rename from webgui/js/shortcut.js rename to webgui/legacy/js/shortcut.js diff --git a/webgui/js/ui/components/ActionButton.js b/webgui/legacy/js/ui/components/ActionButton.js similarity index 100% rename from webgui/js/ui/components/ActionButton.js rename to webgui/legacy/js/ui/components/ActionButton.js diff --git a/webgui/js/ui/components/FaIcon.js b/webgui/legacy/js/ui/components/FaIcon.js similarity index 100% rename from webgui/js/ui/components/FaIcon.js rename to webgui/legacy/js/ui/components/FaIcon.js diff --git a/webgui/legacy/js/ui/components/InputKeyboard.js b/webgui/legacy/js/ui/components/InputKeyboard.js new file mode 100644 index 0000000..ad3aa40 --- /dev/null +++ b/webgui/legacy/js/ui/components/InputKeyboard.js @@ -0,0 +1,118 @@ +import { h, Component } from '../../../lib/preact.min.js'; +import htm from '../../../lib/htm.min.js'; +const html = htm.bind(h); + +class InputKeyboard extends Component { + + constructor(props) { + super(); + + this.props = props; + this.state = { + currentValue: props.value, + keycodeList: props.value ? props.value.split(' ') : [], + selectedKey: C.KEYCODE_MAPPING[C.SUPPORTED_KEYCODES[0]] + }; + } + + handleKeydown(event) { + if (event.repeat) { + event.preventDefault(); + return; + } + + let keycode = event.keyCode || event.which; + + if (!C.KEYCODE_MAPPING[keycode]) { + event.preventDefault(); + } else { + let list = this.state.keycodeList; + if (this.isLastListElemAndCurrent(C.JS_KEYCODE_TAB, keycode)) { + // allow to tab forward or backward over the input field + list.pop(); + if (list[list.length - 1] === C.KEYCODE_MAPPING[C.JS_KEYCODE_SHIFT]) { + list.pop(); + } + } else if (this.isLastListElemAndCurrent(C.JS_KEYCODE_F5, keycode)) { + window.location.reload(); + } else if (this.isLastListElemAndCurrent(C.JS_KEYCODE_BACKSPACE, keycode)) { + event.preventDefault(); + list.pop(); + list.pop(); + } else { + event.preventDefault(); + list.push(C.KEYCODE_MAPPING[keycode]); + } + + this.rerenderList(list); + } + } + + rerenderList(list) { + let value = list.join(' '); + this.setState({ + keycodeList: list, + currentValue: value + }); + if (this.props.onchange) { + this.props.onchange(value); + } + } + + isLastListElemAndCurrent(keycode, currentKeyCode) { + let list = this.state.keycodeList; + return currentKeyCode === keycode && list.length > 0 && list[list.length - 1] === C.KEYCODE_MAPPING[keycode]; + } + + render(props) { + let state = this.state; + return html` +
+ +
+
+
+ +
+
+ +
+
+
+
+
+ +
+
+
+ +
+
+ +
+
+
+
+
+ ${L.translate('Selected keys // Ausgewählte Tasten')} +
+
+
+ ${state.currentValue || L.translate('(no keys selected) // (keine Tasten ausgewählt)')} +
+
+ +
+
+
+
+ `; + } +} + +export {InputKeyboard}; \ No newline at end of file diff --git a/webgui/js/ui/components/InputMacro.js b/webgui/legacy/js/ui/components/InputMacro.js similarity index 100% rename from webgui/js/ui/components/InputMacro.js rename to webgui/legacy/js/ui/components/InputMacro.js diff --git a/webgui/js/ui/components/MouseAndKeyboardVisualization.js b/webgui/legacy/js/ui/components/MouseAndKeyboardVisualization.js similarity index 100% rename from webgui/js/ui/components/MouseAndKeyboardVisualization.js rename to webgui/legacy/js/ui/components/MouseAndKeyboardVisualization.js diff --git a/webgui/legacy/js/ui/components/RadioFieldset.js b/webgui/legacy/js/ui/components/RadioFieldset.js new file mode 100644 index 0000000..b70c362 --- /dev/null +++ b/webgui/legacy/js/ui/components/RadioFieldset.js @@ -0,0 +1,38 @@ +import { h, Component } from '../../../lib/preact.min.js'; +import htm from '../../../lib/htm.min.js'; +const html = htm.bind(h); + +class RadioFieldset extends Component { + render(props) { + props.onchange = props.onchange || (() => {}); + props.legend = props.legend || ''; + props.elements = props.elements || []; // objects with value and label property + props.value = props.value !== undefined ? props.value : null; + props.name = props.name || props.elements[0].value + props.elements[1].value; + + return html` +
+ ${L.translate(props.legend)} + ${props.elements.map(el => html` +
+ + +
+ `)} +
+ ${RadioFieldset.style}`; + } +} + +RadioFieldset.style = html`` + +export {RadioFieldset}; \ No newline at end of file diff --git a/webgui/legacy/js/ui/components/Slider.js b/webgui/legacy/js/ui/components/Slider.js new file mode 100644 index 0000000..c565512 --- /dev/null +++ b/webgui/legacy/js/ui/components/Slider.js @@ -0,0 +1,38 @@ +import { h, Component } from '../../../lib/preact.min.js'; +import htm from '../../../lib/htm.min.js'; +const html = htm.bind(h); + +class Slider extends Component { + render(props) { + props.label = props.label || ''; + props.lang = props.lang || ''; + props.oninput = props.oninput || (() => { }); + props.value = props.value !== undefined ? props.value : null; + props.min = props.min !== undefined ? props.min : 0; + props.max = props.max !== undefined ? props.max : 255; + props.step = props.step !== undefined ? props.step : ''; + props.updateConstants = props.updateConstants || []; + props.toggleFn = props.toggleFn || (() => { }); + props.toggleFnLabel = props.toggleFnLabel || ''; + props.viewFactor = props.viewFactor || 1; + let id = props.updateConstants[0]; + + return html` + +
+ + +
+ ${Slider.style}`; + } +} + +Slider.style = html`` + +export { Slider }; \ No newline at end of file diff --git a/webgui/legacy/js/ui/components/SlotTestModeDialog.js b/webgui/legacy/js/ui/components/SlotTestModeDialog.js new file mode 100644 index 0000000..3dd14f1 --- /dev/null +++ b/webgui/legacy/js/ui/components/SlotTestModeDialog.js @@ -0,0 +1,141 @@ +import { h, Component } from '../../../lib/preact.min.js'; +import htm from '../../../lib/htm.min.js'; +import {ATDevice} from "../../communication/ATDevice.js"; +import {FaIcon} from "./FaIcon.js"; +import {audioUtil} from "../../util/audioUtil.js"; + +const html = htm.bind(h); + +class SlotTestModeDialog extends Component { + + constructor(props) { + super(); + this.state = { + actionPerforming: false, + minimized: false, + revertTime: ATDevice.getSlotTestModeOptions().testSeconds, + coutdownTime: ATDevice.getSlotTestModeOptions().countdownSeconds, + currentTestTime: ATDevice.getSlotTestModeOptions().testSeconds, + currentCountdown: ATDevice.getSlotTestModeOptions().countdownSeconds, + coutdownIntervalHandler: null + } + this.props = props; + window.addEventListener(C.EVENT_CONFIG_CHANGED, () => { + this.setState({}); + }); + } + + startTestCountdown() { + this.stopTesting(); + this.startCountdownTime = new Date().getTime(); + let intervalHandler = setInterval(() => { + if (new Date().getTime() - this.startCountdownTime > this.state.coutdownTime * 1000) { + audioUtil.beepHigh(); + this.stopTesting(); + this.testSlot(); + } else { + this.setState({ + currentCountdownTime: Math.ceil(this.state.coutdownTime - (new Date().getTime() - this.startCountdownTime) / 1000) + }) + } + }, 200); + this.setState({ + coutdownIntervalHandler: intervalHandler + }); + } + + testSlot() { + ATDevice.testCurrentSlot(); + this.startTestTime = new Date().getTime(); + this.intervalHandler = setInterval(() => { + if (new Date().getTime() - this.startTestTime > this.state.revertTime * 1000) { + audioUtil.beep(); + this.stopTesting(); + } else { + this.setState({ + currentTestTime: Math.ceil(this.state.revertTime - (new Date().getTime() - this.startTestTime) / 1000) + }) + } + }, 200); + } + + stopTesting() { + clearInterval(this.intervalHandler); + clearInterval(this.state.coutdownIntervalHandler); + ATDevice.stopTestingCurrentSlot(); + this.setState({ + revertTime: ATDevice.getSlotTestModeOptions().testSeconds, + coutdownTime: ATDevice.getSlotTestModeOptions().countdownSeconds, + coutdownIntervalHandler: null, + currentTestTime: ATDevice.getSlotTestModeOptions().testSeconds, + currentCountdownTime: ATDevice.getSlotTestModeOptions().countdownSeconds + }); + } + + addTime() { + this.setState({ + revertTime: this.state.revertTime + 30 + }) + } + + render(props) { + let state = this.state; + return html` + + ${SlotTestModeDialog.style}`; + } +} + +SlotTestModeDialog.style = html`` + +export {SlotTestModeDialog}; \ No newline at end of file diff --git a/webgui/js/ui/modals/ActionEditModal.js b/webgui/legacy/js/ui/modals/ActionEditModal.js similarity index 100% rename from webgui/js/ui/modals/ActionEditModal.js rename to webgui/legacy/js/ui/modals/ActionEditModal.js diff --git a/webgui/js/ui/modals/FirmwareUpdateModal.js b/webgui/legacy/js/ui/modals/FirmwareUpdateModal.js similarity index 93% rename from webgui/js/ui/modals/FirmwareUpdateModal.js rename to webgui/legacy/js/ui/modals/FirmwareUpdateModal.js index 2a468b6..88c8f12 100644 --- a/webgui/js/ui/modals/FirmwareUpdateModal.js +++ b/webgui/legacy/js/ui/modals/FirmwareUpdateModal.js @@ -1,17 +1,19 @@ import { h, Component } from '../../../lib/preact.min.js'; import htm from '../../../lib/htm.min.js'; -import {FaIcon} from "../components/FaIcon.js"; -import {ATDevice} from "../../communication/ATDevice.js"; +import { FaIcon } from "../components/FaIcon.js"; +import { ATDevice } from "../../communication/ATDevice.js"; const html = htm.bind(h); class FirmwareUpdateModal extends Component { constructor(props) { super(); + this.props = props; this.state = { enteredDownloadMode: false } + } enterDownloadMode() { @@ -29,11 +31,12 @@ class FirmwareUpdateModal extends Component { } this.props.close(); } - + render(props) { if (!props || !props.fwInfo) { return; } + let index = props.fwInfo.originalDownloadUrl.lastIndexOf("/"); let fwFileName = props.fwInfo.originalDownloadUrl.substring(index + 1); @@ -65,7 +68,7 @@ class FirmwareUpdateModal extends Component {
  • ${L.translate('Put the device into download mode. Afterwards it will connect to the computer like an USB storage device. // Versetzen Sie das Gerät in den Download-Modus. Dadurch meldet es sich am PC als USB-Speicher an.')}
    - + ${this.state.enteredDownloadMode ? html`<${FaIcon} icon="fas check"/>` : ''}
    @@ -74,7 +77,7 @@ class FirmwareUpdateModal extends Component {
  • ${L.translate("The device will automatically reboot. Afterwards reload this page and connect again to the device. // Das Gerät wird automatisch neu starten. Laden Sie diese Seite danach neu und verbinden Sie sich erneut zum Gerät.")}
  • -
    +
    ${html`<${FaIcon} icon="fas info-circle"/>`} ${L.translate("Hint: if you want to cancel the update process, disconnect and reconnect the device from the computer and reload this page. // Hinweis: Wenn Sie das Update abbrechen wollen, trennen und verbinden Sie das Gerät erneut. Laden Sie danach diese Seite neu.")}
    @@ -83,11 +86,11 @@ class FirmwareUpdateModal extends Component {