diff --git a/.github/workflows/deploy_configurator.yml b/.github/workflows/deploy_configurator.yml index f9f0cba9..e01792b7 100644 --- a/.github/workflows/deploy_configurator.yml +++ b/.github/workflows/deploy_configurator.yml @@ -38,16 +38,16 @@ jobs: uses: devatherock/minify-js@v1.0.3 with: # File to minify or a folder containing files to minify. By default, all files in current folder and its subfolders will be minified - directory: './Source/loaderConfigurator/dist' # optional + directory: './pages/dist' # optional # Path where the minified files will be saved. By default, the minified files will be saved in the original file path - output: './Source/loaderConfigurator/dist_min' # optional + output: './pages/dist_min' # optional # Indicates if the output files should have the suffix '.min' added after the name. Default is true add_suffix: false # optional - name: Upload artifact uses: actions/upload-pages-artifact@v1 with: # Upload entire repository - path: './Source/loaderConfigurator/dist_min' + path: './pages/dist_min' - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v1 diff --git a/README.md b/README.md index 319b0b75..f4d73eb2 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ Overclocking suite for Horizon OS (HOS) running on Atmosphere CFW. +[Project Homepage](https://kazushime.github.io/Switch-OC-Suite) **DISCLAIMER: USE AT YOUR OWN RISK!** @@ -15,20 +16,13 @@ Overclocking suite for Horizon OS (HOS) running on Atmosphere CFW. - Erista variant (HAC-001) - CPU Overclock (Safe: 1785 MHz) -
Unsafe - - - Due to the limit of board power draw or power IC - - Unlockable frequencies up to 2091 MHz - - See [README for sys-clk-OC](https://github.com/KazushiMe/Switch-OC-Suite/blob/master/Source/sys-clk-OC/README.md) - -
+ - Unsafe + - Due to the limit of board power draw or power IC + - Unlockable frequencies up to 2091 MHz + - See [README for sys-clk-OC](https://github.com/KazushiMe/Switch-OC-Suite/blob/master/Source/sys-clk-OC/README.md) - DRAM Overclock (Safe: 1862.4 MHz) -
Unsafe - - - Up to 2131 MHz with DRAM bus overvolting depending on your DRAM chip - -
+ - Unsafe: Up to 2131 MHz with DRAM bus overvolting depending on your DRAM chip - Modded sys-clk and ReverseNX-RT - CPU & GPU frequency governor (Experimental) @@ -38,20 +32,12 @@ Overclocking suite for Horizon OS (HOS) running on Atmosphere CFW. - Mariko variant (HAC-001-01, HDH-001, HEG-001) - CPU / GPU Overclock (Safe: 1963 / 998 MHz) -
Unsafe - - - Due to the limit of board power draw or power IC - - Unlockable frequencies up to 2397 / 1305 MHz or 2295 / 1267 MHz - - See [README for sys-clk-OC](https://github.com/KazushiMe/Switch-OC-Suite/blob/master/Source/sys-clk-OC/README.md) - -
+ - Unsafe + - Due to the limit of board power draw or power IC + - Unlockable frequencies up to 2397 / 1305 MHz or 2295 / 1267 MHz + - See [README for sys-clk-OC](https://github.com/KazushiMe/Switch-OC-Suite/blob/master/Source/sys-clk-OC/README.md) - DRAM Overclock (Safe: 1996.8 MHz) -
Unsafe - - - [DRAM bus overvolting](https://gist.github.com/KazushiMe/6bb0fcbefe0e03b1274079522516d56d). - -
- Modded sys-clk and ReverseNX-RT - Auto CPU Boost @@ -99,7 +85,7 @@ Overclocking suite for Horizon OS (HOS) running on Atmosphere CFW. | CPU Volt | 1235 mV Max | 1257 mV Max | | GPU OC | 1305 MHz Max | N/A | | RAM OC | 1996 MHz Max | 1862 MHz Max | - | RAM Volt | N/A | Disabled | + | RAM Volt | Disabled | Disabled | | RAM Timing | Auto-Adjusted | Disabled | @@ -151,5 +137,5 @@ When compilation is done, uncompress the kip to make it work with configurator: - RetroNX team for [sys-clk](https://github.com/retronx-team/sys-clk) - SciresM and Reswitched Team for the state-of-the-art [Atmosphere](https://github.com/Atmosphere-NX/Atmosphere) CFW of Switch - Switchbrew [wiki](http://switchbrew.org/wiki/) for Switch in-depth info -- Switchroot for their [modified L4T kernel and device tree]((https://gitlab.com/switchroot/kernel)) +- Switchroot for their [modified L4T kernel and device tree](https://gitlab.com/switchroot/kernel) - ZatchyCatGames for RE and original OC loader patches for Atmosphere diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp index 7caf1f54..57352099 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/customize.cpp @@ -71,7 +71,6 @@ volatile CustomizeTable C = { .marikoEmcVolt = 0, /* Erista CPU: - * Not tested but enabled by default. * - Max Voltage in mV */ .eristaCpuMaxVolt = 1235, diff --git a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.cpp b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.cpp index 19a6fa84..e0cac740 100644 --- a/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.cpp +++ b/Source/Atmosphere/stratosphere/loader/source/oc/pcv/pcv.cpp @@ -31,8 +31,8 @@ Result MemFreqPllmLimit(u32* ptr) { void SafetyCheck() { if (C.custRev != CUST_REV || - C.marikoCpuMaxVolt >= 1300 || - C.eristaCpuMaxVolt >= 1300 || + C.marikoCpuMaxVolt > 1300 || + C.eristaCpuMaxVolt > 1300 || (C.eristaEmcVolt && (C.eristaEmcVolt < 600'000 || C.eristaEmcVolt > 1250'000)) || (C.marikoEmcVolt && (C.marikoEmcVolt < 600'000 || C.marikoEmcVolt > 650'000))) { diff --git a/Source/Atmosphere/stratosphere/loader/source/patch.py b/Source/Atmosphere/stratosphere/loader/source/patch.py index 0cf2b7dc..22c503f7 100755 --- a/Source/Atmosphere/stratosphere/loader/source/patch.py +++ b/Source/Atmosphere/stratosphere/loader/source/patch.py @@ -31,7 +31,7 @@ file_replace_str(ldr_process_creation, namespace ams::ldr {""", """#include "ldr_ro_manager.hpp" -#include "oc/oc_suite.hpp" +#include "oc/oc_loader.hpp" namespace ams::ldr {"""), (""" NsoHeader g_nso_headers[Nso_Count]; diff --git a/Source/loaderConfigurator/.gitignore b/Source/loaderConfigurator/.gitignore deleted file mode 100644 index 504afef8..00000000 --- a/Source/loaderConfigurator/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules/ -package-lock.json diff --git a/Source/loaderConfigurator/dist/index.html b/Source/loaderConfigurator/dist/index.html deleted file mode 100644 index 7daab2d8..00000000 --- a/Source/loaderConfigurator/dist/index.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - -
-
- - Get latest here - -
- - -
-
-
- - - diff --git a/Source/loaderConfigurator/dist/main.js b/Source/loaderConfigurator/dist/main.js deleted file mode 100644 index 321c1b52..00000000 --- a/Source/loaderConfigurator/dist/main.js +++ /dev/null @@ -1,234 +0,0 @@ -const CUST_REV = 2; -var buffer; -function FindMagicOffset(buffer) { - let view = new DataView(buffer); - for (let i = 0; i < view.byteLength; i += 4) { - if (view.getUint32(i, true) == 0x54535543) { // "CUST" - return i; - } - } - throw new Error("Invalid loader.kip file"); -} -function CustEntry(name, size, desc, defval, minmax = [0, 1000000], step = 1, extra_validator) { - this.name = name; - this.size = size; - this.desc = desc; - this.defval = defval; - this.min = minmax[0]; - this.max = minmax[1]; - this.step = step; - this.validator = extra_validator; - this.value = null; - this.offset = null; -} -function InitCustTable() { - let cust = [ - new CustEntry("mtcConf", 2, "DRAM Timing\ -
  • 0: AUTO_ADJ_MARIKO_SAFE: Auto adjust timings for LPDDR4 ≤3733 Mbps specs, 8Gb density.
  • \ -
  • 1: AUTO_ADJ_MARIKO_4266: Auto adjust timings for LPDDR4X 4266 Mbps specs, 8Gb density.
  • ", 0, [0, 3]), - new CustEntry("marikoCpuMaxClock", 4, "Mariko CPU Max Clock in kHz\ -
  • System default: 1785000
  • \ -
  • ≥ 2397000 will enable overvolting (> 1120 mV)
  • ", 2397000, [1785000, 3000000], 100, (x) => (x % 100) == 0), - new CustEntry("marikoCpuBoostClock", 4, "Mariko CPU Boost Clock in kHz\ -
  • System default: 1785000
  • \ -
  • Must not be higher than marikoCpuMaxClock
  • ", 1785000, [1785000, 3000000], 100, (x) => (x % 100) == 0), - new CustEntry("marikoCpuMaxVolt", 4, "Mariko CPU Max Voltage in mV\ -
  • System default: 1120
  • \ -
  • Acceptable range: 1100 ≤ x ≤ 1300
  • ", 1235, [1100, 1300]), - new CustEntry("marikoGpuMaxClock", 4, "Mariko GPU Max Clock in kHz\ -
  • System default: 921600
  • \ -
  • Tegra X1+ official maximum: 1267200
  • ", 1305600, [768000, 1536000], 100, (x) => (x % 100) == 0), - new CustEntry("marikoEmcMaxClock", 4, "Mariko RAM Max Clock in kHz\ -
  • Values should be > 1600000, and divided evenly by 3200.
  • \ -
  • WARNING: RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM
  • ", 1996800, [1612800, 2400000], 3200, (x) => (x % 3200) == 0), - new CustEntry("eristaCpuOCEnable", 4, "Erista CPU Enable Overclock\ -
  • Not tested
  • ", 1, [0, 1]), - new CustEntry("eristaCpuMaxVolt", 4, "Erista CPU Max Voltage in mV\ -
  • Acceptable range: 1100 ≤ x ≤ 1400
  • ", 1257, [0, 1400], 100, (x) => x >= 1100), - new CustEntry("eristaEmcMaxClock", 4, "Erista RAM Max Clock in kHz\ -
  • Values should be > 1600000, and divided evenly by 3200.
  • \ -
  • WARNING: RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM
  • ", 1862400, [1600000, 2400000], 3200, (x) => (x % 3200) == 0), - new CustEntry("eristaEmcVolt", 4, "Erista RAM Voltage in uV\ -
  • Acceptable range: 1100000 ≤ x ≤ 1250000, and it should be divided evenly by 12500.
  • \ -
  • Not enabled by default
  • ", 0, [0, 1250000], 12500, (x) => (x % 12500) == 0 && x >= 1100000), - ]; - return cust; -} -function ValidateCust(cust) { - for (let i of cust) { - if (i.value == 0) - continue; - if (i.value < i.min || i.value > i.max) { - document.getElementById(i.name).focus(); - throw new Error(`Expected range: ${i.min} ≤ ${i.name} ≤ ${i.max}, got ${i.value}`); - } - if (i.validator && !i.validator(i.value)) { - document.getElementById(i.name).focus(); - throw new Error(`Invalid value: ${i.value}(${i.name})\nValidator: ${i.validator}`); - } - } -} -function SaveCust(cust, buffer) { - let dict = Object.assign({}, ...cust.map((x) => ({ [x.name]: x }))); - let view = new DataView(buffer); - let storage = {}; - for (let i of cust) { - let id = i.name; - i.value = document.getElementById(id).value; - storage[i.name] = i.value; - switch (i.size) { - case 2: - view.setUint16(i.offset, i.value, true); - break; - case 4: - view.setUint32(i.offset, i.value, true); - break; - default: - document.getElementById(i.name).focus(); - throw new Error("Unknown size at " + i); - } - } - ValidateCust(cust); - storage["custRev"] = CUST_REV; - localStorage.setItem("last_saved", JSON.stringify(storage)); - let a = document.createElement("a"); - a.href = window.URL.createObjectURL(new Blob([buffer], { type: "application/octet-stream" })); - a.download = "loader.kip"; - a.click(); -} -function LastSaved() { - let storage = localStorage.getItem("last_saved"); - if (!storage) { - return false; - } - let sObj = JSON.parse(storage); - if (!sObj["custRev"] || sObj["custRev"] != CUST_REV) { - localStorage.removeItem("last_saved"); - return false; - } - return true; -} -function LoadLastSaved() { - if (LastSaved()) { - let storage = localStorage.getItem("last_saved"); - let sObj = JSON.parse(storage); - for (let key in sObj) { - if (key == "custRev") { - continue; - } - document.getElementById(key).value = sObj[key]; - } - } -} -function LoadDefault(cust) { - let dict = Object.assign({}, ...cust.map((x) => ({ [x.name]: x }))); - for (let i of cust) { - let id = i.name; - document.getElementById(id).value = i.defval; - } -} -function UpdateHTMLForm(cust) { - let dict = Object.assign({}, ...cust.map((x) => ({ [x.name]: x }))); - let form = document.getElementById("form"); - for (let i of cust) { - let id = i.name; - let input = document.getElementById(id); - if (!input) { - let div = document.createElement("div"); - div.classList.add("form-floating", "my-3"); - let label = document.createElement("label"); - label.setAttribute("for", id); - label.innerHTML = id; - label.classList.add("form-entry"); - input = document.createElement("input"); - input.classList.add("form-control", "form-entry"); - input.min = dict[i.name].min; - input.max = dict[i.name].max; - input.id = id; - input.type = "number"; - input.step = dict[i.name].step; - div.appendChild(input); - div.appendChild(label); - let desc = dict[i.name].desc; - if (desc) { - let tip = document.createElement("small"); - tip.innerHTML = desc; - tip.classList.add("form-entry"); - tip.setAttribute("for", id); - div.appendChild(tip); - } - form === null || form === void 0 ? void 0 : form.appendChild(div); - } - input.value = dict[i.name].value; - } - let btn = document.getElementById("load"); - btn.classList.remove("hide"); - if (LastSaved()) { - btn.innerHTML = "Load Last Saved"; - btn.addEventListener('click', () => { - LoadLastSaved(); - }); - } - else { - btn.addEventListener('click', () => { - LoadDefault(cust); - }); - } - btn = document.getElementById("save"); - btn.classList.remove("hide"); - btn.addEventListener('click', () => { - try { - SaveCust(cust, buffer); - } - catch (e) { - console.log(e); - alert(e); - } - }); -} -function ParseCust(magicOffset, buffer) { - let view = new DataView(buffer); - let cust = InitCustTable(); - let offset = magicOffset + 4; - let rev = view.getUint16(offset, true); - if (rev != CUST_REV) { - throw new Error("Unsupported custRev, expected: " + CUST_REV + ", got " + rev); - } - offset += 2; - for (let i of cust) { - i.offset = offset; - switch (i.size) { - case 2: - i.value = view.getUint16(offset, true); - break; - case 4: - i.value = view.getUint32(offset, true); - break; - default: - document.getElementById(i.name).focus(); - throw new Error("Unknown size at " + i); - } - offset += i.size; - } - ValidateCust(cust); - UpdateHTMLForm(cust); -} -const fileInput = document.getElementById("file"); -fileInput.addEventListener('change', (event) => { - let reader = new FileReader(); - reader.readAsArrayBuffer(event.target.files[0]); - reader.onloadend = (progEvent) => { - if (progEvent.target.readyState === FileReader.DONE) { - try { - buffer = progEvent.target.result; - let offset = FindMagicOffset(buffer); - ParseCust(offset, buffer); - } - catch (e) { - console.log(e); - alert(e); - fileInput.value = ""; - } - } - }; -}); diff --git a/Source/loaderConfigurator/dist/output.css b/Source/loaderConfigurator/dist/output.css deleted file mode 100644 index 40ed4ded..00000000 --- a/Source/loaderConfigurator/dist/output.css +++ /dev/null @@ -1,4071 +0,0 @@ -/* -! tailwindcss v3.2.2 | MIT License | https://tailwindcss.com -*/ - -/* -1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) -2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) -*/ - -*, -::before, -::after { - box-sizing: border-box; - /* 1 */ - border-width: 0; - /* 2 */ - border-style: solid; - /* 2 */ - border-color: #e5e7eb; - /* 2 */ -} - -::before, -::after { - --tw-content: ''; -} - -/* -1. Use a consistent sensible line-height in all browsers. -2. Prevent adjustments of font size after orientation changes in iOS. -3. Use a more readable tab size. -4. Use the user's configured `sans` font-family by default. -5. Use the user's configured `sans` font-feature-settings by default. -*/ - -html { - line-height: 1.5; - /* 1 */ - -webkit-text-size-adjust: 100%; - /* 2 */ - -moz-tab-size: 4; - /* 3 */ - -o-tab-size: 4; - tab-size: 4; - /* 3 */ - font-family: Inter, sans-serif; - /* 4 */ - font-feature-settings: normal; - /* 5 */ -} - -/* -1. Remove the margin in all browsers. -2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. -*/ - -body { - margin: 0; - /* 1 */ - line-height: inherit; - /* 2 */ -} - -/* -1. Add the correct height in Firefox. -2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) -3. Ensure horizontal rules are visible by default. -*/ - -hr { - height: 0; - /* 1 */ - color: inherit; - /* 2 */ - border-top-width: 1px; - /* 3 */ -} - -/* -Add the correct text decoration in Chrome, Edge, and Safari. -*/ - -abbr:where([title]) { - -webkit-text-decoration: underline dotted; - text-decoration: underline dotted; -} - -/* -Remove the default font size and weight for headings. -*/ - -h1, -h2, -h3, -h4, -h5, -h6 { - font-size: inherit; - font-weight: inherit; -} - -/* -Reset links to optimize for opt-in styling instead of opt-out. -*/ - -a { - color: inherit; - text-decoration: inherit; -} - -/* -Add the correct font weight in Edge and Safari. -*/ - -b, -strong { - font-weight: bolder; -} - -/* -1. Use the user's configured `mono` font family by default. -2. Correct the odd `em` font sizing in all browsers. -*/ - -code, -kbd, -samp, -pre { - font-family: ui-monospace, monospace; - /* 1 */ - font-size: 1em; - /* 2 */ -} - -/* -Add the correct font size in all browsers. -*/ - -small { - font-size: 80%; -} - -/* -Prevent `sub` and `sup` elements from affecting the line height in all browsers. -*/ - -sub, -sup { - font-size: 75%; - line-height: 0; - position: relative; - vertical-align: baseline; -} - -sub { - bottom: -0.25em; -} - -sup { - top: -0.5em; -} - -/* -1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) -2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) -3. Remove gaps between table borders by default. -*/ - -table { - text-indent: 0; - /* 1 */ - border-color: inherit; - /* 2 */ - border-collapse: collapse; - /* 3 */ -} - -/* -1. Change the font styles in all browsers. -2. Remove the margin in Firefox and Safari. -3. Remove default padding in all browsers. -*/ - -button, -input, -optgroup, -select, -textarea { - font-family: inherit; - /* 1 */ - font-size: 100%; - /* 1 */ - font-weight: inherit; - /* 1 */ - line-height: inherit; - /* 1 */ - color: inherit; - /* 1 */ - margin: 0; - /* 2 */ - padding: 0; - /* 3 */ -} - -/* -Remove the inheritance of text transform in Edge and Firefox. -*/ - -button, -select { - text-transform: none; -} - -/* -1. Correct the inability to style clickable types in iOS and Safari. -2. Remove default button styles. -*/ - -button, -[type='button'], -[type='reset'], -[type='submit'] { - -webkit-appearance: button; - /* 1 */ - background-color: transparent; - /* 2 */ - background-image: none; - /* 2 */ -} - -/* -Use the modern Firefox focus style for all focusable elements. -*/ - -:-moz-focusring { - outline: auto; -} - -/* -Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) -*/ - -:-moz-ui-invalid { - box-shadow: none; -} - -/* -Add the correct vertical alignment in Chrome and Firefox. -*/ - -progress { - vertical-align: baseline; -} - -/* -Correct the cursor style of increment and decrement buttons in Safari. -*/ - -::-webkit-inner-spin-button, -::-webkit-outer-spin-button { - height: auto; -} - -/* -1. Correct the odd appearance in Chrome and Safari. -2. Correct the outline style in Safari. -*/ - -[type='search'] { - -webkit-appearance: textfield; - /* 1 */ - outline-offset: -2px; - /* 2 */ -} - -/* -Remove the inner padding in Chrome and Safari on macOS. -*/ - -::-webkit-search-decoration { - -webkit-appearance: none; -} - -/* -1. Correct the inability to style clickable types in iOS and Safari. -2. Change font properties to `inherit` in Safari. -*/ - -::-webkit-file-upload-button { - -webkit-appearance: button; - /* 1 */ - font: inherit; - /* 2 */ -} - -/* -Add the correct display in Chrome and Safari. -*/ - -summary { - display: list-item; -} - -/* -Removes the default spacing and border for appropriate elements. -*/ - -blockquote, -dl, -dd, -h1, -h2, -h3, -h4, -h5, -h6, -hr, -figure, -p, -pre { - margin: 0; -} - -fieldset { - margin: 0; - padding: 0; -} - -legend { - padding: 0; -} - -ol, -ul, -menu { - list-style: none; - margin: 0; - padding: 0; -} - -/* -Prevent resizing textareas horizontally by default. -*/ - -textarea { - resize: vertical; -} - -/* -1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) -2. Set the default placeholder color to the user's configured gray 400 color. -*/ - -input::-moz-placeholder, textarea::-moz-placeholder { - opacity: 1; - /* 1 */ - color: #9ca3af; - /* 2 */ -} - -input::placeholder, -textarea::placeholder { - opacity: 1; - /* 1 */ - color: #9ca3af; - /* 2 */ -} - -/* -Set the default cursor for buttons. -*/ - -button, -[role="button"] { - cursor: pointer; -} - -/* -Make sure disabled buttons don't get the pointer cursor. -*/ - -:disabled { - cursor: default; -} - -/* -1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) -2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) - This can trigger a poorly considered lint error in some tools but is included by design. -*/ - -img, -svg, -video, -canvas, -audio, -iframe, -embed, -object { - display: block; - /* 1 */ - vertical-align: middle; - /* 2 */ -} - -/* -Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) -*/ - -img, -video { - max-width: 100%; - height: auto; -} - -/* Make elements with the HTML hidden attribute stay hidden by default */ - -[hidden] { - display: none; -} - -*, ::before, ::after { - --tw-border-spacing-x: 0; - --tw-border-spacing-y: 0; - --tw-translate-x: 0; - --tw-translate-y: 0; - --tw-rotate: 0; - --tw-skew-x: 0; - --tw-skew-y: 0; - --tw-scale-x: 1; - --tw-scale-y: 1; - --tw-pan-x: ; - --tw-pan-y: ; - --tw-pinch-zoom: ; - --tw-scroll-snap-strictness: proximity; - --tw-ordinal: ; - --tw-slashed-zero: ; - --tw-numeric-figure: ; - --tw-numeric-spacing: ; - --tw-numeric-fraction: ; - --tw-ring-inset: ; - --tw-ring-offset-width: 0px; - --tw-ring-offset-color: #fff; - --tw-ring-color: rgb(59 130 246 / 0.5); - --tw-ring-offset-shadow: 0 0 #0000; - --tw-ring-shadow: 0 0 #0000; - --tw-shadow: 0 0 #0000; - --tw-shadow-colored: 0 0 #0000; - --tw-blur: ; - --tw-brightness: ; - --tw-contrast: ; - --tw-grayscale: ; - --tw-hue-rotate: ; - --tw-invert: ; - --tw-saturate: ; - --tw-sepia: ; - --tw-drop-shadow: ; - --tw-backdrop-blur: ; - --tw-backdrop-brightness: ; - --tw-backdrop-contrast: ; - --tw-backdrop-grayscale: ; - --tw-backdrop-hue-rotate: ; - --tw-backdrop-invert: ; - --tw-backdrop-opacity: ; - --tw-backdrop-saturate: ; - --tw-backdrop-sepia: ; -} - -::backdrop { - --tw-border-spacing-x: 0; - --tw-border-spacing-y: 0; - --tw-translate-x: 0; - --tw-translate-y: 0; - --tw-rotate: 0; - --tw-skew-x: 0; - --tw-skew-y: 0; - --tw-scale-x: 1; - --tw-scale-y: 1; - --tw-pan-x: ; - --tw-pan-y: ; - --tw-pinch-zoom: ; - --tw-scroll-snap-strictness: proximity; - --tw-ordinal: ; - --tw-slashed-zero: ; - --tw-numeric-figure: ; - --tw-numeric-spacing: ; - --tw-numeric-fraction: ; - --tw-ring-inset: ; - --tw-ring-offset-width: 0px; - --tw-ring-offset-color: #fff; - --tw-ring-color: rgb(59 130 246 / 0.5); - --tw-ring-offset-shadow: 0 0 #0000; - --tw-ring-shadow: 0 0 #0000; - --tw-shadow: 0 0 #0000; - --tw-shadow-colored: 0 0 #0000; - --tw-blur: ; - --tw-brightness: ; - --tw-contrast: ; - --tw-grayscale: ; - --tw-hue-rotate: ; - --tw-invert: ; - --tw-saturate: ; - --tw-sepia: ; - --tw-drop-shadow: ; - --tw-backdrop-blur: ; - --tw-backdrop-brightness: ; - --tw-backdrop-contrast: ; - --tw-backdrop-grayscale: ; - --tw-backdrop-hue-rotate: ; - --tw-backdrop-invert: ; - --tw-backdrop-opacity: ; - --tw-backdrop-saturate: ; - --tw-backdrop-sepia: ; -} - -.container { - width: 100%; -} - -@media (min-width: 640px) { - .container { - max-width: 640px; - } -} - -@media (min-width: 768px) { - .container { - max-width: 768px; - } -} - -@media (min-width: 1024px) { - .container { - max-width: 1024px; - } -} - -@media (min-width: 1280px) { - .container { - max-width: 1280px; - } -} - -@media (min-width: 1536px) { - .container { - max-width: 1536px; - } -} - -:root { - --bs-blue: #0d6efd; - --bs-indigo: #6610f2; - --bs-purple: #6f42c1; - --bs-pink: #d63384; - --bs-red: #dc3545; - --bs-orange: #fd7e14; - --bs-yellow: #ffc107; - --bs-green: #198754; - --bs-teal: #20c997; - --bs-cyan: #0dcaf0; - --bs-white: #fff; - --bs-gray: #6c757d; - --bs-gray-dark: #343a40; - --bs-gray-100: #f8f9fa; - --bs-gray-200: #e9ecef; - --bs-gray-300: #dee2e6; - --bs-gray-400: #ced4da; - --bs-gray-500: #adb5bd; - --bs-gray-600: #6c757d; - --bs-gray-700: #495057; - --bs-gray-800: #343a40; - --bs-gray-900: #212529; - --bs-primary: #0d6efd; - --bs-secondary: #6c757d; - --bs-success: #198754; - --bs-info: #0dcaf0; - --bs-warning: #ffc107; - --bs-danger: #dc3545; - --bs-light: #f8f9fa; - --bs-dark: #212529; - --bs-primary-rgb: 13, 110, 253; - --bs-secondary-rgb: 108, 117, 125; - --bs-success-rgb: 25, 135, 84; - --bs-info-rgb: 13, 202, 240; - --bs-warning-rgb: 255, 193, 7; - --bs-danger-rgb: 220, 53, 69; - --bs-light-rgb: 248, 249, 250; - --bs-dark-rgb: 33, 37, 41; - --bs-white-rgb: 255, 255, 255; - --bs-black-rgb: 0, 0, 0; - --bs-body-color-rgb: 33, 37, 41; - --bs-body-bg-rgb: 255, 255, 255; - --bs-font-sans-serif: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; - --bs-font-monospace: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - --bs-gradient: linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0)); - --bs-body-font-family: var(--bs-font-sans-serif); - --bs-body-font-size: 1rem; - --bs-body-font-weight: 400; - --bs-body-line-height: 1.5; - --bs-body-color: #212529; - --bs-body-bg: #fff; -} - -.form-control[type=file] { - overflow: hidden; -} - -.form-control[type=file]:not(:disabled):not([readonly]) { - cursor: pointer; -} - -.form-control:focus { - box-shadow: 0 0 0 1px #2563eb; -} - -.form-control::file-selector-button { - padding: 0.375rem 0.75rem; - margin: -0.375rem -0.75rem; - -webkit-margin-end: 0.75rem; - margin-inline-end: 0.75rem; - color: #212529; - background-color: #e9ecef; - pointer-events: none; - border-color: inherit; - border-style: solid; - border-width: 0; - border-inline-end-width: 1px; - border-radius: 0; - 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; -} - -.form-control:hover:not(:disabled):not([readonly])::file-selector-button { - background-color: #dde0e3; -} - -.form-control::-webkit-file-upload-button { - padding: 0.375rem 0.75rem; - margin: -0.375rem -0.75rem; - -webkit-margin-end: 0.75rem; - margin-inline-end: 0.75rem; - color: #374151; - background-color: #f3f4f6; - pointer-events: none; - border-color: inherit; - border-style: solid; - border-width: 0; - border-inline-end-width: 1px; - border-radius: 0; - -webkit-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; - 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; -} - -.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button { - background-color: #dde0e3; -} - -.btn-check[disabled] + .btn { - pointer-events: none; - filter: none; - opacity: 0.65; -} - -.btn-check:disabled + .btn { - pointer-events: none; - filter: none; - opacity: 0.65; -} - -.form-floating { - position: relative; -} - -.form-floating > .form-control { - height: calc(3.5rem + 2px); - line-height: 1.25; - padding: 1rem 0.75rem; -} - -.form-floating > .form-select { - height: calc(3.5rem + 2px); - line-height: 1.25; -} - -.form-floating > label { - position: absolute; - top: 0; - left: 0; - height: 100%; - padding: 1rem 0.75rem; - pointer-events: none; - border: 1px solid transparent; - transform-origin: 0 0; - transition: opacity 0.1s ease-in-out, transform 0.1s ease-in-out; -} - -.form-floating > .form-control::-moz-placeholder { - color: transparent; -} - -.form-floating > .form-control::placeholder { - color: transparent; -} - -.form-floating > .form-control:focus { - padding-top: 1.625rem; - padding-bottom: 0.625rem; -} - -.form-floating > .form-control:not(:-moz-placeholder-shown) { - padding-top: 1.625rem; - padding-bottom: 0.625rem; -} - -.form-floating > .form-control:not(:placeholder-shown) { - padding-top: 1.625rem; - padding-bottom: 0.625rem; -} - -.form-floating > .form-control:-webkit-autofill { - padding-top: 1.625rem; - padding-bottom: 0.625rem; -} - -.form-floating > .form-control:focus ~ label { - opacity: 0.65; - transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); -} - -.form-floating > .form-control:not(:-moz-placeholder-shown) ~ label { - opacity: 0.65; - transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); -} - -.form-floating > .form-control:not(:placeholder-shown) ~ label { - opacity: 0.65; - transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); -} - -.form-floating > .form-select ~ label { - opacity: 0.65; - transform: scale(0.85) translateY(-0.5rem) translateX(0.15rem); -} - -.input-group > .form-control { - width: 1%; -} - -.input-group > .form-control:focus { - z-index: 3; -} - -.input-group .btn { - position: relative; - z-index: 2; -} - -.input-group .btn:focus { - z-index: 3; -} - -.input-group:not(.has-validation) > .dropdown-toggle:nth-last-child(n+3) { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} - -.input-group.has-validation > .dropdown-toggle:nth-last-child(n+4) { - border-top-right-radius: 0; - border-bottom-right-radius: 0; -} - -.was-validated .form-control:valid { - border-color: #198754; - padding-right: calc(1.5em + 0.75rem); - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' 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); -} - -.form-control.is-valid { - border-color: #198754; - padding-right: calc(1.5em + 0.75rem); - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' 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 { - border-color: #198754; - box-shadow: 0 0 0 0.25rem rgba(25, 135, 84, 0.25); -} - -.form-control.is-valid:focus { - border-color: #198754; - box-shadow: 0 0 0 0.25rem rgba(25, 135, 84, 0.25); -} - -.was-validated textarea.form-control:valid { - padding-right: calc(1.5em + 0.75rem); - background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem); -} - -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 .input-group .form-control:valid { - z-index: 1; -} - -.input-group .form-control.is-valid { - z-index: 1; -} - -.was-validated .input-group .form-control:valid:focus { - z-index: 3; -} - -.input-group .form-control.is-valid:focus { - z-index: 3; -} - -.is-invalid ~ .invalid-feedback { - display: block; -} - -.is-invalid ~ .invalid-tooltip { - display: block; -} - -.was-validated .form-control: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' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%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); -} - -.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' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%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 { - border-color: #dc3545; - box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25); -} - -.form-control.is-invalid:focus { - border-color: #dc3545; - box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25); -} - -.was-validated textarea.form-control:invalid { - padding-right: calc(1.5em + 0.75rem); - background-position: top calc(0.375em + 0.1875rem) right calc(0.375em + 0.1875rem); -} - -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); -} - -.form-select.is-invalid { - border-color: #dc3545; -} - -.form-select.is-invalid:not([multiple]):not([size]) { - padding-right: 4.125rem; - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"), url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%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-position: right 0.75rem center, center right 2.25rem; - background-size: 16px 12px, calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); -} - -.form-select.is-invalid:not([multiple])[size="1"] { - padding-right: 4.125rem; - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"), url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%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-position: right 0.75rem center, center right 2.25rem; - background-size: 16px 12px, calc(0.75em + 0.375rem) calc(0.75em + 0.375rem); -} - -.form-select.is-invalid:focus { - border-color: #dc3545; - box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25); -} - -.form-check-input.is-invalid { - border-color: #dc3545; -} - -.form-check-input.is-invalid:checked { - background-color: #dc3545; -} - -.form-check-input.is-invalid:focus { - box-shadow: 0 0 0 0.25rem rgba(220, 53, 69, 0.25); -} - -.form-check-input.is-invalid ~ .form-check-label { - color: #dc3545; -} - -.was-validated .input-group .form-control:invalid { - z-index: 2; -} - -.input-group .form-control.is-invalid { - z-index: 2; -} - -.input-group .form-select.is-invalid { - z-index: 2; -} - -.was-validated .input-group .form-control:invalid:focus { - z-index: 3; -} - -.input-group .form-control.is-invalid:focus { - z-index: 3; -} - -.input-group .form-select.is-invalid:focus { - z-index: 3; -} - -.btn-check:focus + .btn { - outline: 0; - box-shadow: none; -} - -.btn:focus { - outline: 0; - box-shadow: none; -} - -.btn-check:checked + .btn { - box-shadow: none; -} - -.btn-check:active + .btn { - box-shadow: none; -} - -.btn:active { - box-shadow: none; -} - -.btn.active { - box-shadow: none; -} - -.btn-check:checked + .btn:focus { - box-shadow: none; -} - -.btn-check:active + .btn:focus { - box-shadow: none; -} - -.btn:active:focus { - box-shadow: none; -} - -.btn.active:focus { - box-shadow: none; -} - -.fade { - transition: opacity 0.15s linear; -} - -.fade:not(.show) { - opacity: 0; -} - -.collapse:not(.show) { - display: none; -} - -.collapsing { - height: 0; - overflow: hidden; - transition: height 0.35s ease; -} - -.collapsing.collapse-horizontal { - width: 0; - height: auto; - transition: width 0.35s ease; -} - -.dropdown-menu { - z-index: 1000; -} - -.dropdown-item.active { - color: #1f2937; - -webkit-text-decoration: none; - text-decoration: none; - background-color: #0d6efd; -} - -.dropdown-item:active { - color: #1f2937; - -webkit-text-decoration: none; - text-decoration: none; - background-color: #0d6efd; -} - -.dropdown-item:disabled { - color: #adb5bd; - pointer-events: none; - background-color: transparent; -} - -.dropdown-menu.show { - display: block; -} - -.dropdown-menu-dark .dropdown-item.active { - color: #fff; - background-color: #0d6efd; -} - -.dropdown-menu-dark .dropdown-item:active { - color: #fff; - background-color: #0d6efd; -} - -.dropdown-menu-dark .dropdown-item.disabled { - color: #adb5bd; -} - -.dropdown-menu-dark .dropdown-item:disabled { - color: #adb5bd; -} - -.nav-tabs .nav-link { - color: #4b5563; -} - -.nav-tabs .nav-link:hover { - isolation: isolate; -} - -.nav-tabs .nav-link:focus { - isolation: isolate; -} - -.nav-tabs .nav-link.disabled { - color: #9ca3af; - background-color: transparent; - border-color: transparent; -} - -.nav-tabs .nav-link.active { - color: #2563eb; - border-color: #2563eb; -} - -.nav-tabs .nav-item.show .nav-link { - color: #2563eb; - border-color: #2563eb; -} - -.nav-tabs .dropdown-menu { - margin-top: -1px; - border-top-left-radius: 0; - border-top-right-radius: 0; -} - -.nav-pills .nav-link { - background: #f3f4f6; - color: #4b5563; - box-shadow: none; -} - -.nav-pills .nav-link.active { - background: #2563eb; - color: #fff; - box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); -} - -.nav-pills .show > .nav-link { - background: #2563eb; - color: #fff; - box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); -} - -.nav-pills .disabled { - color: #9ca3af; - background-color: rgba(243, 244, 246, 0.5); -} - -.nav-pills.menu-sidebar .nav-link { - background-color: transparent; - box-shadow: none; - padding: 0 5px; - border-radius: 0; -} - -.nav-pills.menu-sidebar .nav-link.active { - color: #1266f1; - font-weight: 600; - border-left: 0.125rem solid #1266f1; -} - -.nav-justified > .nav-link { - -ms-flex-basis: 0; - flex-basis: 0; -} - -.nav-justified .nav-item { - -ms-flex-basis: 0; - flex-basis: 0; -} - -.tab-content > .active { - display: block; -} - -.navbar-expand .navbar-nav { - 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 .offcanvas { - position: inherit; - bottom: 0; - z-index: 1000; - -ms-flex-grow: 1; - flex-grow: 1; - visibility: visible !important; - background-color: transparent; - border-right: 0; - border-left: 0; - transition: none; - transform: none; -} - -.navbar-light .navbar-nav .nav-link.disabled { - color: rgba(0, 0, 0, 0.3); -} - -.navbar-light .navbar-nav .show > .nav-link { - color: rgba(0, 0, 0, 0.9); -} - -.navbar-light .navbar-nav .nav-link.active { - color: rgba(0, 0, 0, 0.9); -} - -.navbar-dark .navbar-nav .nav-link.disabled { - color: rgba(255, 255, 255, 0.25); -} - -.navbar-dark .navbar-nav .show > .nav-link { - color: #fff; -} - -.navbar-dark .navbar-nav .nav-link.active { - color: #fff; -} - -.accordion-item:last-of-type .accordion-button.collapsed { - border-bottom-right-radius: calc(0.5rem - 1px); - border-bottom-left-radius: calc(0.5rem - 1px); -} - -.btn-close.disabled { - pointer-events: none; - -webkit-user-select: none; - -moz-user-select: none; - user-select: none; - opacity: 0.25; -} - -.modal { - z-index: 1055; -} - -.modal-dialog { - margin: 0.5rem; -} - -.modal.fade .modal-dialog { - transition: transform 0.3s ease-out; - transform: translate(0, -50px); -} - -.modal.show .modal-dialog { - transform: none; -} - -.modal.modal-static .modal-dialog { - transform: scale(1.02); -} - -.modal-dialog-scrollable .modal-body { - overflow-y: auto; -} - -.modal-backdrop { - position: fixed; - top: 0; - left: 0; - z-index: 1050; - width: 100vw; - height: 100vh; - background-color: #000; -} - -.modal-backdrop.fade { - opacity: 0; -} - -.modal-backdrop.show { - opacity: 0.5; -} - -.modal-body { - flex: 1 1 auto; -} - -.modal-fullscreen .modal-body { - overflow-y: auto; -} - -.tooltip { - position: absolute; - z-index: 1080; - display: block; - margin: 0; - font-family: var(--bs-font-sans-serif); - font-style: normal; - font-weight: 400; - line-height: 1.5; - -webkit-text-align: start; - text-align: start; - -webkit-text-decoration: none; - text-decoration: none; - -webkit-text-shadow: none; - text-shadow: none; - -webkit-text-transform: 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: 1; -} - -.bs-tooltip-top .tooltip-arrow { - bottom: 0; -} - -.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow { - bottom: 0; -} - -.bs-tooltip-top .tooltip-arrow::before { - top: -1px; - border-width: 0.4rem 0.4rem 0; - border-top-color: #000; -} - -.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before { - top: -1px; - border-width: 0.4rem 0.4rem 0; - border-top-color: #000; -} - -.bs-tooltip-end .tooltip-arrow { - left: 0; - width: 0.4rem; - height: 0.8rem; -} - -.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow { - left: 0; - width: 0.4rem; - height: 0.8rem; -} - -.bs-tooltip-end .tooltip-arrow::before { - right: -1px; - border-width: 0.4rem 0.4rem 0.4rem 0; - border-right-color: #000; -} - -.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before { - right: -1px; - border-width: 0.4rem 0.4rem 0.4rem 0; - border-right-color: #000; -} - -.bs-tooltip-bottom .tooltip-arrow { - top: 0; -} - -.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow { - top: 0; -} - -.bs-tooltip-bottom .tooltip-arrow::before { - bottom: -1px; - border-width: 0 0.4rem 0.4rem; - border-bottom-color: #000; -} - -.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before { - bottom: -1px; - border-width: 0 0.4rem 0.4rem; - border-bottom-color: #000; -} - -.bs-tooltip-start .tooltip-arrow { - right: 0; - width: 0.4rem; - height: 0.8rem; -} - -.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow { - right: 0; - width: 0.4rem; - height: 0.8rem; -} - -.bs-tooltip-start .tooltip-arrow::before { - left: -1px; - border-width: 0.4rem 0 0.4rem 0.4rem; - border-left-color: #000; -} - -.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before { - left: -1px; - border-width: 0.4rem 0 0.4rem 0.4rem; - border-left-color: #000; -} - -.tooltip-inner { - max-width: 200px; - font-size: 14px; - padding: 6px 16px; - color: #fff; - -webkit-text-align: center; - text-align: center; - background-color: #6d6d6d; - border-radius: 0.25rem; -} - -.popover { - position: absolute; - top: 0; - left: 0; - z-index: 1070; - display: block; - max-width: 276px; - font-family: var(--bs-font-sans-serif); - font-style: normal; - font-weight: 400; - line-height: 1.5; - -webkit-text-align: start; - text-align: start; - -webkit-text-decoration: none; - text-decoration: none; - -webkit-text-shadow: none; - text-shadow: none; - -webkit-text-transform: 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: 0; - border-radius: 0.5rem; - box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); -} - -.bs-popover-top > .popover-arrow { - bottom: calc(-0.5rem - 1px); -} - -.bs-popover-auto[data-popper-placement^=top] > .popover-arrow { - bottom: calc(-0.5rem - 1px); -} - -.bs-popover-top > .popover-arrow::before { - bottom: 0; - border-width: 0.5rem 0.5rem 0; - border-top-color: rgba(0, 0, 0, 0.25); -} - -.bs-popover-auto[data-popper-placement^=top] > .popover-arrow::before { - bottom: 0; - border-width: 0.5rem 0.5rem 0; - border-top-color: rgba(0, 0, 0, 0.25); -} - -.bs-popover-top > .popover-arrow::after { - bottom: 1px; - border-width: 0.5rem 0.5rem 0; - border-top-color: #fff; -} - -.bs-popover-auto[data-popper-placement^=top] > .popover-arrow::after { - bottom: 1px; - border-width: 0.5rem 0.5rem 0; - border-top-color: #fff; -} - -.bs-popover-end > .popover-arrow { - left: calc(-0.5rem - 1px); - width: 0.5rem; - height: 1rem; -} - -.bs-popover-auto[data-popper-placement^=right] > .popover-arrow { - left: calc(-0.5rem - 1px); - width: 0.5rem; - height: 1rem; -} - -.bs-popover-end > .popover-arrow::before { - left: 0; - border-width: 0.5rem 0.5rem 0.5rem 0; - border-right-color: rgba(0, 0, 0, 0.25); -} - -.bs-popover-auto[data-popper-placement^=right] > .popover-arrow::before { - left: 0; - border-width: 0.5rem 0.5rem 0.5rem 0; - border-right-color: rgba(0, 0, 0, 0.25); -} - -.bs-popover-end > .popover-arrow::after { - left: 1px; - border-width: 0.5rem 0.5rem 0.5rem 0; - border-right-color: #fff; -} - -.bs-popover-auto[data-popper-placement^=right] > .popover-arrow::after { - left: 1px; - border-width: 0.5rem 0.5rem 0.5rem 0; - border-right-color: #fff; -} - -.bs-popover-bottom > .popover-arrow { - top: calc(-0.5rem - 1px); -} - -.bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow { - top: calc(-0.5rem - 1px); -} - -.bs-popover-bottom > .popover-arrow::before { - top: 0; - border-width: 0 0.5rem 0.5rem 0.5rem; - border-bottom-color: rgba(0, 0, 0, 0.25); -} - -.bs-popover-auto[data-popper-placement^=bottom] > .popover-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 > .popover-arrow::after { - top: 1px; - border-width: 0 0.5rem 0.5rem 0.5rem; - border-bottom-color: #fff; -} - -.bs-popover-auto[data-popper-placement^=bottom] > .popover-arrow::after { - top: 1px; - border-width: 0 0.5rem 0.5rem 0.5rem; - border-bottom-color: #fff; -} - -.bs-popover-bottom .popover-header::before { - position: absolute; - top: 0; - left: 50%; - display: block; - width: 1rem; - margin-left: -0.5rem; - content: ""; - border-bottom: 1px solid #f0f0f0; -} - -.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before { - position: absolute; - top: 0; - left: 50%; - display: block; - width: 1rem; - margin-left: -0.5rem; - content: ""; - border-bottom: 1px solid #f0f0f0; -} - -.bs-popover-start > .popover-arrow { - right: calc(-0.5rem - 1px); - width: 0.5rem; - height: 1rem; -} - -.bs-popover-auto[data-popper-placement^=left] > .popover-arrow { - right: calc(-0.5rem - 1px); - width: 0.5rem; - height: 1rem; -} - -.bs-popover-start > .popover-arrow::before { - right: 0; - border-width: 0.5rem 0 0.5rem 0.5rem; - border-left-color: rgba(0, 0, 0, 0.25); -} - -.bs-popover-auto[data-popper-placement^=left] > .popover-arrow::before { - right: 0; - border-width: 0.5rem 0 0.5rem 0.5rem; - border-left-color: rgba(0, 0, 0, 0.25); -} - -.bs-popover-start > .popover-arrow::after { - right: 1px; - border-width: 0.5rem 0 0.5rem 0.5rem; - border-left-color: #fff; -} - -.bs-popover-auto[data-popper-placement^=left] > .popover-arrow::after { - right: 1px; - border-width: 0.5rem 0 0.5rem 0.5rem; - border-left-color: #fff; -} - -.popover-header { - padding: 0.5rem 1rem; - margin-bottom: 0; - font-size: 1rem; - background-color: #fff; - border-bottom: 1px solid rgba(0, 0, 0, 0.2); - border-top-left-radius: 0.5rem; - border-top-right-radius: 0.5rem; - font-weight: 500; -} - -.popover-header:empty { - display: none; -} - -.popover-body { - padding: 1rem 1rem; - color: #212529; -} - -.carousel.pointer-event { - touch-action: pan-y; -} - -.carousel-item { - display: none; - margin-right: -100%; - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - transition: transform 0.6s ease-in-out; -} - -.carousel-item.active { - display: block; -} - -.carousel-item-next { - display: block; -} - -.carousel-item-prev { - display: block; -} - -.carousel-item-next:not(.carousel-item-start) { - transform: translateX(100%); -} - -.active.carousel-item-end { - transform: translateX(100%); -} - -.carousel-item-prev:not(.carousel-item-end) { - transform: translateX(-100%); -} - -.active.carousel-item-start { - transform: translateX(-100%); -} - -.carousel-fade .carousel-item { - opacity: 0; - transition-property: opacity; - transform: none; -} - -.carousel-fade .carousel-item.active { - z-index: 1; - opacity: 1; -} - -.carousel-fade .carousel-item-next.carousel-item-start { - z-index: 1; - opacity: 1; -} - -.carousel-fade .carousel-item-prev.carousel-item-end { - z-index: 1; - opacity: 1; -} - -.carousel-fade .active.carousel-item-start { - z-index: 0; - opacity: 0; - transition: opacity 0s 0.6s; -} - -.carousel-fade .active.carousel-item-end { - z-index: 0; - opacity: 0; - transition: opacity 0s 0.6s; -} - -.carousel-indicators { - z-index: 2; - margin-right: 15%; - margin-left: 15%; - list-style: none; -} - -.carousel-indicators [data-bs-target] { - box-sizing: content-box; - flex: 0 1 auto; - width: 30px; - height: 3px; - padding: 0; - margin-right: 3px; - margin-left: 3px; - -webkit-text-indent: -999px; - text-indent: -999px; - cursor: pointer; - background-color: #fff; - background-clip: padding-box; - border: 0; - border-top: 10px solid transparent; - border-bottom: 10px solid transparent; - opacity: 0.5; - transition: opacity 0.6s ease; -} - -.carousel-indicators .active { - opacity: 1; -} - -.carousel-dark .carousel-indicators [data-bs-target] { - background-color: #000; -} - -.offcanvas { - z-index: 1045; -} - -.offcanvas-backdrop { - position: fixed; - top: 0; - left: 0; - z-index: 1040; - width: 100vw; - height: 100vh; - background-color: #000; -} - -.offcanvas-backdrop.fade { - opacity: 0; -} - -.offcanvas-backdrop.show { - opacity: 0.5; -} - -.offcanvas.show { - transform: none; -} - -.link-primary { - color: #0d6efd; -} - -.link-primary:hover { - color: #0a58ca; -} - -.link-primary:focus { - color: #0a58ca; -} - -.sticky-top { - position: sticky; - top: 0; - z-index: 1020; -} - -.vr { - display: inline-block; - align-self: stretch; - width: 1px; - min-height: 1em; - background-color: currentColor; - opacity: 0.25; -} - -.animation { - animation-duration: 1s; - animation-fill-mode: both; - padding: auto; -} - -.fade-in { - animation-name: _fade-in; -} - -.fade-out { - animation-name: _fade-out; -} - -.animation.infinite { - animation-iteration-count: infinite; -} - -.animation.delay-1s { - animation-delay: 1s; -} - -.animation.delay-2s { - animation-delay: 2s; -} - -.animation.delay-3s { - animation-delay: 3s; -} - -.animation.delay-4s { - animation-delay: 4s; -} - -.animation.delay-5s { - animation-delay: 5s; -} - -.animation.fast { - animation-duration: 800ms; -} - -.animation.faster { - animation-duration: 500ms; -} - -.animation.slow { - animation-duration: 2s; -} - -.animation.slower { - animation-duration: 3s; -} - -.slide-in-left { - animation-name: _slide-in-left; -} - -.slide-in-right { - animation-name: _slide-in-right; -} - -.slide-out-left { - animation-name: _slide-out-left; -} - -.slide-out-right { - animation-name: _slide-out-right; -} - -.ripple-surface { - position: relative; - overflow: hidden; - display: inline-block; - vertical-align: bottom; -} - -.ripple-surface-unbound { - overflow: visible; -} - -.ripple-wave { - background-image: radial-gradient(circle, rgba(0, 0, 0, 0.2) 0, rgba(0, 0, 0, 0.3) 40%, rgba(0, 0, 0, 0.4) 50%, rgba(0, 0, 0, 0.5) 60%, transparent 70%); - border-radius: 50%; - opacity: 0.5; - pointer-events: none; - position: absolute; - touch-action: none; - transform: scale(0); - transition-property: transform, opacity; - transition-timing-function: cubic-bezier(0, 0, 0.15, 1), cubic-bezier(0, 0, 0.15, 1); - z-index: 999; -} - -.ripple-wave.active { - transform: scale(1); - opacity: 0; -} - -.btn .ripple-wave { - background-image: radial-gradient(circle, rgba(255, 255, 255, 0.2) 0, rgba(255, 255, 255, 0.3) 40%, rgba(255, 255, 255, 0.4) 50%, rgba(255, 255, 255, 0.5) 60%, rgba(255, 255, 255, 0) 70%); -} - -.ripple-surface-primary .ripple-wave { - background-image: radial-gradient(circle, rgba(18, 102, 241, 0.2) 0, rgba(18, 102, 241, 0.3) 40%, rgba(18, 102, 241, 0.4) 50%, rgba(18, 102, 241, 0.5) 60%, rgba(18, 102, 241, 0) 70%); -} - -.ripple-surface-secondary .ripple-wave { - background-image: radial-gradient(circle, rgba(178, 60, 253, 0.2) 0, rgba(178, 60, 253, 0.3) 40%, rgba(178, 60, 253, 0.4) 50%, rgba(178, 60, 253, 0.5) 60%, rgba(178, 60, 253, 0) 70%); -} - -.ripple-surface-success .ripple-wave { - background-image: radial-gradient(circle, rgba(0, 183, 74, 0.2) 0, rgba(0, 183, 74, 0.3) 40%, rgba(0, 183, 74, 0.4) 50%, rgba(0, 183, 74, 0.5) 60%, rgba(0, 183, 74, 0) 70%); -} - -.ripple-surface-info .ripple-wave { - background-image: radial-gradient(circle, rgba(57, 192, 237, 0.2) 0, rgba(57, 192, 237, 0.3) 40%, rgba(57, 192, 237, 0.4) 50%, rgba(57, 192, 237, 0.5) 60%, rgba(57, 192, 237, 0) 70%); -} - -.ripple-surface-warning .ripple-wave { - background-image: radial-gradient(circle, rgba(255, 169, 0, 0.2) 0, rgba(255, 169, 0, 0.3) 40%, rgba(255, 169, 0, 0.4) 50%, rgba(255, 169, 0, 0.5) 60%, rgba(255, 169, 0, 0) 70%); -} - -.ripple-surface-danger .ripple-wave { - background-image: radial-gradient(circle, rgba(249, 49, 84, 0.2) 0, rgba(249, 49, 84, 0.3) 40%, rgba(249, 49, 84, 0.4) 50%, rgba(249, 49, 84, 0.5) 60%, rgba(249, 49, 84, 0) 70%); -} - -.ripple-surface-light .ripple-wave { - background-image: radial-gradient(circle, rgba(251, 251, 251, 0.2) 0, rgba(251, 251, 251, 0.3) 40%, rgba(251, 251, 251, 0.4) 50%, rgba(251, 251, 251, 0.5) 60%, rgba(251, 251, 251, 0) 70%); -} - -.ripple-surface-dark .ripple-wave { - background-image: radial-gradient(circle, rgba(38, 38, 38, 0.2) 0, rgba(38, 38, 38, 0.3) 40%, rgba(38, 38, 38, 0.4) 50%, rgba(38, 38, 38, 0.5) 60%, rgba(38, 38, 38, 0) 70%); -} - -.ripple-surface-white .ripple-wave { - background-image: radial-gradient(circle, rgba(255, 255, 255, 0.2) 0, rgba(255, 255, 255, 0.3) 40%, rgba(255, 255, 255, 0.4) 50%, rgba(255, 255, 255, 0.5) 60%, rgba(255, 255, 255, 0) 70%); -} - -.ripple-surface-black .ripple-wave { - background-image: radial-gradient(circle, rgba(0, 0, 0, 0.2) 0, rgba(0, 0, 0, 0.3) 40%, rgba(0, 0, 0, 0.4) 50%, rgba(0, 0, 0, 0.5) 60%, transparent 70%); -} - -.datepicker-toggle-button { - position: absolute; - outline: none; - border: none; - background-color: transparent; - right: 10px; - top: 50%; - transform: translate(-50%, -50%); -} - -.datepicker-toggle-button:focus { - color: #2979ff; -} - -.datepicker-toggle-button:hover { - color: #2979ff; -} - -.datepicker-backdrop { - width: 100%; - height: 100%; - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - background-color: rgba(0, 0, 0, 0.4); - z-index: 1065; -} - -.datepicker-dropdown-container { - width: 328px; - height: 380px; - background-color: #fff; - border-radius: 0.5rem; - box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.07), 0 4px 6px -2px rgba(0, 0, 0, 0.05); - z-index: 1066; -} - -.datepicker-modal-container { - display: flex; - flex-direction: column; - position: fixed; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 328px; - height: 512px; - background-color: #fff; - border-radius: 0.6rem 0.6rem 0.5rem 0.5rem; - box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.07), 0 4px 6px -2px rgba(0, 0, 0, 0.05); - z-index: 1066; -} - -.datepicker-header { - height: 120px; - padding-right: 24px; - padding-left: 24px; - background-color: #2979ff; - display: flex; - flex-direction: column; - border-radius: 0.5rem 0.5rem 0 0; -} - -.datepicker-title { - height: 32px; - display: flex; - flex-direction: column; - justify-content: flex-end; -} - -.datepicker-title-text { - font-size: 10px; - font-weight: 400; - -webkit-text-transform: uppercase; - text-transform: uppercase; - letter-spacing: 1.7px; - color: #fff; -} - -.datepicker-date { - height: 72px; - display: flex; - flex-direction: column; - justify-content: flex-end; -} - -.datepicker-date-text { - font-size: 34px; - font-weight: 400; - color: #fff; -} - -.datepicker-main { - position: relative; - height: 100%; -} - -.datepicker-date-controls { - padding: 10px 12px 0 12px; - display: flex; - justify-content: space-between; - color: rgba(0, 0, 0, 0.64); -} - -.datepicker-view-change-button { - padding: 10px; - color: #666; - font-weight: 500; - font-size: 0.9rem; - border-radius: 10px; - box-shadow: none; - background-color: transparent; - margin: 0; - border: none; -} - -.datepicker-view-change-button:hover { - background-color: #eee; -} - -.datepicker-view-change-button:focus { - background-color: #eee; -} - -.datepicker-view-change-button:after { - content: ""; - display: inline-block; - width: 0; - height: 0; - border-left: 5px solid transparent; - border-right: 5px solid transparent; - border-top-width: 5px; - border-top-style: solid; - margin: 0 0 0 5px; - vertical-align: middle; -} - -.datepicker-arrow-controls { - margin-top: 10px; -} - -.datepicker-previous-button { - position: relative; - padding: 0; - width: 40px; - height: 40px; - line-height: 40px; - border: none; - outline: none; - margin: 0; - color: rgba(0, 0, 0, 0.64); - background-color: transparent; - margin-right: 24px; -} - -.datepicker-previous-button:hover { - background-color: #eee; - border-radius: 50%; -} - -.datepicker-previous-button:focus { - background-color: #eee; - border-radius: 50%; -} - -.datepicker-previous-button::after { - top: 0; - left: 0; - right: 0; - bottom: 0; - position: absolute; - content: ""; - margin: 15.5px; - border: 0 solid currentColor; - border-top-width: 2px; - border-left-width: 2px; - transform: translateX(2px) rotate(-45deg); -} - -.datepicker-next-button { - position: relative; - padding: 0; - width: 40px; - height: 40px; - line-height: 40px; - border: none; - outline: none; - margin: 0; - color: rgba(0, 0, 0, 0.64); - background-color: transparent; -} - -.datepicker-next-button:hover { - background-color: #eee; - border-radius: 50%; -} - -.datepicker-next-button:focus { - background-color: #eee; - border-radius: 50%; -} - -.datepicker-next-button::after { - top: 0; - left: 0; - right: 0; - bottom: 0; - position: absolute; - content: ""; - margin: 15.5px; - border: 0 solid currentColor; - border-top-width: 2px; - border-right-width: 2px; - transform: translateX(-2px) rotate(45deg); -} - -.datepicker-view { - padding-left: 12px; - padding-right: 12px; - outline: none; -} - -.datepicker-table { - margin-right: auto; - margin-left: auto; - width: 304px; -} - -.datepicker-day-heading { - width: 40px; - height: 40px; - -webkit-text-align: center; - text-align: center; - font-size: 12px; - font-weight: 400; -} - -.datepicker-cell { - -webkit-text-align: center; - text-align: center; -} - -.datepicker-cell.disabled { - color: #ccc; - cursor: default; - pointer-events: none; -} - -.datepicker-cell.disabled:hover { - cursor: default; -} - -.datepicker-cell:hover { - cursor: pointer; -} - -.datepicker-cell:not(.disabled):not(.selected):hover .datepicker-cell-content { - background-color: #d3d3d3; -} - -.datepicker-cell.selected .datepicker-cell-content { - background-color: #2979ff; - color: #fff; -} - -.datepicker-cell:not(.selected).focused .datepicker-cell-content { - background-color: #eee; -} - -.datepicker-cell.focused .datepicker-cell-content.selected { - background-color: #2979ff; -} - -.datepicker-cell.current .datepicker-cell-content { - border: 1px solid #000; -} - -.datepicker-small-cell { - width: 40px; - height: 40px; -} - -.datepicker-small-cell-content { - width: 36px; - height: 36px; - line-height: 36px; - border-radius: 50%; - font-size: 13px; -} - -.datepicker-large-cell { - width: 76px; - height: 42px; -} - -.datepicker-large-cell-content { - width: 72px; - height: 40px; - line-height: 40px; - padding: 1px 2px; - border-radius: 999px; -} - -.datepicker-footer { - height: 56px; - display: flex; - position: absolute; - width: 100%; - bottom: 0; - justify-content: flex-end; - align-items: center; - padding-left: 12px; - padding-right: 12px; -} - -.datepicker-footer-btn { - background-color: #fff; - color: #2979ff; - border: none; - cursor: pointer; - padding: 0 10px; - -webkit-text-transform: uppercase; - text-transform: uppercase; - font-size: 0.8rem; - font-weight: 500; - height: 40px; - line-height: 40px; - letter-spacing: 0.1rem; - border-radius: 10px; - margin-bottom: 10px; -} - -.datepicker-footer-btn:hover { - background-color: #eee; -} - -.datepicker-footer-btn:focus { - background-color: #eee; -} - -.datepicker-clear-btn { - margin-right: auto; -} - -.timepicker-wrapper { - touch-action: none; - z-index: 1065; - opacity: 0; - right: 0; - bottom: 0; - top: 0; - left: 0; - background-color: rgba(0, 0, 0, 0.4); -} - -.timepicker-elements { - min-width: 310px; - min-height: 325px; - background: #fff; - border-top-right-radius: 0.6rem; - border-top-left-radius: 0.6rem; -} - -.timepicker-head { - background-color: #2979ff; - height: 100px; - border-top-right-radius: 0.5rem; - border-top-left-radius: 0.5rem; - padding: 10px 24px 10px 50px; -} - -.timepicker-button { - font-size: 0.8rem; - min-width: 64px; - box-sizing: border-box; - font-weight: 500; - line-height: 40px; - border-radius: 10px; - letter-spacing: 0.1rem; - -webkit-text-transform: uppercase; - text-transform: uppercase; - color: #2979ff; - border: none; - background-color: transparent; - transition: background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms, border 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; - outline: none; - padding: 0 10px; - height: 40px; - margin-bottom: 10px; -} - -.timepicker-button:hover { - background-color: rgba(0, 0, 0, 0.08); -} - -.timepicker-button:focus { - outline: none; - background-color: rgba(0, 0, 0, 0.08); -} - -.timepicker-current { - font-size: 3.75rem; - font-weight: 300; - line-height: 1.2; - letter-spacing: -0.00833em; - color: #fff; - opacity: 0.54; - border: none; - background: transparent; - padding: 0; -} - -.timepicker-current.active { - opacity: 1; -} - -.timepicker-current-wrapper { - direction: ltr; -} - -.timepicker-mode-wrapper { - margin-left: 20px; - font-size: 18px; - color: rgba(255, 255, 255, 0.54); -} - -.timepicker-mode-wrapper.active { - opacity: 1; -} - -.timepicker-clock-wrapper { - min-width: 310px; - max-width: 325px; - min-height: 305px; - overflow-x: hidden; - height: 100%; -} - -.timepicker-clock { - position: relative; - border-radius: 100%; - width: 260px; - height: 260px; - cursor: default; - margin: 0 auto; - background-color: rgba(0, 0, 0, 0.07); -} - -.timepicker-time-tips-minutes.active { - color: #fff; - background-color: #2979ff; - font-weight: 400; -} - -.timepicker-time-tips-inner.active { - color: #fff; - background-color: #2979ff; - font-weight: 400; -} - -.timepicker-time-tips-hours.active { - color: #fff; - background-color: #2979ff; - font-weight: 400; -} - -.timepicker-time-tips-minutes.disabled { - color: #b3afaf; - pointer-events: none; - background-color: transparent; -} - -.timepicker-time-tips-inner.disabled { - color: #b3afaf; - pointer-events: none; - background-color: transparent; -} - -.timepicker-time-tips-hours.disabled { - color: #b3afaf; - pointer-events: none; - background-color: transparent; -} - -.timepicker-dot { - font-weight: 300; - line-height: 1.2; - letter-spacing: -0.00833em; - color: #fff; - font-size: 3.75rem; - opacity: 0.54; - border: none; - background: transparent; - padding: 0; -} - -.timepicker-middle-dot { - top: 50%; - left: 50%; - width: 6px; - height: 6px; - transform: translate(-50%, -50%); - border-radius: 50%; - background-color: #2979ff; -} - -.timepicker-hand-pointer { - background-color: #2979ff; - bottom: 50%; - height: 40%; - left: calc(50% - 1px); - transform-origin: center bottom 0; - width: 2px; -} - -.timepicker-time-tips.active { - color: #fff; -} - -.timepicker-circle { - top: -21px; - left: -15px; - width: 4px; - border: 14px solid #2979ff; - height: 4px; - box-sizing: content-box; - border-radius: 100%; -} - -.timepicker-hour-mode { - padding: 0; - background-color: transparent; - border: none; - color: #fff; - opacity: 0.54; - cursor: pointer; -} - -.timepicker-hour { - cursor: pointer; -} - -.timepicker-minute { - cursor: pointer; -} - -.timepicker-hour-mode:hover { - background-color: rgba(0, 0, 0, 0.15); - outline: none; -} - -.timepicker-hour-mode:focus { - background-color: rgba(0, 0, 0, 0.15); - outline: none; -} - -.timepicker-hour:hover { - background-color: rgba(0, 0, 0, 0.15); - outline: none; -} - -.timepicker-hour:focus { - background-color: rgba(0, 0, 0, 0.15); - outline: none; -} - -.timepicker-minute:hover { - background-color: rgba(0, 0, 0, 0.15); - outline: none; -} - -.timepicker-minute:focus { - background-color: rgba(0, 0, 0, 0.15); - outline: none; -} - -.timepicker-hour-mode.active { - color: #fff; - opacity: 1; -} - -.timepicker-hour.active { - color: #fff; - opacity: 1; -} - -.timepicker-minute.active { - color: #fff; - opacity: 1; -} - -.timepicker-footer { - border-bottom-left-radius: 0.5rem; - border-bottom-right-radius: 0.5rem; - display: flex; - justify-content: space-between; - align-items: center; - width: 100%; - height: 56px; - padding-left: 12px; - padding-right: 12px; - background-color: #fff; -} - -.timepicker-container { - max-height: calc(100% - 64px); - overflow-y: auto; - box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.07), 0 4px 6px -2px rgba(0, 0, 0, 0.05); -} - -.timepicker-icon-up.active { - opacity: 1; -} - -.timepicker-icon-down.active { - opacity: 1; -} - -.timepicker-toggle-button { - position: absolute; - outline: none; - border: none; - background-color: transparent; - right: 10px; - top: 50%; - transform: translate(-50%, -50%); - transition: all 0.3s ease; - cursor: pointer; -} - -.timepicker-toggle-button:hover { - color: #2979ff; -} - -.timepicker-toggle-button:focus { - color: #2979ff; -} - -.timepicker-input:focus + .timepicker-toggle-button { - color: #2979ff; -} - -.timepicker-input:focus + .timepicker-toggle-button i { - color: #2979ff; -} - -.timepicker a.timepicker-toggle-button { - right: 1px; -} - -.timepicker-toggle-button.timepicker-icon { - right: 1px; -} - -.timepicker-modal .fade.show { - opacity: 1; -} - -.stepper { - position: relative; - padding: 0; - margin: 0; - width: 100%; - list-style: none; - overflow: hidden; - transition: height 0.2s ease-in-out; -} - -.stepper:not(.stepper-vertical) { - display: flex; - justify-content: space-between; -} - -.stepper:not(.stepper-vertical) .stepper-content { - position: absolute; - width: 100%; - padding: 1rem; -} - -.stepper:not(.stepper-vertical) .stepper-step { - flex: auto; - height: 4.5rem; -} - -.stepper:not(.stepper-vertical) .stepper-step:first-child .stepper-head { - padding-left: 1.5rem; -} - -.stepper:not(.stepper-vertical) .stepper-step:last-child .stepper-head { - padding-right: 1.5rem; -} - -.stepper:not(.stepper-vertical) .stepper-step:not(:first-child) .stepper-head:before { - flex: 1; - height: 1px; - width: 100%; - margin-right: 0.5rem; - content: ""; - background-color: rgba(0, 0, 0, 0.1); -} - -.stepper:not(.stepper-vertical) .stepper-step:not(:last-child) .stepper-head:after { - flex: 1; - height: 1px; - width: 100%; - margin-left: 0.5rem; - content: ""; - background-color: rgba(0, 0, 0, 0.1); -} - -.stepper:not(.stepper-vertical) .stepper-head-icon { - margin: 1.5rem 0.5rem 1.5rem 0; -} - -.stepper.stepper-mobile { - justify-content: center; - align-items: flex-end; -} - -.stepper.stepper-mobile.stepper-progress-bar .stepper-head-icon { - display: none; -} - -.stepper.stepper-mobile .stepper-step { - flex: unset; - height: -moz-fit-content; - height: fit-content; - margin: 1rem 0 1rem 0; -} - -.stepper.stepper-mobile .stepper-step:not(:last-child) .stepper-head:after { - margin-left: 0; -} - -.stepper.stepper-mobile .stepper-step:not(:first-child) .stepper-head:before { - margin-right: 0; -} - -.stepper.stepper-mobile .stepper-step:not(:last-child):not(:first-child) .stepper-head { - padding-left: 0.25rem; - padding-right: 0.25rem; -} - -.stepper.stepper-mobile .stepper-head-icon { - font-size: 0; - margin: 0; - height: 0.5rem; - width: 0.5rem; - z-index: 1; -} - -.stepper.stepper-mobile .stepper-head-text { - display: none; -} - -.stepper.stepper-mobile .stepper-content { - top: 2.56rem; -} - -@media (prefers-reduced-motion: reduce) { - .form-control::file-selector-button { - transition: none; - } - - .form-control::-webkit-file-upload-button { - -webkit-transition: none; - transition: none; - } - - .form-switch .form-check-input { - transition: none; - } - - .form-range::-webkit-slider-thumb { - -webkit-transition: none; - transition: none; - } - - .form-range::-moz-range-thumb { - -moz-transition: none; - transition: none; - } - - .form-floating > label { - transition: none; - } - - .fade { - transition: none; - } - - .collapsing { - transition: none; - } - - .collapsing.collapse-horizontal { - transition: none; - } - - .accordion-button::after { - transition: none; - } - - .modal.fade .modal-dialog { - transition: none; - } - - .carousel-item { - transition: none; - } - - .carousel-fade .active.carousel-item-start { - transition: none; - } - - .carousel-fade .active.carousel-item-end { - transition: none; - } - - .carousel-control-prev { - transition: none; - } - - .carousel-control-next { - transition: none; - } - - .carousel-indicators [data-bs-target] { - transition: none; - } - - .spinner-border { - animation-duration: 1.5s; - } - - .spinner-grow { - animation-duration: 1.5s; - } -} - -@media (min-width: 576px) { - .navbar-expand-sm { - flex-wrap: nowrap; - justify-content: flex-start; - } - - .navbar-expand-sm .navbar-nav { - 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 .navbar-nav-scroll { - overflow: visible; - } - - .navbar-expand-sm .navbar-collapse { - display: flex !important; - -ms-flex-basis: auto; - flex-basis: auto; - } - - .navbar-expand-sm .navbar-toggler { - display: none; - } - - .navbar-expand-sm .offcanvas-header { - display: none; - } - - .navbar-expand-sm .offcanvas { - position: inherit; - bottom: 0; - z-index: 1000; - -ms-flex-grow: 1; - flex-grow: 1; - visibility: visible !important; - background-color: transparent; - border-right: 0; - border-left: 0; - transition: none; - transform: none; - } - - .navbar-expand-sm .offcanvas-top { - height: auto; - border-top: 0; - border-bottom: 0; - } - - .navbar-expand-sm .offcanvas-bottom { - height: auto; - border-top: 0; - border-bottom: 0; - } - - .navbar-expand-sm .offcanvas-body { - display: flex; - -ms-flex-grow: 0; - flex-grow: 0; - padding: 0; - overflow-y: visible; - } - - .modal-dialog { - max-width: 500px; - margin: 1.75rem auto; - } - - .modal-dialog-scrollable { - height: calc(100% - 3.5rem); - } - - .modal-dialog-centered { - min-height: calc(100% - 3.5rem); - } - - .modal-sm { - max-width: 300px; - } - - .sticky-sm-top { - position: sticky; - top: 0; - z-index: 1020; - } -} - -@media (min-width: 768px) { - .navbar-expand-md { - flex-wrap: nowrap; - justify-content: flex-start; - } - - .navbar-expand-md .navbar-nav { - 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 .navbar-nav-scroll { - overflow: visible; - } - - .navbar-expand-md .navbar-collapse { - display: flex !important; - -ms-flex-basis: auto; - flex-basis: auto; - } - - .navbar-expand-md .navbar-toggler { - display: none; - } - - .navbar-expand-md .offcanvas-header { - display: none; - } - - .navbar-expand-md .offcanvas { - position: inherit; - bottom: 0; - z-index: 1000; - -ms-flex-grow: 1; - flex-grow: 1; - visibility: visible !important; - background-color: transparent; - border-right: 0; - border-left: 0; - transition: none; - transform: none; - } - - .navbar-expand-md .offcanvas-top { - height: auto; - border-top: 0; - border-bottom: 0; - } - - .navbar-expand-md .offcanvas-bottom { - height: auto; - border-top: 0; - border-bottom: 0; - } - - .navbar-expand-md .offcanvas-body { - display: flex; - -ms-flex-grow: 0; - flex-grow: 0; - padding: 0; - overflow-y: visible; - } - - .sticky-md-top { - position: sticky; - top: 0; - z-index: 1020; - } -} - -@media (min-width: 992px) { - .navbar-expand-lg { - flex-wrap: nowrap; - justify-content: flex-start; - } - - .navbar-expand-lg .navbar-nav { - 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 .navbar-nav-scroll { - overflow: visible; - } - - .navbar-expand-lg .navbar-collapse { - display: flex !important; - -ms-flex-basis: auto; - flex-basis: auto; - } - - .navbar-expand-lg .navbar-toggler { - display: none; - } - - .navbar-expand-lg .offcanvas-header { - display: none; - } - - .navbar-expand-lg .offcanvas { - position: inherit; - bottom: 0; - z-index: 1000; - -ms-flex-grow: 1; - flex-grow: 1; - visibility: visible !important; - background-color: transparent; - border-right: 0; - border-left: 0; - transition: none; - transform: none; - } - - .navbar-expand-lg .offcanvas-top { - height: auto; - border-top: 0; - border-bottom: 0; - } - - .navbar-expand-lg .offcanvas-bottom { - height: auto; - border-top: 0; - border-bottom: 0; - } - - .navbar-expand-lg .offcanvas-body { - display: flex; - -ms-flex-grow: 0; - flex-grow: 0; - padding: 0; - overflow-y: visible; - } - - .modal-lg { - max-width: 800px; - } - - .modal-xl { - max-width: 800px; - } - - .sticky-lg-top { - position: sticky; - top: 0; - z-index: 1020; - } -} - -@media (min-width: 1200px) { - .navbar-expand-xl { - flex-wrap: nowrap; - justify-content: flex-start; - } - - .navbar-expand-xl .navbar-nav { - 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 .navbar-nav-scroll { - overflow: visible; - } - - .navbar-expand-xl .navbar-collapse { - display: flex !important; - -ms-flex-basis: auto; - flex-basis: auto; - } - - .navbar-expand-xl .navbar-toggler { - display: none; - } - - .navbar-expand-xl .offcanvas-header { - display: none; - } - - .navbar-expand-xl .offcanvas { - position: inherit; - bottom: 0; - z-index: 1000; - -ms-flex-grow: 1; - flex-grow: 1; - visibility: visible !important; - background-color: transparent; - border-right: 0; - border-left: 0; - transition: none; - transform: none; - } - - .navbar-expand-xl .offcanvas-top { - height: auto; - border-top: 0; - border-bottom: 0; - } - - .navbar-expand-xl .offcanvas-bottom { - height: auto; - border-top: 0; - border-bottom: 0; - } - - .navbar-expand-xl .offcanvas-body { - display: flex; - -ms-flex-grow: 0; - flex-grow: 0; - padding: 0; - overflow-y: visible; - } - - .modal-xl { - max-width: 1140px; - } - - .sticky-xl-top { - position: sticky; - top: 0; - z-index: 1020; - } -} - -@media (min-width: 1400px) { - .navbar-expand-xxl { - flex-wrap: nowrap; - justify-content: flex-start; - } - - .navbar-expand-xxl .navbar-nav { - flex-direction: row; - } - - .navbar-expand-xxl .navbar-nav .dropdown-menu { - position: absolute; - } - - .navbar-expand-xxl .navbar-nav .nav-link { - padding-right: 0.5rem; - padding-left: 0.5rem; - } - - .navbar-expand-xxl .navbar-nav-scroll { - overflow: visible; - } - - .navbar-expand-xxl .navbar-collapse { - display: flex !important; - -ms-flex-basis: auto; - flex-basis: auto; - } - - .navbar-expand-xxl .navbar-toggler { - display: none; - } - - .navbar-expand-xxl .offcanvas-header { - display: none; - } - - .navbar-expand-xxl .offcanvas { - position: inherit; - bottom: 0; - z-index: 1000; - -ms-flex-grow: 1; - flex-grow: 1; - visibility: visible !important; - background-color: transparent; - border-right: 0; - border-left: 0; - transition: none; - transform: none; - } - - .navbar-expand-xxl .offcanvas-top { - height: auto; - border-top: 0; - border-bottom: 0; - } - - .navbar-expand-xxl .offcanvas-bottom { - height: auto; - border-top: 0; - border-bottom: 0; - } - - .navbar-expand-xxl .offcanvas-body { - display: flex; - -ms-flex-grow: 0; - flex-grow: 0; - padding: 0; - overflow-y: visible; - } - - .sticky-xxl-top { - position: sticky; - top: 0; - z-index: 1020; - } -} - -@media (max-width: 575.98px) { - .modal-fullscreen-sm-down { - width: 100vw; - max-width: none; - height: 100%; - margin: 0; - } - - .modal-fullscreen-sm-down .modal-content { - height: 100%; - border: 0; - border-radius: 0; - } - - .modal-fullscreen-sm-down .modal-header { - border-radius: 0; - } - - .modal-fullscreen-sm-down .modal-body { - overflow-y: auto; - } - - .modal-fullscreen-sm-down .modal-footer { - border-radius: 0; - } -} - -@media (max-width: 767.98px) { - .modal-fullscreen-md-down { - width: 100vw; - max-width: none; - height: 100%; - margin: 0; - } - - .modal-fullscreen-md-down .modal-content { - height: 100%; - border: 0; - border-radius: 0; - } - - .modal-fullscreen-md-down .modal-header { - border-radius: 0; - } - - .modal-fullscreen-md-down .modal-body { - overflow-y: auto; - } - - .modal-fullscreen-md-down .modal-footer { - border-radius: 0; - } -} - -@media (max-width: 991.98px) { - .modal-fullscreen-lg-down { - width: 100vw; - max-width: none; - height: 100%; - margin: 0; - } - - .modal-fullscreen-lg-down .modal-content { - height: 100%; - border: 0; - border-radius: 0; - } - - .modal-fullscreen-lg-down .modal-header { - border-radius: 0; - } - - .modal-fullscreen-lg-down .modal-body { - overflow-y: auto; - } - - .modal-fullscreen-lg-down .modal-footer { - border-radius: 0; - } -} - -@media (max-width: 1199.98px) { - .modal-fullscreen-xl-down { - width: 100vw; - max-width: none; - height: 100%; - margin: 0; - } - - .modal-fullscreen-xl-down .modal-content { - height: 100%; - border: 0; - border-radius: 0; - } - - .modal-fullscreen-xl-down .modal-header { - border-radius: 0; - } - - .modal-fullscreen-xl-down .modal-body { - overflow-y: auto; - } - - .modal-fullscreen-xl-down .modal-footer { - border-radius: 0; - } -} - -@media (max-width: 1399.98px) { - .modal-fullscreen-xxl-down { - width: 100vw; - max-width: none; - height: 100%; - margin: 0; - } - - .modal-fullscreen-xxl-down .modal-content { - height: 100%; - border: 0; - border-radius: 0; - } - - .modal-fullscreen-xxl-down .modal-header { - border-radius: 0; - } - - .modal-fullscreen-xxl-down .modal-body { - overflow-y: auto; - } - - .modal-fullscreen-xxl-down .modal-footer { - border-radius: 0; - } -} - -@media (prefers-reduced-motion) { - .animation { - transition: none !important; - animation: unset !important; - } -} - -@media screen and (min-width: 320px) and (max-width: 820px) and (orientation: landscape) { - .datepicker-modal-container .datepicker-header { - height: 100%; - } - - .datepicker-modal-container .datepicker-date { - margin-top: 100px; - } - - .datepicker-modal-container .datepicker-day-cell { - width: 32x; - height: 32x; - } - - .datepicker-modal-container { - flex-direction: row; - width: 475px; - height: 360px; - } - - .datepicker-modal-container.datepicker-day-cell { - width: 36px; - height: 36px; - } -} - -@media screen and (min-width: 320px) and (max-width: 825px) and (orientation: landscape) { - .timepicker-elements { - flex-direction: row !important; - border-bottom-left-radius: 0.5rem; - min-width: auto; - min-height: auto; - overflow-y: auto; - } - - .timepicker-head { - border-top-right-radius: 0; - border-bottom-left-radius: 0; - padding: 10px; - padding-right: 10px !important; - height: auto; - min-height: 305px; - } - - .timepicker-head-content { - flex-direction: column; - } - - .timepicker-mode-wrapper { - justify-content: space-around !important; - flex-direction: row !important; - } - - .timepicker-current { - font-size: 3rem; - font-weight: 400; - } - - .timepicker-dot { - font-size: 3rem; - font-weight: 400; - } -} - -@keyframes _spinner-grow { - 0% { - transform: scale(0); - } - - 50% { - opacity: 1; - transform: none; - } -} - -@keyframes _fade-in { - from { - opacity: 0; - } - - to { - opacity: 1; - } -} - -@keyframes _fade-out { - from { - opacity: 1; - } - - to { - opacity: 0; - } -} - -@keyframes _fade-in-down { - from { - opacity: 0; - transform: translate3d(0, -100%, 0); - } - - to { - opacity: 1; - transform: translate3d(0, 0, 0); - } -} - -@keyframes _fade-in-left { - from { - opacity: 0; - transform: translate3d(-100%, 0, 0); - } - - to { - opacity: 1; - transform: translate3d(0, 0, 0); - } -} - -@keyframes _fade-in-right { - from { - opacity: 0; - transform: translate3d(100%, 0, 0); - } - - to { - opacity: 1; - transform: translate3d(0, 0, 0); - } -} - -@keyframes _fade-in-up { - from { - opacity: 0; - transform: translate3d(0, 100%, 0); - } - - to { - opacity: 1; - transform: translate3d(0, 0, 0); - } -} - -@keyframes _fade-out-down { - from { - opacity: 1; - } - - to { - opacity: 0; - transform: translate3d(0, 100%, 0); - } -} - -@keyframes _fade-out-left { - from { - opacity: 1; - } - - to { - opacity: 0; - transform: translate3d(-100%, 0, 0); - } -} - -@keyframes _fade-out-right { - from { - opacity: 1; - } - - to { - opacity: 0; - transform: translate3d(100%, 0, 0); - } -} - -@keyframes _fade-out-up { - from { - opacity: 1; - } - - to { - opacity: 0; - transform: translate3d(0, -100%, 0); - } -} - -@keyframes _slide-in-down { - from { - visibility: visible; - transform: translate3d(0, -100%, 0); - } - - to { - transform: translate3d(0, 0, 0); - } -} - -@keyframes _slide-in-left { - from { - visibility: visible; - transform: translate3d(-100%, 0, 0); - } - - to { - transform: translate3d(0, 0, 0); - } -} - -@keyframes _slide-in-right { - from { - visibility: visible; - transform: translate3d(100%, 0, 0); - } - - to { - transform: translate3d(0, 0, 0); - } -} - -@keyframes _slide-in-up { - from { - visibility: visible; - transform: translate3d(0, 100%, 0); - } - - to { - transform: translate3d(0, 0, 0); - } -} - -@keyframes _slide-out-down { - from { - transform: translate3d(0, 0, 0); - } - - to { - visibility: hidden; - transform: translate3d(0, 100%, 0); - } -} - -@keyframes _slide-out-left { - from { - transform: translate3d(0, 0, 0); - } - - to { - visibility: hidden; - transform: translate3d(-100%, 0, 0); - } -} - -@keyframes _slide-out-right { - from { - transform: translate3d(0, 0, 0); - } - - to { - visibility: hidden; - transform: translate3d(100%, 0, 0); - } -} - -@keyframes _slide-out-up { - from { - transform: translate3d(0, 0, 0); - } - - to { - visibility: hidden; - transform: translate3d(0, -100%, 0); - } -} - -@keyframes _slide-down { - from { - transform: translate3d(0, 0, 0); - } - - to { - transform: translate3d(0, 100%, 0); - } -} - -@keyframes _slide-left { - from { - transform: translate3d(0, 0, 0); - } - - to { - transform: translate3d(-100%, 0, 0); - } -} - -@keyframes _slide-right { - from { - transform: translate3d(0, 0, 0); - } - - to { - transform: translate3d(100%, 0, 0); - } -} - -@keyframes _slide-up { - from { - transform: translate3d(0, 0, 0); - } - - to { - transform: translate3d(0, -100%, 0); - } -} - -@keyframes _zoom-in { - from { - opacity: 0; - transform: scale3d(0.3, 0.3, 0.3); - } - - 50% { - opacity: 1; - } -} - -@keyframes _zoom-out { - from { - opacity: 1; - } - - 50% { - opacity: 0; - transform: scale3d(0.3, 0.3, 0.3); - } - - to { - opacity: 0; - } -} - -@keyframes _tada { - from { - transform: scale3d(1, 1, 1); - } - - 10% { - transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg); - } - - 20% { - transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg); - } - - 30% { - transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); - } - - 50% { - transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); - } - - 70% { - transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); - } - - 90% { - transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); - } - - 40% { - transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); - } - - 60% { - transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); - } - - 80% { - transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); - } - - to { - transform: scale3d(1, 1, 1); - } -} - -@keyframes _pulse { - from { - transform: scale3d(1, 1, 1); - } - - 50% { - transform: scale3d(1.05, 1.05, 1.05); - } - - to { - transform: scale3d(1, 1, 1); - } -} - -@keyframes _show-up-clock { - 0% { - opacity: 0; - transform: scale(0.7); - } - - to { - opacity: 1; - transform: scale(1); - } -} - -.visible { - visibility: visible; -} - -.collapse { - visibility: collapse; -} - -.static { - position: static; -} - -.fixed { - position: fixed; -} - -.absolute { - position: absolute; -} - -.relative { - position: relative; -} - -.sticky { - position: sticky; -} - -.my-10 { - margin-top: 2.5rem; - margin-bottom: 2.5rem; -} - -.my-3 { - margin-top: 0.75rem; - margin-bottom: 0.75rem; -} - -.block { - display: block; -} - -.inline { - display: inline; -} - -.flex { - display: flex; -} - -.table { - display: table; -} - -.hidden { - display: none; -} - -.h-full { - height: 100%; -} - -.w-full { - width: 100%; -} - -.max-w-md { - max-width: 28rem; -} - -.transform { - transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); -} - -.resize { - resize: both; -} - -.flex-row { - flex-direction: row; -} - -.flex-col { - flex-direction: column; -} - -.items-center { - align-items: center; -} - -.justify-center { - justify-content: center; -} - -.justify-between { - justify-content: space-between; -} - -.justify-around { - justify-content: space-around; -} - -.justify-evenly { - justify-content: space-evenly; -} - -.rounded-lg { - border-radius: 0.5rem; -} - -.bg-slate-200 { - --tw-bg-opacity: 1; - background-color: rgb(226 232 240 / var(--tw-bg-opacity)); -} - -.bg-slate-100 { - --tw-bg-opacity: 1; - background-color: rgb(241 245 249 / var(--tw-bg-opacity)); -} - -.p-6 { - padding: 1.5rem; -} - -.shadow-lg { - --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); - --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); -} - -.outline { - outline-style: solid; -} - -.blur { - --tw-blur: blur(8px); - filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); -} - -.filter { - filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); -} - -label.form-entry { - margin-bottom: 0.5rem; - display: inline-block; - --tw-text-opacity: 1; - color: rgb(51 65 85 / var(--tw-text-opacity)); -} - -@media (prefers-color-scheme: dark) { - label.form-entry { - --tw-text-opacity: 1; - color: rgb(226 232 240 / var(--tw-text-opacity)); - } -} - -input.form-entry { - margin: 0px; - display: block; - width: 100%; - border-radius: 0.25rem; - border-width: 1px; - border-style: solid; - --tw-border-opacity: 1; - border-color: rgb(203 213 225 / var(--tw-border-opacity)); - --tw-bg-opacity: 1; - background-color: rgb(241 245 249 / var(--tw-bg-opacity)); - background-clip: padding-box; - padding-left: 0.75rem; - padding-right: 0.75rem; - padding-top: 0.375rem; - padding-bottom: 0.375rem; - font-size: 1rem; - line-height: 1.5rem; - font-weight: 400; - --tw-text-opacity: 1; - color: rgb(71 85 105 / var(--tw-text-opacity)); - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; - transition-duration: 150ms; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); -} - -input.form-entry:focus { - --tw-border-opacity: 1; - border-color: rgb(37 99 235 / var(--tw-border-opacity)); - --tw-bg-opacity: 1; - background-color: rgb(241 245 249 / var(--tw-bg-opacity)); - --tw-text-opacity: 1; - color: rgb(51 65 85 / var(--tw-text-opacity)); - outline: 2px solid transparent; - outline-offset: 2px; -} - -@media (prefers-color-scheme: dark) { - input.form-entry { - --tw-border-opacity: 1; - border-color: rgb(75 85 99 / var(--tw-border-opacity)); - --tw-bg-opacity: 1; - background-color: rgb(15 23 42 / var(--tw-bg-opacity)); - --tw-text-opacity: 1; - color: rgb(203 213 225 / var(--tw-text-opacity)); - } - - input.form-entry:focus { - --tw-border-opacity: 1; - border-color: rgb(147 197 253 / var(--tw-border-opacity)); - --tw-bg-opacity: 1; - background-color: rgb(30 41 59 / var(--tw-bg-opacity)); - --tw-text-opacity: 1; - color: rgb(226 232 240 / var(--tw-text-opacity)); - } -} - -small.form-entry { - margin-top: 0.25rem; - display: block; - padding-left: 0.75rem; - padding-right: 0.75rem; - padding-top: 0.375rem; - padding-bottom: 0.375rem; - font-size: 0.75rem; - line-height: 1rem; - --tw-text-opacity: 1; - color: rgb(71 85 105 / var(--tw-text-opacity)); -} - -@media (prefers-color-scheme: dark) { - small.form-entry { - --tw-text-opacity: 1; - color: rgb(148 163 184 / var(--tw-text-opacity)); - } -} - -a.link-primary { - margin-bottom: 1rem; - --tw-text-opacity: 1; - color: rgb(59 130 246 / var(--tw-text-opacity)); - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; - transition-duration: 300ms; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); -} - -a.link-primary:hover { - --tw-text-opacity: 1; - color: rgb(37 99 235 / var(--tw-text-opacity)); -} - -a.link-primary:active { - --tw-text-opacity: 1; - color: rgb(29 78 216 / var(--tw-text-opacity)); -} - -div.btn { - margin-top: 0.75rem; - display: grid; - grid-template-columns: repeat(2, minmax(0, 1fr)); - -moz-column-gap: 1.5rem; - column-gap: 1.5rem; -} - -button.btn { - display: inline-block; - border-radius: 0.25rem; - padding-left: 1.5rem; - padding-right: 1.5rem; - padding-top: 0.625rem; - padding-bottom: 0.625rem; - font-size: 0.75rem; - line-height: 1rem; - font-weight: 500; - text-transform: uppercase; - line-height: 1.25; - --tw-text-opacity: 1; - color: rgb(241 245 249 / var(--tw-text-opacity)); - --tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); - --tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; - transition-duration: 150ms; - transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); -} - -button.btn:hover { - --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); - --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); -} - -button.btn:focus { - --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); - --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); - outline: 2px solid transparent; - outline-offset: 2px; - --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color); - --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color); - box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000); -} - -button.btn:active { - --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); - --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); - box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); -} - -button.btn-primary { - --tw-bg-opacity: 1; - background-color: rgb(37 99 235 / var(--tw-bg-opacity)); -} - -button.btn-primary:hover { - --tw-bg-opacity: 1; - background-color: rgb(29 78 216 / var(--tw-bg-opacity)); -} - -button.btn-primary:focus { - --tw-bg-opacity: 1; - background-color: rgb(29 78 216 / var(--tw-bg-opacity)); -} - -button.btn-primary:active { - --tw-bg-opacity: 1; - background-color: rgb(30 58 138 / var(--tw-bg-opacity)); -} - -button.btn-secondary { - --tw-bg-opacity: 1; - background-color: rgb(71 85 105 / var(--tw-bg-opacity)); -} - -button.btn-secondary:hover { - --tw-bg-opacity: 1; - background-color: rgb(51 65 85 / var(--tw-bg-opacity)); -} - -button.btn-secondary:focus { - --tw-bg-opacity: 1; - background-color: rgb(51 65 85 / var(--tw-bg-opacity)); -} - -button.btn-secondary:active { - --tw-bg-opacity: 1; - background-color: rgb(15 23 42 / var(--tw-bg-opacity)); -} - -.hide { - display: none !important; -} - -@media (prefers-color-scheme: dark) { - .dark\:bg-slate-900 { - --tw-bg-opacity: 1; - background-color: rgb(15 23 42 / var(--tw-bg-opacity)); - } - - .dark\:bg-slate-800 { - --tw-bg-opacity: 1; - background-color: rgb(30 41 59 / var(--tw-bg-opacity)); - } -} diff --git a/Source/loaderConfigurator/ldr_config_old.py b/Source/loaderConfigurator/ldr_config_old.py deleted file mode 100755 index 809f5fd0..00000000 --- a/Source/loaderConfigurator/ldr_config_old.py +++ /dev/null @@ -1,173 +0,0 @@ -#!python3 -cust_conf = { -# DRAM Timing: -# 0: AUTO_ADJ_MARIKO_SAFE: Auto adjust timings for LPDDR4 ≤3733 Mbps specs, 8Gb density (Default). -# 1: AUTO_ADJ_MARIKO_4266: Auto adjust timings for LPDDR4X 4266 Mbps specs, 8Gb density. -# 2: ENTIRE_TABLE_ERISTA: -# 3: ENTIRE_TABLE_MARIKO: Replace the entire max mtc table with customized one (provided by user). - "mtcConf": 0, -# Mariko CPU: -# - Max Clock in kHz: -# Default: 1785000 -# >= 2193000 will enable overvolting (> 1120 mV) -# - Boost Clock in kHz: -# Default: 1785000 -# Boost clock will be applied when applications request higher CPU frequency for quicker loading. -# - Max Voltage in mV: -# Default voltage: 1120 -# Haven't tested anything higher than 1220. - "marikoCpuMaxClock": 2397000, - "marikoCpuBoostClock": 1785000, - "marikoCpuMaxVolt": 1220, -# Mariko GPU: -# - Max Clock in kHz: -# Default: 921600 -# NVIDIA Maximum: 1267200 - "marikoGpuMaxClock": 1305600, -# Mariko EMC: -# - RAM Clock in kHz: -# Values should be > 1600000, and divided evenly by 9600. -# [WARNING] -# RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM: -# - Graphical glitches -# - System instabilities -# - NAND corruption -# Timings from auto-adjustment have been tested safe for up to 1996.8 MHz for all DRAM chips. - "marikoEmcMaxClock": 1996800, -# Erista CPU: -# Not tested and not enabled by default. -# - Enable Overclock -# Require modificaitions towards NewCpuTables! -# - Max Voltage in mV - "eristaCpuOCEnable": 0, - "eristaCpuMaxVolt": 0, -# Erista EMC: -# - RAM Clock in kHz -# [WARNING] -# RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM: -# - Graphical glitches -# - System instabilities -# - NAND corruption -# - RAM Voltage in uV -# Range: 600'000 to 1250'000 uV -# Value should be divided evenly by 12'500 -# Default(HOS): 1125'000 -# Not enabled by default. - "eristaEmcMaxClock": 1862400, - "eristaEmcVolt": 0 -} - -cust_range = { - "mtcConf": (0, 3), - "marikoCpuMaxClock": (1785000, 3000000), - "marikoCpuBoostClock": (1785000, 3000000), - "marikoCpuMaxVolt": (1100, 1300), - "marikoGpuMaxClock": (768000, 1536000), - "marikoEmcMaxClock": (1612800, 2400000), - "eristaCpuMaxVolt": (1100, 1400), - "eristaEmcMaxClock": (1600000, 2400000), - "eristaEmcVolt": (1100000, 1250000) -} - - -import struct -import argparse - -cust_rev = 2 -cust_head = ["cust", "custRev"] -cust_body = ["mtcConf", - "marikoCpuMaxClock", "marikoCpuBoostClock", "marikoCpuMaxVolt", "marikoGpuMaxClock", "marikoEmcMaxClock", - "eristaCpuOCEnable", "eristaCpuMaxVolt", "eristaEmcMaxClock", "eristaEmcVolt"] -cust_key = [*cust_head, *cust_body] -cust_val_num = len(cust_conf) - 1 -cust_fmt = '<4s2H' + str(cust_val_num) + 'I' -cust_head_fmt = '<4s1H' -cust_body_fmt = '<1H' + str(cust_val_num) + 'I' - -parser = argparse.ArgumentParser(description='Loader Configurator v'+str(cust_rev)) -parser.add_argument("file", help="Path of loader.kip") -parser.add_argument("--save", "-s", action="store_true", help="Save configuration to loader.kip") -parser.add_argument("--ignore", action="store_true", help="Ignore range safety check") -args = parser.parse_args() - -def KIPCustParse(file_loc, conf_print=True) -> (int, dict): - with open(file_loc, "rb") as file: - header_str = b'KIP1Loader' - header = file.read(len(header_str)) - file.seek(0) - cust_magic = b'CUST' - cust_pos = file.read().find(cust_magic) - - if header != header_str or cust_pos == -1: - raise Exception("\n Invalid kip file!") - - file.seek(cust_pos) - cust_size = struct.calcsize(cust_fmt) - cust_buf = file.read(cust_size) - cust_val = struct.unpack(cust_fmt, cust_buf) - cust_dict = dict(zip(cust_key, cust_val)) - - if cust_dict['custRev'] != cust_rev: - raise Exception(f"\n custRev does NOT match, expected: {cust_rev}, got: {cust_dict['custRev']}!") - - [cust_dict.pop(key) for key in cust_head] - - if conf_print: - print("Configuration from file") - [print(f"- {i:20s} : {cust_dict[i]:8d}") for i in cust_dict] - - return (cust_pos, cust_dict) - - -def CustRangeCheck(cust): - range_error_str = "" - for i in cust_range: - val = int(cust[i]) - if val and (val < cust_range[i][0] or val > cust_range[i][1]) : - range_error_str += f"\n- {i:20s} = {val:8d}, Expected range: {[*cust_range[i]]}" - - if range_error_str: - raise ValueError(range_error_str) - - -def KIPCustSave(file_loc, cust_pos, cust_dict, range_check=True, cust_to_save={}): - missing = set(cust_body) - set(cust_to_save.keys()) - if missing: - missing_str = "\n Invalid cust! Missing: " - for i in missing: - missing_str += f"\n- {i}" - raise Exception(missing_str) - - if range_check: - CustRangeCheck(cust_to_save) - - diff_count = 0 - for i in cust_body: - diff_str = "" - if cust_dict[i] != cust_conf[i]: - diff_str = f"-> {cust_conf[i]:8d}" - diff_count += 1 - print(f"- {i:20s} : {cust_dict[i]:8d} {diff_str}") - - if not diff_count: - print("Cust is identical, abort saving!") - return - - with open(file_loc, "rb+") as file: - cust_bin = struct.pack(cust_body_fmt, *[cust_to_save[i] for i in cust_body]) - file.seek(cust_pos + struct.calcsize(cust_head_fmt)) - file.write(cust_bin) - - print("Done!") - - -def main(file_loc, ignore=False, save=False): - (cust_pos, cust_dict) = KIPCustParse(file_loc, conf_print=(not save)) - - if save: - print("Saving new configuration...") - KIPCustSave(file_loc, cust_pos, cust_dict, range_check=(not ignore), cust_to_save=cust_conf) - - -if __name__ == "__main__": - main(args.file, args.ignore, args.save) diff --git a/Source/loaderConfigurator/package.json b/Source/loaderConfigurator/package.json deleted file mode 100644 index a763f8c7..00000000 --- a/Source/loaderConfigurator/package.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "devDependencies": { - "tailwindcss": "^3.2.2" - }, - "dependencies": { - "tw-elements": "^1.0.0-alpha12" - } -} diff --git a/Source/loaderConfigurator/src/index.html b/Source/loaderConfigurator/src/index.html deleted file mode 100644 index 7daab2d8..00000000 --- a/Source/loaderConfigurator/src/index.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - -
    -
    - - Get latest here - -
    - - -
    -
    -
    - - - diff --git a/Source/loaderConfigurator/src/main.ts b/Source/loaderConfigurator/src/main.ts deleted file mode 100644 index 0333ec8e..00000000 --- a/Source/loaderConfigurator/src/main.ts +++ /dev/null @@ -1,320 +0,0 @@ -const CUST_REV = 2; -var buffer: string | ArrayBuffer; - -function FindMagicOffset(buffer) { - let view = new DataView(buffer); - for (let i = 0; i < view.byteLength; i += 4) { - if (view.getUint32(i, true) == 0x54535543) { // "CUST" - return i; - } - } - throw new Error("Invalid loader.kip file"); -} - -function CustEntry(name: string, size: number, desc: string, defval: number, minmax = [0, 1_000_000], step = 1, extra_validator?) { - this.name = name; - this.size = size; - this.desc = desc; - this.defval = defval; - this.min = minmax[0]; - this.max = minmax[1]; - this.step = step; - this.validator = extra_validator; - this.value = null; - this.offset = null; -} - -function InitCustTable() { - let cust = [ - new CustEntry( - "mtcConf", - 2, - "DRAM Timing\ -
  • 0: AUTO_ADJ_MARIKO_SAFE: Auto adjust timings for LPDDR4 ≤3733 Mbps specs, 8Gb density.
  • \ -
  • 1: AUTO_ADJ_MARIKO_4266: Auto adjust timings for LPDDR4X 4266 Mbps specs, 8Gb density.
  • ", - 0, - [0, 3], - ), - new CustEntry( - "marikoCpuMaxClock", - 4, - "Mariko CPU Max Clock in kHz\ -
  • System default: 1785000
  • \ -
  • ≥ 2397000 will enable overvolting (> 1120 mV)
  • ", - 2397_000, - [1785_000, 3000_000], - 100, - (x: number) => (x % 100) == 0 - ), - new CustEntry( - "marikoCpuBoostClock", - 4, - "Mariko CPU Boost Clock in kHz\ -
  • System default: 1785000
  • \ -
  • Must not be higher than marikoCpuMaxClock
  • ", - 1785_000, - [1785_000, 3000_000], - 100, - (x: number) => (x % 100) == 0 - ), - new CustEntry( - "marikoCpuMaxVolt", - 4, - "Mariko CPU Max Voltage in mV\ -
  • System default: 1120
  • \ -
  • Acceptable range: 1100 ≤ x ≤ 1300
  • ", - 1235, - [1100, 1300], - ), - new CustEntry( - "marikoGpuMaxClock", - 4, - "Mariko GPU Max Clock in kHz\ -
  • System default: 921600
  • \ -
  • Tegra X1+ official maximum: 1267200
  • ", - 1305_600, - [768_000, 1536_000], - 100, - (x: number) => (x % 100) == 0 - ), - new CustEntry( - "marikoEmcMaxClock", - 4, - "Mariko RAM Max Clock in kHz\ -
  • Values should be > 1600000, and divided evenly by 3200.
  • \ -
  • WARNING: RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM
  • ", - 1996_800, - [1612_800, 2400_000], - 3200, - (x: number) => (x % 3200) == 0 - ), - new CustEntry( - "eristaCpuOCEnable", - 4, - "Erista CPU Enable Overclock\ -
  • Not tested
  • ", - 1, - [0, 1] - ), - new CustEntry( - "eristaCpuMaxVolt", - 4, - "Erista CPU Max Voltage in mV\ -
  • Acceptable range: 1100 ≤ x ≤ 1400
  • ", - 1257, - [0, 1400], - 100, - (x: number) => x >= 1100 - ), - new CustEntry( - "eristaEmcMaxClock", - 4, - "Erista RAM Max Clock in kHz\ -
  • Values should be > 1600000, and divided evenly by 3200.
  • \ -
  • WARNING: RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM
  • ", - 1862_400, - [1600_000, 2400_000], - 3200, - (x: number) => (x % 3200) == 0 - ), - new CustEntry( - "eristaEmcVolt", - 4, - "Erista RAM Voltage in uV\ -
  • Acceptable range: 1100000 ≤ x ≤ 1250000, and it should be divided evenly by 12500.
  • \ -
  • Not enabled by default
  • ", - 0, - [0, 1250_000], - 12500, - (x: number) => (x % 12500) == 0 && x >= 1100000 - ), - ]; - return cust; -} - -function ValidateCust(cust) { - for (let i of cust) { - if (i.value == 0) - continue; - if (i.value < i.min || i.value > i.max) { - document.getElementById(i.name)!.focus(); - throw new Error(`Expected range: ${i.min} ≤ ${i.name} ≤ ${i.max}, got ${i.value}`); - } - if (i.validator && !i.validator(i.value)) { - document.getElementById(i.name)!.focus(); - throw new Error(`Invalid value: ${i.value}(${i.name})\nValidator: ${i.validator}`); - } - } -} - -function SaveCust(cust, buffer) { - let dict = Object.assign({}, ...cust.map((x: { name: any; }) => ({[x.name]: x}))); - let view = new DataView(buffer); - let storage = {}; - for (let i of cust) { - let id = i.name; - i.value = (document.getElementById(id) as HTMLInputElement).value; - storage[i.name] = i.value; - switch (i.size) { - case 2: - view.setUint16(i.offset, i.value, true); - break; - case 4: - view.setUint32(i.offset, i.value, true); - break; - default: - document.getElementById(i.name)!.focus(); - throw new Error("Unknown size at " + i); - } - } - ValidateCust(cust); - storage["custRev"] = CUST_REV; - localStorage.setItem("last_saved", JSON.stringify(storage)); - let a = document.createElement("a"); - a.href = window.URL.createObjectURL(new Blob([buffer], {type: "application/octet-stream"})); - a.download = "loader.kip"; - a.click(); -} - -function LastSaved() { - let storage = localStorage.getItem("last_saved"); - if (!storage) { - return false; - } - let sObj = JSON.parse(storage); - if (!sObj["custRev"] || sObj["custRev"] != CUST_REV) { - localStorage.removeItem("last_saved"); - return false; - } - return true; -} - -function LoadLastSaved() { - if (LastSaved()) { - let storage = localStorage.getItem("last_saved"); - let sObj = JSON.parse(storage!); - for (let key in sObj) { - if (key == "custRev") { - continue; - } - (document.getElementById(key) as HTMLInputElement).value = sObj[key]; - } - } -} - -function LoadDefault(cust: any[]) { - let dict = Object.assign({}, ...cust.map((x: { name: any; }) => ({[x.name]: x}))); - for (let i of cust) { - let id = i.name; - (document.getElementById(id) as HTMLInputElement).value = i.defval; - } -} - -function UpdateHTMLForm(cust: any[]) { - let dict = Object.assign({}, ...cust.map((x: { name: any; }) => ({[x.name]: x}))); - let form = document.getElementById("form"); - for (let i of cust) { - let id = i.name; - let input = document.getElementById(id) as HTMLInputElement; - if (!input) { - let div = document.createElement("div"); - div.classList.add("form-floating", "my-3"); - let label = document.createElement("label"); - label.setAttribute("for", id); - label.innerHTML = id; - label.classList.add("form-entry"); - input = document.createElement("input"); - input.classList.add("form-control", "form-entry"); - input.min = dict[i.name].min; - input.max = dict[i.name].max; - input.id = id; - input.type = "number"; - input.step = dict[i.name].step; - div.appendChild(input); - div.appendChild(label); - - let desc = dict[i.name].desc; - if (desc) { - let tip = document.createElement("small"); - tip.innerHTML = desc; - tip.classList.add("form-entry"); - tip.setAttribute("for", id); - div.appendChild(tip); - } - - form?.appendChild(div); - } - input.value = dict[i.name].value; - } - - let btn = document.getElementById("load")!; - btn.classList.remove("hide"); - if (LastSaved()) { - btn.innerHTML = "Load Last Saved"; - btn.addEventListener('click', () => { - LoadLastSaved(); - }); - } else { - btn.addEventListener('click', () => { - LoadDefault(cust); - }); - } - - btn = document.getElementById("save")!; - btn.classList.remove("hide"); - btn.addEventListener('click', () => { - try { - SaveCust(cust, buffer); - } catch (e) { - console.log(e); - alert(e); - } - }); -} - -function ParseCust(magicOffset, buffer) { - let view = new DataView(buffer); - let cust = InitCustTable(); - let offset = magicOffset + 4; - let rev = view.getUint16(offset, true); - if (rev != CUST_REV) { - throw new Error("Unsupported custRev, expected: " + CUST_REV + ", got " + rev); - } - offset += 2; - for (let i of cust) { - i.offset = offset; - switch (i.size) { - case 2: - i.value = view.getUint16(offset, true); - break; - case 4: - i.value = view.getUint32(offset, true); - break; - default: - document.getElementById(i.name)!.focus(); - throw new Error("Unknown size at " + i); - } - offset += i.size; - } - ValidateCust(cust); - UpdateHTMLForm(cust); -} - -const fileInput = document.getElementById("file") as HTMLInputElement; -fileInput.addEventListener('change', (event) => { - let reader = new FileReader(); - reader.readAsArrayBuffer((event.target as HTMLInputElement).files![0]); - reader.onloadend = (progEvent) => { - if (progEvent.target!.readyState === FileReader.DONE) { - buffer = progEvent.target!.result!; - try { - let offset = FindMagicOffset(buffer); - ParseCust(offset, buffer); - } catch (e) { - console.log(e); - alert(e); - fileInput.value = ""; - } - } - } -}) \ No newline at end of file diff --git a/Source/loaderConfigurator/src/style.css b/Source/loaderConfigurator/src/style.css deleted file mode 100644 index 03978d39..00000000 --- a/Source/loaderConfigurator/src/style.css +++ /dev/null @@ -1,39 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - -label.form-entry { - @apply inline-block mb-2 text-slate-700 dark:text-slate-200; -} - -input.form-entry { - @apply block w-full px-3 py-1.5 text-base font-normal text-slate-600 dark:text-slate-300 bg-slate-100 dark:bg-slate-900 bg-clip-padding border border-solid border-slate-300 dark:border-gray-600 rounded transition ease-in-out m-0 focus:text-slate-700 dark:focus:text-slate-200 focus:bg-slate-100 dark:focus:bg-slate-800 focus:border-blue-600 dark:focus:border-blue-300 focus:outline-none; -} - -small.form-entry { - @apply block mt-1 text-xs text-slate-600 dark:text-slate-400 px-3 py-1.5; -} - -a.link-primary { - @apply text-blue-500 hover:text-blue-600 active:text-blue-700 transition duration-300 ease-in-out mb-4; -} - -div.btn { - @apply grid grid-cols-2 gap-x-6 mt-3; -} - -button.btn { - @apply inline-block px-6 py-2.5 text-slate-100 font-medium text-xs leading-tight uppercase rounded shadow-md hover:shadow-lg focus:shadow-lg focus:outline-none focus:ring-0 active:shadow-lg transition duration-150 ease-in-out; -} - -button.btn-primary { - @apply bg-blue-600 hover:bg-blue-700 focus:bg-blue-700 active:bg-blue-900; -} - -button.btn-secondary { - @apply bg-slate-600 hover:bg-slate-700 focus:bg-slate-700 active:bg-slate-900; -} - -.hide { - display: none !important; -} diff --git a/Source/loaderConfigurator/tailwind.config.js b/Source/loaderConfigurator/tailwind.config.js deleted file mode 100644 index 96b69fd8..00000000 --- a/Source/loaderConfigurator/tailwind.config.js +++ /dev/null @@ -1,10 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -module.exports = { - content: ['./dist/*.html', './dist/*.js', './node_modules/tw-elements/dist/js/**/*.js'], - theme: { - extend: {}, - }, - plugins: [ - require('tw-elements/dist/plugin') - ], -} diff --git a/Source/sys-clk-OC/common/include/sysclk/client/ipc.h b/Source/sys-clk-OC/common/include/sysclk/client/ipc.h index 5056a078..0f6c37f9 100644 --- a/Source/sys-clk-OC/common/include/sysclk/client/ipc.h +++ b/Source/sys-clk-OC/common/include/sysclk/client/ipc.h @@ -10,6 +10,10 @@ #pragma once +#ifdef __cplusplus +extern "C" { +#endif + #include "types.h" #include "../config.h" #include "../clocks.h" @@ -40,3 +44,7 @@ static inline Result sysclkIpcRemoveOverride(SysClkModule module) { return sysclkIpcSetOverride(module, 0); } + +#ifdef __cplusplus +} +#endif diff --git a/Source/sys-clk-OC/manager/src/ipc/client.h b/Source/sys-clk-OC/manager/src/ipc/client.h index 1b90c52a..51b62722 100644 --- a/Source/sys-clk-OC/manager/src/ipc/client.h +++ b/Source/sys-clk-OC/manager/src/ipc/client.h @@ -22,13 +22,5 @@ #include "ipc.h" -#if defined(__SWITCH__) && defined(__cplusplus) -extern "C" { -#endif - #include #include - -#if defined(__SWITCH__) && defined(__cplusplus) -} -#endif diff --git a/Source/sys-clk-OC/overlay/src/ipc.h b/Source/sys-clk-OC/overlay/src/ipc.h index 56c9bcdf..3ce147a9 100644 --- a/Source/sys-clk-OC/overlay/src/ipc.h +++ b/Source/sys-clk-OC/overlay/src/ipc.h @@ -10,14 +10,5 @@ #pragma once -#if defined(__cplusplus) -extern "C" -{ -#endif - #include #include - -#if defined(__cplusplus) -} -#endif diff --git a/Source/loaderConfigurator/build.sh b/pages/build.sh similarity index 70% rename from Source/loaderConfigurator/build.sh rename to pages/build.sh index 70d687f3..a3fbff59 100755 --- a/Source/loaderConfigurator/build.sh +++ b/pages/build.sh @@ -2,5 +2,5 @@ [ -d "./dist" ] || mkdir ./dist cp -Rf ./src/*.html ./dist/ +# README_HTML=`pandoc -f gfm -t html5 ../README.md` tsc ./src/main.ts --outDir ./dist/ -lib es2015,dom -t es2015 -npx tailwindcss -i ./src/style.css -o ./dist/output.css diff --git a/pages/dist/index.html b/pages/dist/index.html new file mode 100644 index 00000000..aedc5605 --- /dev/null +++ b/pages/dist/index.html @@ -0,0 +1,382 @@ + + + + + + + Switch OC Suite + + + + + +
    + +
    +
    +

    Switch OC Suite

    +

    Overclocking suite for Horizon OS (HOS) running on Atmosphere CFW. Licensed under GPL v2.

    +
    +
    +
    +
    +
    +
    +
    +
    +

    Read Me

    +

    DISCLAIMER: 🚨USE AT YOUR OWN RISK!🚨

    +
    +
  • + Overclocking in general will shorten the lifespan of some + hardware components. YOU ARE RESPONSIBLE for any problem or + potential damage if unsafe frequencies are ENABLED in + sys-clk-OC. Issues like asking for bypassing limit will BE IGNORED OR + CLOSED WITHOUT REPLY. +
  • +
  • + Due to HorizonOS design, instabilities from unsafe RAM clocks may + cause filesystem corruption. Always make backup before enabling + DRAM OC. +
  • +
    + + +

    Features

    +
    + For Erista variant (HAC-001) +
      +
    • CPU Overclock (Safe: 1785 MHz) +
        +
      • Unsafe +
          +
        • Due to the limit of board power draw or power IC
        • +
        • Unlockable frequencies up to 2091 MHz
        • +
        • See README + for sys-clk-OC
        • +
        +
      • +
      +
    • +
    • DRAM Overclock (Safe: 1862.4 MHz) +
        +
      • Unsafe: Up to 2131 MHz with DRAM bus overvolting depending on your + DRAM chip
      • +
      +
    • +
    • Modded sys-clk and ReverseNX-RT +
        +
      • CPU & GPU frequency governor (Experimental)
      • +
      • Set charging current (100 mA - 2000 mA) and charging limit (20% - + 100%)
      • +
      • Global Profile
      • +
      • Sync ReverseNX Mode
      • +
      +
    • +
    +
    +
    + For Mariko variant (HAC-001-01, HDH-001, HEG-001) +
      +
    • CPU / GPU Overclock (Safe: 1963 / 998 MHz) +
        +
      • Unsafe +
          +
        • Due to the limit of board power draw or power IC
        • +
        • Unlockable frequencies up to 2397 / 1305 MHz or 2295 / 1267 MHz
        • +
        • See README + for sys-clk-OC
        • +
        +
      • +
      +
    • +
    • DRAM Overclock (Safe: 1996.8 MHz)
    • +
    • Modded sys-clk and ReverseNX-RT +
        +
      • Auto CPU Boost
      • +
      • CPU & GPU frequency governor (Experimental)
      • +
      • Set charging current (100 mA - 2000 mA) and charging limit (20% - + 100%)
      • +
      • Global Profile
      • +
      • Sync ReverseNX Mode
      • +
      +
    • +
    +
    +
    + Auto CPU Boost +
      +
    • For faster game loading
    • +
    • Enable CPU Boost (1785 MHz) when CPU Core#3 (System Core) is + stressed (mainly I/O operations).
    • +
    • Effective only when charger is connected.
    • +
    +
    +
    + CPU & GPU frequency governor (Experimental) +
      +
    • Adjust frequency based on load. Might decrease power draw but can + introduce stutters. Can be turned off for specific titles.
    • +
    +
    +
    + Setting charge limit (20% - 100%) +
      +
    • Long-term use of charge limit may render the battery gauge + inaccurate. Performing full cycles could help recalibration, or try battery_desync_fix_nx.
    • +
    +
    +
    + Global profile +
      +
    • Designated a dummy title id 0xA111111111111111.
    • +
    • Priority: "Temp overrides" > "Application profile" > "Global + profile" > "System default".
    • +
    +
    +
    + Sync ReverseNX Mode +
      +
    • No need to change clocks manually after toggling modes in ReverseNX + (-RT and -Tool)
    • +
    +
    +
    + System Settings (Optional) + See system_settings.md +
    +

    Installation

    +
      +
    1. Download latest release.
    2. +
    3. Copy all files in SdOut to the root of SD card.
    4. +
    5. Grab x.x.x_loader.kip for your Atmosphere version, rename it to loader.kip and + place it in /atmosphere/kips/.
    6. +
    7. Customization via online loader configurator +
      + Default config + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      DefaultsMarikoErista
      CPU OC2397 MHz Max2091 MHz Max
      CPU Boost1785 MHzN/A
      CPU Volt1235 mV Max1257 mV Max
      GPU OC1305 MHz MaxN/A
      RAM OC1996 MHz Max1862 MHz Max
      RAM VoltDisabledDisabled
      RAM TimingAuto-AdjustedDisabled
      +
      +
    8. +
    9. Hekate-ipl bootloader Only +
        +
      • Add kip1=atmosphere/kips/loader.kip to boot entry + section in bootloader/hekate_ipl.ini.
      • +
      +
    10. +
    +
    + Deprecated: patching sysmodules manually +
      +
    • This method is only served as reference as it could damage your MMC file system if not handled + properly.
    • +
    • Patched sysmodules would be persistent until pcv or ptm was updated in new HOS (normally in + x.0.0). +
    • +
    • Tools: + +
    • +
    +
      +
    1. Dump prod.keys with Lockpick_RCM
    2. +
    3. Dump HOS firmware with TegraExplorer
    4. +
    5. Configure and run test_patch.sh to generate patched pcv & ptm sysmodules in nca
    6. +
    7. Replace nca in SYSTEM:/Contents/registered/ with TegraExplorer
    8. +
    9. ValidateAcidSignature() should be stubbed to allow unsigned sysmodules to load (a.k.a. + loader_patch) +
    10. +
    +
    +
    + How to build this project +
      +
    1. Grab necessary patches from the repo, then compile sys-clk, ReverseNX-RT and Atmosphere loader with + devkitpro.
    2. +
    3. Before compiling Atmosphere loader, run patch.py in + Atmosphere/stratosphere/loader/source/ to insert oc module into loader sysmodule. +
    4. +
    5. When compilation is done, uncompress the kip to make it work with configurator: + hactool -t kip1 Atmosphere/stratosphere/loader/out/nintendo_nx_arm64_armv8a/release/loader.kip --uncompress=./loader.kip +
    6. +
    +
    +

    Frequently Asked Questions

    +
    + How to enable unsafe frequencies in sys-clk-OC? +
  • Above all else, you should know what "unsafe" means and issues might arise.
  • +
  • See the end of README in sys-clk-OC. Add this line allow_unsafe_freq=1 into + /config/sys-clk/config.ini +
  • +
    +
    + I would like to bypass limit enforced in sys-clk to improve handheld performance without charger + connected. +
  • Never will it be implemented here, or work out of the box.
  • +
  • You have to modify the code yourself for your own use. If you are to share modified binaries you have + made based on this project publicly, make sure to comply with GPL v2 licenses.
  • +
    + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +

    Configurator

    +

    Configure frequencies and voltages to suit your hardware and yourself.

    +
    +
    +
    + +
    +
    +
    + + + +
    +
    +
    +
    +
    +
    + Build with Pico +
    + + + + \ No newline at end of file diff --git a/pages/dist/main.js b/pages/dist/main.js new file mode 100644 index 00000000..ee21df64 --- /dev/null +++ b/pages/dist/main.js @@ -0,0 +1,414 @@ +var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +}; +/* Config: Cust */ +var CUST_REV; +var buffer; +class CustEntry { + constructor(name, size, desc, defval, minmax = [0, 1000000], step = 1, extra_validator) { + this.name = name; + this.size = size; + this.desc = desc; + this.defval = defval; + this.min = minmax[0]; + this.max = minmax[1]; + this.step = step; + this.validator = extra_validator; + this.value = null; + this.offset = null; + } + ; +} +var CustTable = null; +const CustTableV2 = [ + new CustEntry("mtcConf", 2, "DRAM Timing\ +
  • 0: AUTO_ADJ_MARIKO_SAFE: Auto adjust timings for LPDDR4 ≤3733 Mbps specs, 8Gb density. (Default)
  • \ +
  • 1: AUTO_ADJ_MARIKO_4266: Auto adjust timings for LPDDR4X 4266 Mbps specs, 8Gb density.
  • ", 0, [0, 3]), + new CustEntry("marikoCpuMaxClock", 4, "Mariko CPU Max Clock in kHz\ +
  • System default: 1785000
  • \ +
  • 2397000 might be unreachable for some SoCs.
  • ", 2397000, [1785000, 3000000], 100, (x) => (x % 100) == 0), + new CustEntry("marikoCpuBoostClock", 4, "Mariko CPU Boost Clock in kHz\ +
  • System default: 1785000
  • \ +
  • Boost clock will be applied when applications request higher CPU frequency for quicker loading.
  • \ +
  • This will be set regardless of whether sys-clk is enabled.
  • ", 1785000, [1785000, 3000000], 100, (x) => (x % 100) == 0), + new CustEntry("marikoCpuMaxVolt", 4, "Mariko CPU Max Voltage in mV\ +
  • System default: 1120
  • \ +
  • Acceptable range: 1100 ≤ x ≤ 1300
  • ", 1235, [1100, 1300]), + new CustEntry("marikoGpuMaxClock", 4, "Mariko GPU Max Clock in kHz\ +
  • System default: 921600
  • \ +
  • Tegra X1+ official maximum: 1267200
  • \ +
  • 1305600 might be unreachable for some SoCs.
  • ", 1305600, [768000, 1536000], 100, (x) => (x % 100) == 0), + new CustEntry("marikoEmcMaxClock", 4, "Mariko RAM Max Clock in kHz\ +
  • Values should be > 1600000, and divided evenly by 3200.
  • \ +
  • WARNING: RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM
  • ", 1996800, [1612800, 2400000], 3200, (x) => (x % 3200) == 0), + new CustEntry("eristaOCEnable", 4, "Erista CPU Enable Overclock\ +
  • Not tested
  • ", 1, [0, 1]), + new CustEntry("eristaCpuMaxVolt", 4, "Erista CPU Max Voltage in mV\ +
  • Acceptable range: 1100 ≤ x ≤ 1300
  • ", 1235, [0, 1300], 1, (x) => x >= 1100), + new CustEntry("eristaEmcMaxClock", 4, "Erista RAM Max Clock in kHz\ +
  • Values should be > 1600000, and divided evenly by 3200.
  • \ +
  • WARNING: RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM
  • ", 1862400, [1600000, 2400000], 3200, (x) => (x % 3200) == 0), + new CustEntry("eristaEmcVolt", 4, "Erista RAM Voltage in uV\ +
  • Acceptable range: 1100000 ≤ x ≤ 1250000, and it should be divided evenly by 12500.
  • \ +
  • Not enabled by default
  • ", 0, [0, 1250000], 12500, (x) => (x % 12500) == 0 && x >= 1100000), +]; +const CustTableV3 = [ + new CustEntry("mtcConf", 2, "DRAM Timing\ +
  • 0: AUTO_ADJ_MARIKO_SAFE: Auto adjust timings for LPDDR4 ≤3733 Mbps specs, 8Gb density. (Default)
  • \ +
  • 1: AUTO_ADJ_MARIKO_4266: Auto adjust timings for LPDDR4X 4266 Mbps specs, 8Gb density.
  • \ +
  • 2: NO_ADJ_ALL: No timing adjustment for both Erista and Mariko. Might achieve better performance on Mariko but lower maximum frequency is expected.", 0, [0, 3]), + new CustEntry("marikoCpuMaxClock", 4, "Mariko CPU Max Clock in kHz\ +
  • System default: 1785000
  • \ +
  • 2397000 might be unreachable for some SoCs.
  • ", 2397000, [1785000, 3000000], 100, (x) => (x % 100) == 0), + new CustEntry("marikoCpuBoostClock", 4, "Mariko CPU Boost Clock in kHz\ +
  • System default: 1785000
  • \ +
  • Boost clock will be applied when applications request higher CPU frequency for quicker loading.
  • \ +
  • This will be set regardless of whether sys-clk is enabled.
  • ", 1785000, [1785000, 3000000], 100, (x) => (x % 100) == 0), + new CustEntry("marikoCpuMaxVolt", 4, "Mariko CPU Max Voltage in mV\ +
  • System default: 1120
  • \ +
  • Acceptable range: 1100 ≤ x ≤ 1300
  • ", 1235, [1100, 1300]), + new CustEntry("marikoGpuMaxClock", 4, "Mariko GPU Max Clock in kHz\ +
  • System default: 921600
  • \ +
  • Tegra X1+ official maximum: 1267200
  • \ +
  • 1305600 might be unreachable for some SoCs.
  • ", 1305600, [768000, 1536000], 100, (x) => (x % 100) == 0), + new CustEntry("marikoEmcMaxClock", 4, "Mariko RAM Max Clock in kHz\ +
  • Values should be > 1600000, and divided evenly by 3200.
  • \ +
  • WARNING: RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM
  • ", 1996800, [1612800, 2400000], 3200, (x) => (x % 3200) == 0), + new CustEntry("marikoEmcVolt", 4, "Mariko RAM Voltage in uV\ +
  • Acceptable range: 600000 ≤ x ≤ 650000
  • \ +
  • Value should be divided evenly by 5'000
  • \ +
  • Not enabled by default.
  • \ +
  • This will not work without sys-clk-OC.
  • ", 1, [0, 1]), + new CustEntry("eristaCpuMaxVolt", 4, "Erista CPU Max Voltage in mV\ +
  • Acceptable range: 1100 ≤ x ≤ 1300
  • ", 1235, [0, 1300], 1, (x) => x >= 1100), + new CustEntry("eristaEmcMaxClock", 4, "Erista RAM Max Clock in kHz\ +
  • Values should be > 1600000, and divided evenly by 3200.
  • \ +
  • WARNING: RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM
  • ", 1862400, [1600000, 2400000], 3200, (x) => (x % 3200) == 0), + new CustEntry("eristaEmcVolt", 4, "Erista RAM Voltage in uV\ +
  • Acceptable range: 1100000 ≤ x ≤ 1250000, and it should be divided evenly by 12500.
  • \ +
  • Not enabled by default
  • ", 0, [0, 1250000], 12500, (x) => (x % 12500) == 0 && x >= 1100000), +]; +function FindMagicOffset(buffer) { + let view = new DataView(buffer); + for (let i = 0; i < view.byteLength; i += 4) { + if (view.getUint32(i, true) == 0x54535543) { // "CUST" + return i; + } + } + throw new Error("Invalid loader.kip file"); +} +class ErrorToolTip { + constructor(id) { + this.id = id; + this.element = document.getElementById(id); + } + ; + setMsg(msg) { + this.message = msg; + } + show() { + if (this.element) { + this.element.setAttribute("aria-invalid", "true"); + if (this.message) { + this.element.setAttribute("title", this.message); + this.element.parentElement.setAttribute("data-tooltip", this.message); + this.element.parentElement.setAttribute("data-placement", "top"); + } + } + } + ; + clear() { + if (this.element) { + this.element.removeAttribute("aria-invalid"); + this.element.removeAttribute("title"); + this.element.parentElement.removeAttribute("data-tooltip"); + this.element.parentElement.removeAttribute("data-placement"); + } + } + addChangeListener() { + if (this.element) { + this.element.addEventListener('change', (_evt) => { + let obj = CustTable.filter((obj) => { return obj.name === this.id; })[0]; + obj.value = Number(this.element.value); + ValidateCustEntry(obj); + }); + } + } +} +; +function ValidateCustEntry(entry) { + let elementId = entry.name; + let tip = new ErrorToolTip(elementId); + tip.clear(); + if (entry.value == 0) + return null; + if (entry.value < entry.min || entry.value > entry.max) { + tip.setMsg(`Expected range: [${entry.min}, ${entry.max}], got ${entry.value}.`); + tip.show(); + return tip; + } + if (entry.validator && !entry.validator(entry.value)) { + tip.setMsg(`Invalid value: ${entry.value}. Did not pass this validator: ${entry.validator}.`); + tip.show(); + return tip; + } + return null; +} +function ValidateCust() { + let tooltips = []; + for (let i of CustTable) { + let tip = ValidateCustEntry(i); + if (tip) { + tooltips.push(tip); + } + } + if (tooltips.length > 0) { + throw new Error("Invalid cust"); + } +} +function SaveCust(buffer) { + let view = new DataView(buffer); + let storage = {}; + storage["custRev"] = CUST_REV; + for (let i of CustTable) { + let id = i.name; + i.value = Number(document.getElementById(id).value); + storage[i.name] = i.value; + switch (i.size) { + case 2: + view.setUint16(i.offset, i.value, true); + break; + case 4: + view.setUint32(i.offset, i.value, true); + break; + default: + document.getElementById(i.name).focus(); + throw new Error("Unknown size at " + i); + } + } + ValidateCust(); + localStorage.setItem("last_saved", JSON.stringify(storage)); + let a = document.createElement("a"); + a.href = window.URL.createObjectURL(new Blob([buffer], { type: "application/octet-stream" })); + a.download = "loader.kip"; + a.click(); +} +function LastSaved() { + let storage = localStorage.getItem("last_saved"); + if (!storage) { + return false; + } + let sObj = JSON.parse(storage); + if (!sObj["custRev"] || sObj["custRev"] != CUST_REV) { + localStorage.removeItem("last_saved"); + return false; + } + return true; +} +function LoadLastSaved() { + if (LastSaved()) { + let storage = localStorage.getItem("last_saved"); + let sObj = JSON.parse(storage); + for (let key in sObj) { + if (key == "custRev") { + continue; + } + document.getElementById(key).value = sObj[key]; + } + } +} +function LoadDefault() { + for (let i of CustTable) { + let id = i.name; + document.getElementById(id).value = String(i.defval); + } +} +function ClearHTMLForm() { + var _a, _b; + if (!CustTable) { + return; + } + for (let i of CustTable) { + let id = i.name; + let input = document.getElementById(id); + (_b = (_a = input.parentElement) === null || _a === void 0 ? void 0 : _a.parentElement) === null || _b === void 0 ? void 0 : _b.remove(); + } +} +function UpdateHTMLForm() { + let dict = Object.assign({}, ...CustTable.map((x) => ({ [x.name]: x }))); + let form = document.getElementById("form"); + for (let i of CustTable) { + let id = i.name; + let input = document.getElementById(id); + if (!input) { + let grid = document.createElement("div"); + grid.classList.add("grid"); + // Label and input box + input = document.createElement("input"); + input.min = dict[i.name].min; + input.max = dict[i.name].max; + input.id = id; + input.type = "number"; + input.step = dict[i.name].step; + let label = document.createElement("label"); + label.setAttribute("for", id); + label.innerHTML = id; + label.appendChild(input); + grid.appendChild(label); + // Description in blockquote style + let desc = dict[i.name].desc; + let block = document.createElement("blockquote"); + block.style["margin-top"] = "0"; + block.innerHTML = desc; + block.setAttribute("for", id); + grid.appendChild(block); + grid.style["margin-top"] = "3rem"; + form.appendChild(grid); + let tooltip = new ErrorToolTip(id); + tooltip.addChangeListener(); + } + input.value = dict[i.name].value; + } + let default_btn = document.getElementById("load_default"); + default_btn.removeAttribute("disabled"); + default_btn.addEventListener('click', () => { + LoadDefault(); + }); + if (LastSaved()) { + let last_btn = document.getElementById("load_saved"); + last_btn.removeAttribute("disabled"); + last_btn.addEventListener('click', () => { + LoadLastSaved(); + }); + } + let save_btn = document.getElementById("save"); + save_btn.removeAttribute("disabled"); + save_btn.addEventListener('click', () => { + try { + SaveCust(buffer); + } + catch (e) { + console.log(e); + alert(e); + } + }); +} +function ParseCust(magicOffset, buffer) { + let view = new DataView(buffer); + let offset = magicOffset + 4; + let rev = view.getUint16(offset, true); + if (rev != 2 && rev != 3) { + throw new Error("Unsupported custRev, expected: 2 or 3, got " + rev); + } + CUST_REV = rev; + document.getElementById("cust_rev").innerHTML = `Cust V${CUST_REV} is loaded.`; + if (rev == 2) { + CustTable = CustTableV2; + } + else { + CustTable = CustTableV3; + } + offset += 2; + for (let i of CustTable) { + i.offset = offset; + switch (i.size) { + case 2: + i.value = view.getUint16(offset, true); + break; + case 4: + i.value = view.getUint32(offset, true); + break; + default: + document.getElementById(i.name).focus(); + throw new Error("Unknown size at " + i); + } + offset += i.size; + } +} +const fileInput = document.getElementById("file"); +fileInput.addEventListener('change', (event) => { + let reader = new FileReader(); + reader.readAsArrayBuffer(event.target.files[0]); + reader.onloadend = (progEvent) => { + if (progEvent.target.readyState === FileReader.DONE) { + buffer = progEvent.target.result; + try { + let offset = FindMagicOffset(buffer); + ClearHTMLForm(); + ParseCust(offset, buffer); + ValidateCust(); + UpdateHTMLForm(); + } + catch (e) { + console.log(e); + alert(e); + fileInput.value = ""; + } + } + }; +}); +function fetchRelease() { + return __awaiter(this, void 0, void 0, function* () { + try { + const responseFromSuite = yield fetch('https://api.github.com/repos/KazushiMe/Switch-OC-Suite/releases/latest', { + method: 'GET', + headers: { + Accept: 'application/json', + }, + }); + if (!responseFromSuite.ok) { + throw new Error(`Failed to fetch latest release info from GitHub: ${responseFromSuite.status}`); + } + const resultFromSuite = yield responseFromSuite.json(); + const latestVerFromSuite = resultFromSuite.tag_name; + const correspondingVerFromAMS = latestVerFromSuite.split(".").slice(0, 3).join("."); + const loaderKip = resultFromSuite.assets.filter((obj) => { + return obj.name.endsWith("loader.kip"); + })[0]; + const sdOut = resultFromSuite.assets.filter((obj) => { + return obj.name.endsWith(".zip"); + })[0]; + const amsReleaseUrl = `https://github.com/Atmosphere-NX/Atmosphere/releases/tags/${correspondingVerFromAMS}`; + let info = { + OCSuiteVer: latestVerFromSuite, + LoaderKipUrl: loaderKip.browser_download_url, + SdOutZipUrl: sdOut.browser_download_url, + AMSVer: correspondingVerFromAMS, + AMSUrl: amsReleaseUrl + }; + return info; + } + catch (e) { + console.log(e); + alert(e); + } + }); +} +function updateDownloadUrls() { + return __awaiter(this, void 0, void 0, function* () { + const updateHref = (id, name, url) => { + let element = document.getElementById(id); + element.innerHTML = name; + element.removeAttribute("aria-busy"); + element.setAttribute("href", url); + }; + let info = yield fetchRelease(); + if (info) { + const loaderKipName = `loader.kip ${info.OCSuiteVer}`; + updateHref("loader_kip_btn", loaderKipName, info.LoaderKipUrl); + const sdOutName = `SdOut.zip ${info.OCSuiteVer}`; + updateHref("sdout_zip_btn", sdOutName, info.SdOutZipUrl); + const amsName = `Atmosphere-NX ${info.AMSVer}`; + updateHref("ams_btn", amsName, info.AMSUrl); + } + }); +} +addEventListener('DOMContentLoaded', (_evt) => __awaiter(this, void 0, void 0, function* () { + yield updateDownloadUrls(); +})); diff --git a/pages/src/index.html b/pages/src/index.html new file mode 100644 index 00000000..aedc5605 --- /dev/null +++ b/pages/src/index.html @@ -0,0 +1,382 @@ + + + + + + + Switch OC Suite + + + + + +
    + +
    +
    +

    Switch OC Suite

    +

    Overclocking suite for Horizon OS (HOS) running on Atmosphere CFW. Licensed under GPL v2.

    +
    +
    +
    +
    +
    +
    +
    +
    +

    Read Me

    +

    DISCLAIMER: 🚨USE AT YOUR OWN RISK!🚨

    +
    +
  • + Overclocking in general will shorten the lifespan of some + hardware components. YOU ARE RESPONSIBLE for any problem or + potential damage if unsafe frequencies are ENABLED in + sys-clk-OC. Issues like asking for bypassing limit will BE IGNORED OR + CLOSED WITHOUT REPLY. +
  • +
  • + Due to HorizonOS design, instabilities from unsafe RAM clocks may + cause filesystem corruption. Always make backup before enabling + DRAM OC. +
  • +
    + + +

    Features

    +
    + For Erista variant (HAC-001) +
      +
    • CPU Overclock (Safe: 1785 MHz) +
        +
      • Unsafe +
          +
        • Due to the limit of board power draw or power IC
        • +
        • Unlockable frequencies up to 2091 MHz
        • +
        • See README + for sys-clk-OC
        • +
        +
      • +
      +
    • +
    • DRAM Overclock (Safe: 1862.4 MHz) +
        +
      • Unsafe: Up to 2131 MHz with DRAM bus overvolting depending on your + DRAM chip
      • +
      +
    • +
    • Modded sys-clk and ReverseNX-RT +
        +
      • CPU & GPU frequency governor (Experimental)
      • +
      • Set charging current (100 mA - 2000 mA) and charging limit (20% - + 100%)
      • +
      • Global Profile
      • +
      • Sync ReverseNX Mode
      • +
      +
    • +
    +
    +
    + For Mariko variant (HAC-001-01, HDH-001, HEG-001) +
      +
    • CPU / GPU Overclock (Safe: 1963 / 998 MHz) +
        +
      • Unsafe +
          +
        • Due to the limit of board power draw or power IC
        • +
        • Unlockable frequencies up to 2397 / 1305 MHz or 2295 / 1267 MHz
        • +
        • See README + for sys-clk-OC
        • +
        +
      • +
      +
    • +
    • DRAM Overclock (Safe: 1996.8 MHz)
    • +
    • Modded sys-clk and ReverseNX-RT +
        +
      • Auto CPU Boost
      • +
      • CPU & GPU frequency governor (Experimental)
      • +
      • Set charging current (100 mA - 2000 mA) and charging limit (20% - + 100%)
      • +
      • Global Profile
      • +
      • Sync ReverseNX Mode
      • +
      +
    • +
    +
    +
    + Auto CPU Boost +
      +
    • For faster game loading
    • +
    • Enable CPU Boost (1785 MHz) when CPU Core#3 (System Core) is + stressed (mainly I/O operations).
    • +
    • Effective only when charger is connected.
    • +
    +
    +
    + CPU & GPU frequency governor (Experimental) +
      +
    • Adjust frequency based on load. Might decrease power draw but can + introduce stutters. Can be turned off for specific titles.
    • +
    +
    +
    + Setting charge limit (20% - 100%) +
      +
    • Long-term use of charge limit may render the battery gauge + inaccurate. Performing full cycles could help recalibration, or try battery_desync_fix_nx.
    • +
    +
    +
    + Global profile +
      +
    • Designated a dummy title id 0xA111111111111111.
    • +
    • Priority: "Temp overrides" > "Application profile" > "Global + profile" > "System default".
    • +
    +
    +
    + Sync ReverseNX Mode +
      +
    • No need to change clocks manually after toggling modes in ReverseNX + (-RT and -Tool)
    • +
    +
    +
    + System Settings (Optional) + See system_settings.md +
    +

    Installation

    +
      +
    1. Download latest release.
    2. +
    3. Copy all files in SdOut to the root of SD card.
    4. +
    5. Grab x.x.x_loader.kip for your Atmosphere version, rename it to loader.kip and + place it in /atmosphere/kips/.
    6. +
    7. Customization via online loader configurator +
      + Default config + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      DefaultsMarikoErista
      CPU OC2397 MHz Max2091 MHz Max
      CPU Boost1785 MHzN/A
      CPU Volt1235 mV Max1257 mV Max
      GPU OC1305 MHz MaxN/A
      RAM OC1996 MHz Max1862 MHz Max
      RAM VoltDisabledDisabled
      RAM TimingAuto-AdjustedDisabled
      +
      +
    8. +
    9. Hekate-ipl bootloader Only +
        +
      • Add kip1=atmosphere/kips/loader.kip to boot entry + section in bootloader/hekate_ipl.ini.
      • +
      +
    10. +
    +
    + Deprecated: patching sysmodules manually +
      +
    • This method is only served as reference as it could damage your MMC file system if not handled + properly.
    • +
    • Patched sysmodules would be persistent until pcv or ptm was updated in new HOS (normally in + x.0.0). +
    • +
    • Tools: + +
    • +
    +
      +
    1. Dump prod.keys with Lockpick_RCM
    2. +
    3. Dump HOS firmware with TegraExplorer
    4. +
    5. Configure and run test_patch.sh to generate patched pcv & ptm sysmodules in nca
    6. +
    7. Replace nca in SYSTEM:/Contents/registered/ with TegraExplorer
    8. +
    9. ValidateAcidSignature() should be stubbed to allow unsigned sysmodules to load (a.k.a. + loader_patch) +
    10. +
    +
    +
    + How to build this project +
      +
    1. Grab necessary patches from the repo, then compile sys-clk, ReverseNX-RT and Atmosphere loader with + devkitpro.
    2. +
    3. Before compiling Atmosphere loader, run patch.py in + Atmosphere/stratosphere/loader/source/ to insert oc module into loader sysmodule. +
    4. +
    5. When compilation is done, uncompress the kip to make it work with configurator: + hactool -t kip1 Atmosphere/stratosphere/loader/out/nintendo_nx_arm64_armv8a/release/loader.kip --uncompress=./loader.kip +
    6. +
    +
    +

    Frequently Asked Questions

    +
    + How to enable unsafe frequencies in sys-clk-OC? +
  • Above all else, you should know what "unsafe" means and issues might arise.
  • +
  • See the end of README in sys-clk-OC. Add this line allow_unsafe_freq=1 into + /config/sys-clk/config.ini +
  • +
    +
    + I would like to bypass limit enforced in sys-clk to improve handheld performance without charger + connected. +
  • Never will it be implemented here, or work out of the box.
  • +
  • You have to modify the code yourself for your own use. If you are to share modified binaries you have + made based on this project publicly, make sure to comply with GPL v2 licenses.
  • +
    + + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    +
    +
    +

    Configurator

    +

    Configure frequencies and voltages to suit your hardware and yourself.

    +
    +
    +
    + +
    +
    +
    + + + +
    +
    +
    +
    +
    +
    + Build with Pico +
    + + + + \ No newline at end of file diff --git a/pages/src/main.ts b/pages/src/main.ts new file mode 100644 index 00000000..4af12410 --- /dev/null +++ b/pages/src/main.ts @@ -0,0 +1,605 @@ +/* Config: Cust */ +var CUST_REV: number | null; +var buffer: string | ArrayBuffer; + +class CustEntry { + name: string; + size: number; + desc: string; + defval: number; + min: number; + max: number; + step: number; + validator: Function | null; + value: number | null; + offset: number | null; + + constructor(name: string, size: number, desc: string, defval: number, minmax = [0, 1_000_000], step = 1, extra_validator?) { + this.name = name; + this.size = size; + this.desc = desc; + this.defval = defval; + this.min = minmax[0]; + this.max = minmax[1]; + this.step = step; + this.validator = extra_validator; + this.value = null; + this.offset = null; + }; +} + +var CustTable: Array | null = null; + +const CustTableV2: Array = [ + new CustEntry( + "mtcConf", + 2, + "DRAM Timing\ +
  • 0: AUTO_ADJ_MARIKO_SAFE: Auto adjust timings for LPDDR4 ≤3733 Mbps specs, 8Gb density. (Default)
  • \ +
  • 1: AUTO_ADJ_MARIKO_4266: Auto adjust timings for LPDDR4X 4266 Mbps specs, 8Gb density.
  • ", + 0, + [0, 3], + ), + new CustEntry( + "marikoCpuMaxClock", + 4, + "Mariko CPU Max Clock in kHz\ +
  • System default: 1785000
  • \ +
  • 2397000 might be unreachable for some SoCs.
  • ", + 2397_000, + [1785_000, 3000_000], + 100, + (x: number) => (x % 100) == 0 + ), + new CustEntry( + "marikoCpuBoostClock", + 4, + "Mariko CPU Boost Clock in kHz\ +
  • System default: 1785000
  • \ +
  • Boost clock will be applied when applications request higher CPU frequency for quicker loading.
  • \ +
  • This will be set regardless of whether sys-clk is enabled.
  • ", + 1785_000, + [1785_000, 3000_000], + 100, + (x: number) => (x % 100) == 0 + ), + new CustEntry( + "marikoCpuMaxVolt", + 4, + "Mariko CPU Max Voltage in mV\ +
  • System default: 1120
  • \ +
  • Acceptable range: 1100 ≤ x ≤ 1300
  • ", + 1235, + [1100, 1300], + ), + new CustEntry( + "marikoGpuMaxClock", + 4, + "Mariko GPU Max Clock in kHz\ +
  • System default: 921600
  • \ +
  • Tegra X1+ official maximum: 1267200
  • \ +
  • 1305600 might be unreachable for some SoCs.
  • ", + 1305_600, + [768_000, 1536_000], + 100, + (x: number) => (x % 100) == 0 + ), + new CustEntry( + "marikoEmcMaxClock", + 4, + "Mariko RAM Max Clock in kHz\ +
  • Values should be > 1600000, and divided evenly by 3200.
  • \ +
  • WARNING: RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM
  • ", + 1996_800, + [1612_800, 2400_000], + 3200, + (x: number) => (x % 3200) == 0 + ), + new CustEntry( + "eristaOCEnable", + 4, + "Erista CPU Enable Overclock\ +
  • Not tested
  • ", + 1, + [0, 1] + ), + new CustEntry( + "eristaCpuMaxVolt", + 4, + "Erista CPU Max Voltage in mV\ +
  • Acceptable range: 1100 ≤ x ≤ 1300
  • ", + 1235, + [0, 1300], + 1, + (x: number) => x >= 1100 + ), + new CustEntry( + "eristaEmcMaxClock", + 4, + "Erista RAM Max Clock in kHz\ +
  • Values should be > 1600000, and divided evenly by 3200.
  • \ +
  • WARNING: RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM
  • ", + 1862_400, + [1600_000, 2400_000], + 3200, + (x: number) => (x % 3200) == 0 + ), + new CustEntry( + "eristaEmcVolt", + 4, + "Erista RAM Voltage in uV\ +
  • Acceptable range: 1100000 ≤ x ≤ 1250000, and it should be divided evenly by 12500.
  • \ +
  • Not enabled by default
  • ", + 0, + [0, 1250_000], + 12500, + (x: number) => (x % 12500) == 0 && x >= 1100000 + ), +]; + +const CustTableV3: Array = [ + new CustEntry( + "mtcConf", + 2, + "DRAM Timing\ +
  • 0: AUTO_ADJ_MARIKO_SAFE: Auto adjust timings for LPDDR4 ≤3733 Mbps specs, 8Gb density. (Default)
  • \ +
  • 1: AUTO_ADJ_MARIKO_4266: Auto adjust timings for LPDDR4X 4266 Mbps specs, 8Gb density.
  • \ +
  • 2: NO_ADJ_ALL: No timing adjustment for both Erista and Mariko. Might achieve better performance on Mariko but lower maximum frequency is expected.", + 0, + [0, 3], + ), + new CustEntry( + "marikoCpuMaxClock", + 4, + "Mariko CPU Max Clock in kHz\ +
  • System default: 1785000
  • \ +
  • 2397000 might be unreachable for some SoCs.
  • ", + 2397_000, + [1785_000, 3000_000], + 100, + (x: number) => (x % 100) == 0 + ), + new CustEntry( + "marikoCpuBoostClock", + 4, + "Mariko CPU Boost Clock in kHz\ +
  • System default: 1785000
  • \ +
  • Boost clock will be applied when applications request higher CPU frequency for quicker loading.
  • \ +
  • This will be set regardless of whether sys-clk is enabled.
  • ", + 1785_000, + [1785_000, 3000_000], + 100, + (x: number) => (x % 100) == 0 + ), + new CustEntry( + "marikoCpuMaxVolt", + 4, + "Mariko CPU Max Voltage in mV\ +
  • System default: 1120
  • \ +
  • Acceptable range: 1100 ≤ x ≤ 1300
  • ", + 1235, + [1100, 1300], + ), + new CustEntry( + "marikoGpuMaxClock", + 4, + "Mariko GPU Max Clock in kHz\ +
  • System default: 921600
  • \ +
  • Tegra X1+ official maximum: 1267200
  • \ +
  • 1305600 might be unreachable for some SoCs.
  • ", + 1305_600, + [768_000, 1536_000], + 100, + (x: number) => (x % 100) == 0 + ), + new CustEntry( + "marikoEmcMaxClock", + 4, + "Mariko RAM Max Clock in kHz\ +
  • Values should be > 1600000, and divided evenly by 3200.
  • \ +
  • WARNING: RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM
  • ", + 1996_800, + [1612_800, 2400_000], + 3200, + (x: number) => (x % 3200) == 0 + ), + new CustEntry( + "marikoEmcVolt", + 4, + "Mariko RAM Voltage in uV\ +
  • Acceptable range: 600000 ≤ x ≤ 650000
  • \ +
  • Value should be divided evenly by 5'000
  • \ +
  • Not enabled by default.
  • \ +
  • This will not work without sys-clk-OC.
  • ", + 1, + [0, 1] + ), + new CustEntry( + "eristaCpuMaxVolt", + 4, + "Erista CPU Max Voltage in mV\ +
  • Acceptable range: 1100 ≤ x ≤ 1300
  • ", + 1235, + [0, 1300], + 1, + (x: number) => x >= 1100 + ), + new CustEntry( + "eristaEmcMaxClock", + 4, + "Erista RAM Max Clock in kHz\ +
  • Values should be > 1600000, and divided evenly by 3200.
  • \ +
  • WARNING: RAM overclock could be UNSTABLE if timing parameters are not suitable for your DRAM
  • ", + 1862_400, + [1600_000, 2400_000], + 3200, + (x: number) => (x % 3200) == 0 + ), + new CustEntry( + "eristaEmcVolt", + 4, + "Erista RAM Voltage in uV\ +
  • Acceptable range: 1100000 ≤ x ≤ 1250000, and it should be divided evenly by 12500.
  • \ +
  • Not enabled by default
  • ", + 0, + [0, 1250_000], + 12500, + (x: number) => (x % 12500) == 0 && x >= 1100000 + ), +]; + +function FindMagicOffset(buffer) { + let view = new DataView(buffer); + for (let i = 0; i < view.byteLength; i += 4) { + if (view.getUint32(i, true) == 0x54535543) { // "CUST" + return i; + } + } + throw new Error("Invalid loader.kip file"); +} + +class ErrorToolTip { + id: string; + message: string | null; + element: HTMLElement | null; + + constructor(id: string) { + this.id = id; + this.element = document.getElementById(id); + }; + + setMsg(msg: string) { + this.message = msg; + } + + show() { + if (this.element) { + this.element.setAttribute("aria-invalid", "true"); + if (this.message) { + this.element.setAttribute("title", this.message); + this.element.parentElement!.setAttribute("data-tooltip", this.message); + this.element.parentElement!.setAttribute("data-placement", "top"); + } + } + }; + + clear() { + if (this.element) { + this.element.removeAttribute("aria-invalid"); + this.element.removeAttribute("title"); + this.element.parentElement!.removeAttribute("data-tooltip"); + this.element.parentElement!.removeAttribute("data-placement"); + } + } + + addChangeListener() { + if (this.element) { + this.element.addEventListener('change', (_evt) => { + let obj = CustTable!.filter((obj) => { return obj.name === this.id; })[0]; + obj.value = Number((this.element as HTMLInputElement).value); + ValidateCustEntry(obj); + }); + } + } +}; + +function ValidateCustEntry(entry: CustEntry): ErrorToolTip | null { + let elementId = entry.name; + let tip = new ErrorToolTip(elementId); + tip.clear(); + if (entry.value! == 0) + return null; + if (entry.value! < entry.min || entry.value! > entry.max) { + tip.setMsg(`Expected range: [${entry.min}, ${entry.max}], got ${entry.value}.`); + tip.show(); + return tip; + } + if (entry.validator && !entry.validator(entry.value)) { + tip.setMsg(`Invalid value: ${entry.value}. Did not pass this validator: ${entry.validator}.`); + tip.show(); + return tip; + } + return null; +} + +function ValidateCust() { + let tooltips: Array = []; + + for (let i of CustTable!) { + let tip = ValidateCustEntry(i); + if (tip) { + tooltips.push(tip); + } + } + + if (tooltips.length > 0) { + throw new Error("Invalid cust"); + } +} + +function SaveCust(buffer) { + let view = new DataView(buffer); + let storage = {}; + storage["custRev"] = CUST_REV; + + for (let i of CustTable!) { + let id = i.name; + i.value = Number((document.getElementById(id) as HTMLInputElement).value); + storage[i.name] = i.value; + switch (i.size) { + case 2: + view.setUint16(i.offset!, i.value, true); + break; + case 4: + view.setUint32(i.offset!, i.value, true); + break; + default: + document.getElementById(i.name)!.focus(); + throw new Error("Unknown size at " + i); + } + } + ValidateCust(); + + localStorage.setItem("last_saved", JSON.stringify(storage)); + let a = document.createElement("a"); + a.href = window.URL.createObjectURL(new Blob([buffer], { type: "application/octet-stream" })); + a.download = "loader.kip"; + a.click(); +} + +function LastSaved() { + let storage = localStorage.getItem("last_saved"); + if (!storage) { + return false; + } + let sObj = JSON.parse(storage); + if (!sObj["custRev"] || sObj["custRev"] != CUST_REV) { + localStorage.removeItem("last_saved"); + return false; + } + return true; +} + +function LoadLastSaved() { + if (LastSaved()) { + let storage = localStorage.getItem("last_saved"); + let sObj = JSON.parse(storage!); + for (let key in sObj) { + if (key == "custRev") { + continue; + } + (document.getElementById(key) as HTMLInputElement).value = sObj[key]; + } + } +} + +function LoadDefault() { + for (let i of CustTable!) { + let id = i.name; + (document.getElementById(id) as HTMLInputElement).value = String(i.defval); + } +} + +function ClearHTMLForm() { + if (!CustTable) { + return; + } + for (let i of CustTable) { + let id = i.name; + let input = document.getElementById(id) as HTMLInputElement; + input.parentElement?.parentElement?.remove(); + } +} + +function UpdateHTMLForm() { + let dict = Object.assign({}, ...CustTable!.map((x: { name: any; }) => ({ [x.name]: x }))); + let form = document.getElementById("form")!; + for (let i of CustTable!) { + let id = i.name; + let input = document.getElementById(id) as HTMLInputElement; + if (!input) { + let grid = document.createElement("div"); + grid.classList.add("grid"); + + // Label and input box + input = document.createElement("input"); + input.min = dict[i.name].min; + input.max = dict[i.name].max; + input.id = id; + input.type = "number"; + input.step = dict[i.name].step; + let label = document.createElement("label"); + label.setAttribute("for", id); + label.innerHTML = id; + label.appendChild(input); + grid.appendChild(label); + + // Description in blockquote style + let desc = dict[i.name].desc!; + let block = document.createElement("blockquote"); + block.style["margin-top"] = "0"; + block.innerHTML = desc; + block.setAttribute("for", id); + grid.appendChild(block); + + grid.style["margin-top"] = "3rem"; + form.appendChild(grid); + + let tooltip = new ErrorToolTip(id); + tooltip.addChangeListener(); + } + input.value = dict[i.name].value; + } + + let default_btn = document.getElementById("load_default")!; + default_btn.removeAttribute("disabled"); + default_btn.addEventListener('click', () => { + LoadDefault(); + }); + + if (LastSaved()) { + let last_btn = document.getElementById("load_saved")!; + last_btn.removeAttribute("disabled"); + last_btn.addEventListener('click', () => { + LoadLastSaved(); + }); + } + + let save_btn = document.getElementById("save")!; + save_btn.removeAttribute("disabled"); + save_btn.addEventListener('click', () => { + try { + SaveCust(buffer); + } catch (e) { + console.log(e); + alert(e); + } + }); +} + +function ParseCust(magicOffset, buffer) { + let view = new DataView(buffer); + let offset = magicOffset + 4; + let rev = view.getUint16(offset, true); + if (rev != 2 && rev != 3) { + throw new Error("Unsupported custRev, expected: 2 or 3, got " + rev); + } + CUST_REV = rev; + document.getElementById("cust_rev")!.innerHTML = `Cust V${CUST_REV} is loaded.`; + if (rev == 2) { + CustTable = CustTableV2; + } else { + CustTable = CustTableV3; + } + + offset += 2; + for (let i of CustTable) { + i.offset = offset; + switch (i.size) { + case 2: + i.value = view.getUint16(offset, true); + break; + case 4: + i.value = view.getUint32(offset, true); + break; + default: + document.getElementById(i.name)!.focus(); + throw new Error("Unknown size at " + i); + } + offset += i.size; + } +} + +const fileInput = document.getElementById("file") as HTMLInputElement; +fileInput.addEventListener('change', (event) => { + let reader = new FileReader(); + reader.readAsArrayBuffer((event.target as HTMLInputElement).files![0]); + reader.onloadend = (progEvent) => { + if (progEvent.target!.readyState === FileReader.DONE) { + buffer = progEvent.target!.result!; + try { + let offset = FindMagicOffset(buffer); + ClearHTMLForm(); + ParseCust(offset, buffer); + ValidateCust(); + UpdateHTMLForm(); + } catch (e) { + console.log(e); + alert(e); + fileInput.value = ""; + } + } + } +}) + +/* GitHub Release fetch */ +type ReleaseInfo = { + OCSuiteVer: string; + LoaderKipUrl: string; + SdOutZipUrl: string; + AMSVer: string; + AMSUrl: string; +}; + +async function fetchRelease(): Promise { + try { + const responseFromSuite = await fetch('https://api.github.com/repos/KazushiMe/Switch-OC-Suite/releases/latest', { + method: 'GET', + headers: { + Accept: 'application/json', + }, + }); + if (!responseFromSuite.ok) { + throw new Error(`Failed to fetch latest release info from GitHub: ${responseFromSuite.status}`); + } + + const resultFromSuite = await responseFromSuite.json(); + const latestVerFromSuite = resultFromSuite.tag_name; + const correspondingVerFromAMS = latestVerFromSuite.split(".").slice(0, 3).join("."); + + const loaderKip = resultFromSuite.assets.filter((obj) => { + return obj.name.endsWith("loader.kip"); + })[0]; + const sdOut = resultFromSuite.assets.filter((obj) => { + return obj.name.endsWith(".zip"); + })[0]; + const amsReleaseUrl = `https://github.com/Atmosphere-NX/Atmosphere/releases/tags/${correspondingVerFromAMS}`; + + let info: ReleaseInfo = { + OCSuiteVer: latestVerFromSuite, + LoaderKipUrl: loaderKip.browser_download_url, + SdOutZipUrl: sdOut.browser_download_url, + AMSVer: correspondingVerFromAMS, + AMSUrl: amsReleaseUrl + }; + return info; + } catch (e) { + console.log(e); + alert(e); + } +} + +async function updateDownloadUrls() { + const updateHref = (id: string, name: string, url: string) => { + let element = document.getElementById(id)!; + element.innerHTML = name; + element.removeAttribute("aria-busy"); + element.setAttribute("href", url); + }; + + let info = await fetchRelease(); + if (info) { + const loaderKipName = `loader.kip ${info.OCSuiteVer}`; + updateHref("loader_kip_btn", loaderKipName, info.LoaderKipUrl); + + const sdOutName = `SdOut.zip ${info.OCSuiteVer}`; + updateHref("sdout_zip_btn", sdOutName, info.SdOutZipUrl); + + const amsName = `Atmosphere-NX ${info.AMSVer}`; + updateHref("ams_btn", amsName, info.AMSUrl); + } +} + +addEventListener('DOMContentLoaded', async (_evt) => { + await updateDownloadUrls(); +}); \ No newline at end of file