diff --git a/.env.example b/.env.example index 06069496..3fd316e5 100644 --- a/.env.example +++ b/.env.example @@ -30,4 +30,11 @@ SMTP_HOST = "localhost" SMTP_PORT = "1025" SMTP_SECURE = "false" SMTP_USERNAME = "ignored" -SMTP_PASSWORD = "ignored" \ No newline at end of file +SMTP_PASSWORD = "ignored" + +S3_ENDPOINT="http://localhost:9000" +S3_REGION="eu-north-1" +S3_BUCKET="device-images" +S3_ACCESS_KEY="minioadmin" +S3_SECRET_KEY="minioadmin123" +S3_PUBLIC_URL="http://localhost:9000/device-images" diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 60b0d507..dbc190e6 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -118,7 +118,7 @@ jobs: # Setup cache - name: ⚡️ Cache Docker layers - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: /tmp/.buildx-cache key: ${{ runner.os }}-buildx-${{ github.sha }} @@ -131,6 +131,11 @@ jobs: with: images: ghcr.io/opensensemap/frontend + - name: 👓 Read .nvmrc + id: nvmrc + run: | + echo "NODE_VERSION=$(cat .nvmrc)" >> $GITHUB_OUTPUT + - name: 🔑 GitHub Registry Auth uses: docker/login-action@v3 with: @@ -148,6 +153,7 @@ jobs: labels: ${{ steps.meta.outputs.labels }} build-args: | COMMIT_SHA=${{ github.sha }} + NODE_VERSION=${{ steps.nvmrc.outputs.NODE_VERSION }} cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,mode=max,dest=/tmp/.buildx-cache-new diff --git a/.gitignore b/.gitignore index f15acc73..ef2cdd12 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,6 @@ measurements.csv .react-router/ -/coverage \ No newline at end of file +/coverage + +/minio-data \ No newline at end of file diff --git a/.nvmrc b/.nvmrc index 564e92d0..3fe3b157 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v24.4.1 +24.13.0 diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 6d4bf5c7..8eefcd81 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -2,6 +2,7 @@ // List of extensions which should be recommended for users of this workspace. "recommendations": [ "esbenp.prettier-vscode", - "bradlc.vscode-tailwindcss" + "bradlc.vscode-tailwindcss", + "lokalise.i18n-ally" ] -} \ No newline at end of file +} diff --git a/app/app.css b/app/app.css deleted file mode 100644 index 5bfbcbef..00000000 --- a/app/app.css +++ /dev/null @@ -1 +0,0 @@ -/*! tailwindcss v3.4.3 | MIT License | https://tailwindcss.com*/*,:after,:before{border-color:var(--color-gray-200);border-style:solid;border-width:0;box-sizing:border-box}:after,:before{--tw-content:""}:host,html{-webkit-text-size-adjust:100%;font-feature-settings:normal;-webkit-tap-highlight-color:transparent;font-family:Urbanist,ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-variation-settings:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-feature-settings:normal;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em;font-variation-settings:normal}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{font-feature-settings:inherit;color:inherit;font-family:inherit;font-size:100%;font-variation-settings:inherit;font-weight:inherit;letter-spacing:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:var(--color-gray-400);opacity:1}input::placeholder,textarea::placeholder{color:var(--color-gray-400);opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,:after,:before{--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-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--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:rgba(147,197,253,.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: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::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-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--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:rgba(147,197,253,.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: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }.container{margin-left:auto;margin-right:auto;padding-left:2rem;padding-right:2rem;width:100%}@media (min-width:1400px){.container{max-width:1400px}}.prose{color:var(--tw-prose-body);max-width:65ch}.prose :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em;margin-top:1.25em}.prose :where([class~=lead]):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-lead);font-size:1.25em;line-height:1.6;margin-bottom:1.2em;margin-top:1.2em}.prose :where(a):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-links);font-weight:500;text-decoration:underline}.prose :where(strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-bold);font-weight:600}.prose :where(a strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(blockquote strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(thead th strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:decimal;margin-bottom:1.25em;margin-top:1.25em;padding-inline-start:1.625em}.prose :where(ol[type=A]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.prose :where(ol[type=a]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.prose :where(ol[type=A s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.prose :where(ol[type=a s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.prose :where(ol[type=I]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.prose :where(ol[type=i]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.prose :where(ol[type=I s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.prose :where(ol[type=i s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.prose :where(ol[type="1"]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:decimal}.prose :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:disc;margin-bottom:1.25em;margin-top:1.25em;padding-inline-start:1.625em}.prose :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{color:var(--tw-prose-counters);font-weight:400}.prose :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{color:var(--tw-prose-bullets)}.prose :where(dt):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;margin-top:1.25em}.prose :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:var(--tw-prose-hr);border-top-width:1px;margin-bottom:3em;margin-top:3em}.prose :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)){border-inline-start-color:var(--tw-prose-quote-borders);border-inline-start-width:.25rem;color:var(--tw-prose-quotes);font-style:italic;font-weight:500;margin-bottom:1.6em;margin-top:1.6em;padding-inline-start:1em;quotes:"\201C""\201D""\2018""\2019"}.prose :where(blockquote p:first-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:open-quote}.prose :where(blockquote p:last-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:close-quote}.prose :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-size:2.25em;font-weight:800;line-height:1.1111111;margin-bottom:.8888889em;margin-top:0}.prose :where(h1 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:900}.prose :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-size:1.5em;font-weight:700;line-height:1.3333333;margin-bottom:1em;margin-top:2em}.prose :where(h2 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:800}.prose :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-size:1.25em;font-weight:600;line-height:1.6;margin-bottom:.6em;margin-top:1.6em}.prose :where(h3 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:700}.prose :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;line-height:1.5;margin-bottom:.5em;margin-top:1.5em}.prose :where(h4 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:700}.prose :where(img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:2em;margin-top:2em}.prose :where(picture):not(:where([class~=not-prose],[class~=not-prose] *)){display:block;margin-bottom:2em;margin-top:2em}.prose :where(video):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:2em;margin-top:2em}.prose :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){border-radius:.3125rem;box-shadow:0 0 0 1px rgb(var(--tw-prose-kbd-shadows)/10%),0 3px 0 rgb(var(--tw-prose-kbd-shadows)/10%);color:var(--tw-prose-kbd);font-family:inherit;font-size:.875em;font-weight:500;padding-inline-end:.375em;padding-bottom:.1875em;padding-top:.1875em;padding-inline-start:.375em}.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-code);font-size:.875em;font-weight:600}.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:"`"}.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:"`"}.prose :where(a code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(h1 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(h2 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.875em}.prose :where(h3 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.9em}.prose :where(h4 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(blockquote code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(thead th code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(pre):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:var(--tw-prose-pre-bg);border-radius:.375rem;color:var(--tw-prose-pre-code);font-size:.875em;font-weight:400;line-height:1.7142857;margin-bottom:1.7142857em;margin-top:1.7142857em;overflow-x:auto;padding-inline-end:1.1428571em;padding-bottom:.8571429em;padding-top:.8571429em;padding-inline-start:1.1428571em}.prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:transparent;border-radius:0;border-width:0;color:inherit;font-family:inherit;font-size:inherit;font-weight:inherit;line-height:inherit;padding:0}.prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:none}.prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:none}.prose :where(table):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.875em;line-height:1.7142857;margin-bottom:2em;margin-top:2em;table-layout:auto;text-align:start;width:100%}.prose :where(thead):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-color:var(--tw-prose-th-borders);border-bottom-width:1px}.prose :where(thead th):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;padding-inline-end:.5714286em;padding-bottom:.5714286em;padding-inline-start:.5714286em;vertical-align:bottom}.prose :where(tbody tr):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-color:var(--tw-prose-td-borders);border-bottom-width:1px}.prose :where(tbody tr:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:0}.prose :where(tbody td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:baseline}.prose :where(tfoot):not(:where([class~=not-prose],[class~=not-prose] *)){border-top-color:var(--tw-prose-th-borders);border-top-width:1px}.prose :where(tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:top}.prose :where(figure>*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0;margin-top:0}.prose :where(figcaption):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-captions);font-size:.875em;line-height:1.4285714;margin-top:.8571429em}.prose{--tw-prose-body:#374151;--tw-prose-headings:#111827;--tw-prose-lead:#4b5563;--tw-prose-links:#111827;--tw-prose-bold:#111827;--tw-prose-counters:#6b7280;--tw-prose-bullets:#d1d5db;--tw-prose-hr:#e5e7eb;--tw-prose-quotes:#111827;--tw-prose-quote-borders:#e5e7eb;--tw-prose-captions:#6b7280;--tw-prose-kbd:#111827;--tw-prose-kbd-shadows:17 24 39;--tw-prose-code:#111827;--tw-prose-pre-code:#e5e7eb;--tw-prose-pre-bg:#1f2937;--tw-prose-th-borders:#d1d5db;--tw-prose-td-borders:#e5e7eb;--tw-prose-invert-body:#d1d5db;--tw-prose-invert-headings:#fff;--tw-prose-invert-lead:#9ca3af;--tw-prose-invert-links:#fff;--tw-prose-invert-bold:#fff;--tw-prose-invert-counters:#9ca3af;--tw-prose-invert-bullets:#4b5563;--tw-prose-invert-hr:#374151;--tw-prose-invert-quotes:#f3f4f6;--tw-prose-invert-quote-borders:#374151;--tw-prose-invert-captions:#9ca3af;--tw-prose-invert-kbd:#fff;--tw-prose-invert-kbd-shadows:255 255 255;--tw-prose-invert-code:#fff;--tw-prose-invert-pre-code:#d1d5db;--tw-prose-invert-pre-bg:rgba(0,0,0,.5);--tw-prose-invert-th-borders:#4b5563;--tw-prose-invert-td-borders:#374151;font-size:1rem;line-height:1.75}.prose :where(picture>img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0;margin-top:0}.prose :where(li):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.5em;margin-top:.5em}.prose :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.375em}.prose :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.375em}.prose :where(.prose>ul>li p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.75em;margin-top:.75em}.prose :where(.prose>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.prose :where(.prose>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.prose :where(.prose>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.prose :where(.prose>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.prose :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.75em;margin-top:.75em}.prose :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em;margin-top:1.25em}.prose :where(dd):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.5em;padding-inline-start:1.625em}.prose :where(hr+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(h2+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(h3+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(h4+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(thead th:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.prose :where(thead th:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.prose :where(tbody td,tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:.5714286em;padding-bottom:.5714286em;padding-top:.5714286em;padding-inline-start:.5714286em}.prose :where(tbody td:first-child,tfoot td:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.prose :where(tbody td:last-child,tfoot td:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.prose :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:2em;margin-top:2em}.prose :where(.prose>:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(.prose>:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0}.sr-only{clip:rect(0,0,0,0);border-width:0;height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;white-space:nowrap;width:1px}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.visible{visibility:visible}.invisible{visibility:hidden}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{inset:0}.inset-x-0{left:0;right:0}.inset-x-4{left:1rem;right:1rem}.inset-y-0{bottom:0;top:0}.-left-4{left:-1rem}.-left-\[10px\]{left:-10px}.-right-3{right:-.75rem}.-top-4{top:-1rem}.-top-\[10px\]{top:-10px}.bottom-0{bottom:0}.bottom-4{bottom:1rem}.bottom-6{bottom:1.5rem}.bottom-\[10px\]{bottom:10px}.bottom-\[15\%\]{bottom:15%}.bottom-full{bottom:100%}.left-0{left:0}.left-1{left:.25rem}.left-1\/2{left:50%}.left-2{left:.5rem}.left-4{left:1rem}.left-5{left:1.25rem}.left-\[50\%\]{left:50%}.right-0{right:0}.right-1{right:.25rem}.right-1\/2{right:50%}.right-2{right:.5rem}.right-3{right:.75rem}.right-4{right:1rem}.top-0{top:0}.top-1\/2{top:50%}.top-10{top:2.5rem}.top-14{top:3.5rem}.top-2{top:.5rem}.top-3{top:.75rem}.top-4{top:1rem}.top-\[20\%\]{top:20%}.top-\[50\%\]{top:50%}.top-full{top:100%}.z-10{z-index:10}.z-20{z-index:20}.z-40{z-index:40}.z-50{z-index:50}.z-\[100\]{z-index:100}.z-auto{z-index:auto}.col-span-1{grid-column:span 1/span 1}.col-span-2{grid-column:span 2/span 2}.col-span-3{grid-column:span 3/span 3}.col-span-6{grid-column:span 6/span 6}.col-span-8{grid-column:span 8/span 8}.col-start-3{grid-column-start:3}.float-left{float:left}.m-0{margin:0}.m-1{margin:.25rem}.m-2{margin:.5rem}.m-auto{margin:auto}.-mx-1{margin-left:-.25rem;margin-right:-.25rem}.-mx-4{margin-left:-1rem;margin-right:-1rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.mx-32{margin-left:8rem;margin-right:8rem}.mx-4{margin-left:1rem;margin-right:1rem}.mx-5{margin-left:1.25rem;margin-right:1.25rem}.mx-auto{margin-left:auto;margin-right:auto}.my-1{margin-bottom:.25rem;margin-top:.25rem}.my-2{margin-bottom:.5rem;margin-top:.5rem}.my-3{margin-bottom:.75rem;margin-top:.75rem}.my-4{margin-bottom:1rem;margin-top:1rem}.my-5{margin-bottom:1.25rem;margin-top:1.25rem}.my-6{margin-bottom:1.5rem;margin-top:1.5rem}.-mb-\[2px\]{margin-bottom:-2px}.-mt-4{margin-top:-1rem}.mb-0{margin-bottom:0}.mb-1{margin-bottom:.25rem}.mb-10{margin-bottom:2.5rem}.mb-12{margin-bottom:3rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-4{margin-left:1rem}.ml-6{margin-left:1.5rem}.ml-\[6px\]{margin-left:6px}.ml-auto{margin-left:auto}.mr-1{margin-right:.25rem}.mr-2{margin-right:.5rem}.mr-3{margin-right:.75rem}.mr-4{margin-right:1rem}.mt-0{margin-top:0}.mt-1{margin-top:.25rem}.mt-10{margin-top:2.5rem}.mt-14{margin-top:3.5rem}.mt-2{margin-top:.5rem}.mt-24{margin-top:6rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.mt-6{margin-top:1.5rem}.mt-7{margin-top:1.75rem}.mt-8{margin-top:2rem}.mt-\[20px\]{margin-top:20px}.mt-auto{margin-top:auto}.box-border{box-sizing:border-box}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.\!table{display:table!important}.table{display:table}.table-cell{display:table-cell}.grid{display:grid}.hidden{display:none}.aspect-\[4\/3\]{aspect-ratio:4/3}.aspect-auto{aspect-ratio:auto}.aspect-square{aspect-ratio:1/1}.h-0{height:0}.h-1\/2{height:50%}.h-10{height:2.5rem}.h-11{height:2.75rem}.h-12{height:3rem}.h-14{height:3.5rem}.h-16{height:4rem}.h-2{height:.5rem}.h-2\.5{height:.625rem}.h-24{height:6rem}.h-3{height:.75rem}.h-3\.5{height:.875rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-52{height:13rem}.h-6{height:1.5rem}.h-60{height:15rem}.h-64{height:16rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-\[1026px\]{height:1026px}.h-\[17px\]{height:17px}.h-\[1px\]{height:1px}.h-\[222px\]{height:222px}.h-\[24px\]{height:24px}.h-\[3\.625rem\]{height:3.625rem}.h-\[300px\]{height:300px}.h-\[350px\]{height:350px}.h-\[448px\]{height:448px}.h-\[85vh\]{height:85vh}.h-\[90px\]{height:90px}.h-\[var\(--radix-select-trigger-height\)\]{height:var(--radix-select-trigger-height)}.h-auto{height:auto}.h-fit{height:-moz-fit-content;height:fit-content}.h-full{height:100%}.h-px{height:1px}.h-screen{height:100vh}.max-h-24{max-height:6rem}.max-h-\[300px\]{max-height:300px}.max-h-\[calc\(100vh-4rem\)\]{max-height:calc(100vh - 4rem)}.max-h-screen{max-height:100vh}.min-h-\[2\.5rem\]{min-height:2.5rem}.min-h-\[80px\]{min-height:80px}.min-h-full{min-height:100%}.w-0{width:0}.w-0\.5{width:.125rem}.w-1\/2{width:50%}.w-1\/4{width:25%}.w-1\/5{width:20%}.w-10{width:2.5rem}.w-10\/12{width:83.333333%}.w-12{width:3rem}.w-16{width:4rem}.w-2{width:.5rem}.w-2\.5{width:.625rem}.w-2\/3{width:66.666667%}.w-3{width:.75rem}.w-3\.5{width:.875rem}.w-3\/4{width:75%}.w-4{width:1rem}.w-40{width:10rem}.w-48{width:12rem}.w-5{width:1.25rem}.w-5\/6{width:83.333333%}.w-52{width:13rem}.w-56{width:14rem}.w-6{width:1.5rem}.w-64{width:16rem}.w-7{width:1.75rem}.w-72{width:18rem}.w-8{width:2rem}.w-9{width:2.25rem}.w-\[100px\]{width:100px}.w-\[1026px\]{width:1026px}.w-\[15px\]{width:15px}.w-\[16px\]{width:16px}.w-\[1px\]{width:1px}.w-\[200px\]{width:200px}.w-\[30\%\]{width:30%}.w-\[300px\]{width:300px}.w-\[310px\]{width:310px}.w-\[44px\]{width:44px}.w-\[50\%\]{width:50%}.w-\[500px\]{width:500px}.w-\[80\%\]{width:80%}.w-auto{width:auto}.w-fit{width:-moz-fit-content;width:fit-content}.w-full{width:100%}.w-screen{width:100vw}.min-w-0{min-width:0}.min-w-\[8rem\]{min-width:8rem}.min-w-\[var\(--radix-select-trigger-width\)\]{min-width:var(--radix-select-trigger-width)}.min-w-fit{min-width:-moz-fit-content;min-width:fit-content}.max-w-24{max-width:6rem}.max-w-2xl{max-width:42rem}.max-w-3xl{max-width:48rem}.max-w-5xl{max-width:64rem}.max-w-7xl{max-width:80rem}.max-w-\[100px\]{max-width:100px}.max-w-\[150px\]{max-width:150px}.max-w-lg{max-width:32rem}.max-w-md{max-width:28rem}.max-w-screen-xl{max-width:1280px}.max-w-sm{max-width:24rem}.flex-1{flex:1 1 0%}.flex-none{flex:none}.shrink{flex-shrink:1}.shrink-0{flex-shrink:0}.flex-grow,.grow{flex-grow:1}.grow-0{flex-grow:0}.basis-0{flex-basis:0px}.basis-10{flex-basis:2.5rem}.caption-bottom{caption-side:bottom}.border-collapse{border-collapse:collapse}.-translate-x-1\/2{--tw-translate-x:-50%}.-translate-x-1\/2,.-translate-y-1\/2{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))}.-translate-y-1\/2{--tw-translate-y:-50%}.translate-x-1\/2{--tw-translate-x:50%}.translate-x-1\/2,.translate-x-\[-50\%\]{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))}.translate-x-\[-50\%\]{--tw-translate-x:-50%}.translate-y-\[-50\%\]{--tw-translate-y:-50%}.scale-\[1\.2\],.translate-y-\[-50\%\]{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))}.scale-\[1\.2\]{--tw-scale-x:1.2;--tw-scale-y:1.2}.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))}@keyframes ping{75%,to{opacity:0;transform:scale(2)}}.animate-ping{animation:ping 1s cubic-bezier(0,0,.2,1) infinite}@keyframes pulse{50%{opacity:.5}0%,to{opacity:1}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}@keyframes spin{to{transform:rotate(1turn)}}.animate-spin{animation:spin 1s linear infinite}.cursor-default{cursor:default}.cursor-move{cursor:move}.cursor-pointer{cursor:pointer}.touch-none{touch-action:none}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize{resize:both}.appearance-auto{-webkit-appearance:auto;-moz-appearance:auto;appearance:auto}.grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.grid-cols-8{grid-template-columns:repeat(8,minmax(0,1fr))}.grid-cols-\[1fr_110px\]{grid-template-columns:1fr 110px}.grid-rows-1{grid-template-rows:repeat(1,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-col-reverse{flex-direction:column-reverse}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.justify-around{justify-content:space-around}.justify-evenly{justify-content:space-evenly}.gap-0{gap:0}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.gap-10{gap:2.5rem}.gap-2{gap:.5rem}.gap-20{gap:5rem}.gap-3{gap:.75rem}.gap-4{gap:1rem}.gap-5{gap:1.25rem}.gap-6{gap:1.5rem}.gap-x-3{-moz-column-gap:.75rem;column-gap:.75rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-x-6{-moz-column-gap:1.5rem;column-gap:1.5rem}.gap-y-2{row-gap:.5rem}.gap-y-4{row-gap:1rem}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.25rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.25rem*var(--tw-space-x-reverse))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.5rem*var(--tw-space-x-reverse))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(1rem*var(--tw-space-x-reverse))}.space-y-0>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(0px*var(--tw-space-y-reverse));margin-top:calc(0px*(1 - var(--tw-space-y-reverse)))}.space-y-0\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.125rem*var(--tw-space-y-reverse));margin-top:calc(.125rem*(1 - var(--tw-space-y-reverse)))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.25rem*var(--tw-space-y-reverse));margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)))}.space-y-1\.5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.375rem*var(--tw-space-y-reverse));margin-top:calc(.375rem*(1 - var(--tw-space-y-reverse)))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.5rem*var(--tw-space-y-reverse));margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1rem*var(--tw-space-y-reverse));margin-top:calc(1rem*(1 - var(--tw-space-y-reverse)))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1.25rem*var(--tw-space-y-reverse));margin-top:calc(1.25rem*(1 - var(--tw-space-y-reverse)))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1.5rem*var(--tw-space-y-reverse));margin-top:calc(1.5rem*(1 - var(--tw-space-y-reverse)))}.space-y-8>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(2rem*var(--tw-space-y-reverse));margin-top:calc(2rem*(1 - var(--tw-space-y-reverse)))}.space-y-\[2px\]>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(2px*var(--tw-space-y-reverse));margin-top:calc(2px*(1 - var(--tw-space-y-reverse)))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-bottom-width:calc(1px*var(--tw-divide-y-reverse));border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)))}.divide-gray-200>:not([hidden])~:not([hidden]){border-color:var(--color-gray-200)}.self-center{align-self:center}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-visible{overflow:visible}.overflow-y-auto{overflow-y:auto}.overflow-x-hidden{overflow-x:hidden}.overflow-y-scroll{overflow-y:scroll}.truncate{overflow:hidden;white-space:nowrap}.overflow-ellipsis,.truncate{text-overflow:ellipsis}.whitespace-nowrap{white-space:nowrap}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:1rem}.rounded-3xl{border-radius:1.5rem}.rounded-\[1\.25rem\]{border-radius:1.25rem}.rounded-\[1px\]{border-radius:1px}.rounded-\[3px\]{border-radius:3px}.rounded-\[5px\]{border-radius:5px}.rounded-\[inherit\]{border-radius:inherit}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:var(--radius)}.rounded-md{border-radius:calc(var(--radius) - 2px)}.rounded-none{border-radius:0}.rounded-sm{border-radius:calc(var(--radius) - 4px)}.rounded-xl{border-radius:.75rem}.rounded-b-lg{border-bottom-left-radius:var(--radius);border-bottom-right-radius:var(--radius)}.rounded-l-full{border-bottom-left-radius:9999px;border-top-left-radius:9999px}.rounded-t-\[10px\]{border-top-left-radius:10px;border-top-right-radius:10px}.rounded-t-lg{border-top-left-radius:var(--radius);border-top-right-radius:var(--radius)}.rounded-bl-none{border-bottom-left-radius:0}.rounded-br-none{border-bottom-right-radius:0}.rounded-tl-none{border-top-left-radius:0}.rounded-tr-none{border-top-right-radius:0}.border{border-width:1px}.border-0{border-width:0}.border-2{border-width:2px}.border-4{border-width:4px}.border-8{border-width:8px}.border-\[1\.5px\]{border-width:1.5px}.border-\[1px\]{border-width:1px}.border-b{border-bottom-width:1px}.border-b-0{border-bottom-width:0}.border-b-2{border-bottom-width:2px}.border-b-8{border-bottom-width:8px}.border-l{border-left-width:1px}.border-l-4{border-left-width:4px}.border-l-\[0px\]{border-left-width:0}.border-r{border-right-width:1px}.border-r-8{border-right-width:8px}.border-r-\[1px\]{border-right-width:1px}.border-t{border-top-width:1px}.border-t-0{border-top-width:0}.border-t-2{border-top-width:2px}.border-t-4{border-top-width:4px}.border-t-\[1px\]{border-top-width:1px}.border-solid{border-style:solid}.border-none{border-style:none}.border-\[\#2e6da4\]{--tw-border-opacity:1;border-color:rgb(46 109 164/var(--tw-border-opacity))}.border-\[\#3c763d\]{--tw-border-opacity:1;border-color:rgb(60 118 61/var(--tw-border-opacity))}.border-\[\#4eaf47\]{--tw-border-opacity:1;border-color:rgb(78 175 71/var(--tw-border-opacity))}.border-\[\#9b9494\]{--tw-border-opacity:1;border-color:rgb(155 148 148/var(--tw-border-opacity))}.border-\[\#FF0000\]{--tw-border-opacity:1;border-color:rgb(255 0 0/var(--tw-border-opacity))}.border-\[\#ac2925\]{--tw-border-opacity:1;border-color:rgb(172 41 37/var(--tw-border-opacity))}.border-\[\#bce8f1\]{--tw-border-opacity:1;border-color:rgb(188 232 241/var(--tw-border-opacity))}.border-\[\#ccc\;\]{border-color:#ccc;}.border-\[\#ccc\]{--tw-border-opacity:1;border-color:rgb(204 204 204/var(--tw-border-opacity))}.border-\[\#d1d5da\]{--tw-border-opacity:1;border-color:rgb(209 213 218/var(--tw-border-opacity))}.border-\[\#d43f3a\]{--tw-border-opacity:1;border-color:rgb(212 63 58/var(--tw-border-opacity))}.border-\[\#ddd\]{--tw-border-opacity:1;border-color:rgb(221 221 221/var(--tw-border-opacity))}.border-\[\#e1e4e8\]{--tw-border-opacity:1;border-color:rgb(225 228 232/var(--tw-border-opacity))}.border-\[\#e2e8f0\]{--tw-border-opacity:1;border-color:rgb(226 232 240/var(--tw-border-opacity))}.border-\[\#f7e1b5\]{--tw-border-opacity:1;border-color:rgb(247 225 181/var(--tw-border-opacity))}.border-\[\#faebcc\]{--tw-border-opacity:1;border-color:rgb(250 235 204/var(--tw-border-opacity))}.border-blue-100{border-color:var(--color-blue-100)}.border-destructive{border-color:hsl(var(--destructive))}.border-gray-100{border-color:var(--color-gray-100)}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-300{border-color:var(--color-gray-300)}.border-gray-500{border-color:var(--color-gray-500)}.border-gray-600{border-color:var(--color-gray-600)}.border-green-200{border-color:var(--color-green-200)}.border-green-500{border-color:var(--color-green-500)}.border-input{border-color:hsl(var(--input))}.border-light-blue{--tw-border-opacity:1;border-color:rgb(3 126 161/var(--tw-border-opacity))}.border-light-green{--tw-border-opacity:1;border-color:rgb(61 132 63/var(--tw-border-opacity))}.border-primary{border-color:hsl(var(--primary))}.border-slate-200{--tw-border-opacity:1;border-color:rgb(226 232 240/var(--tw-border-opacity))}.border-slate-900{--tw-border-opacity:1;border-color:rgb(15 23 42/var(--tw-border-opacity))}.border-transparent{border-color:transparent}.border-zinc-200{border-color:var(--color-zinc-200)}.border-x-gray-100{border-left-color:var(--color-gray-100);border-right-color:var(--color-gray-100)}.border-b-\[\#ddd\]{--tw-border-opacity:1;border-bottom-color:rgb(221 221 221/var(--tw-border-opacity))}.border-b-\[ddd\]{border-bottom-color:ddd}.border-b-gray-100{border-bottom-color:var(--color-gray-100)}.border-l-transparent{border-left-color:transparent}.border-t-gray-100{border-top-color:var(--color-gray-100)}.border-t-transparent{border-top-color:transparent}.bg-\[\#337ab7\]{--tw-bg-opacity:1;background-color:rgb(51 122 183/var(--tw-bg-opacity))}.bg-\[\#4eaf47\]{--tw-bg-opacity:1;background-color:rgb(78 175 71/var(--tw-bg-opacity))}.bg-\[\#d9534f\]{--tw-bg-opacity:1;background-color:rgb(217 83 79/var(--tw-bg-opacity))}.bg-\[\#d9edf7\]{--tw-bg-opacity:1;background-color:rgb(217 237 247/var(--tw-bg-opacity))}.bg-\[\#dcdada\]{--tw-bg-opacity:1;background-color:rgb(220 218 218/var(--tw-bg-opacity))}.bg-\[\#e77817\]{--tw-bg-opacity:1;background-color:rgb(231 120 23/var(--tw-bg-opacity))}.bg-\[\#e9e9ed\]{--tw-bg-opacity:1;background-color:rgb(233 233 237/var(--tw-bg-opacity))}.bg-\[\#f5f5f5\]{--tw-bg-opacity:1;background-color:rgb(245 245 245/var(--tw-bg-opacity))}.bg-\[\#f9f2f4\]{--tw-bg-opacity:1;background-color:rgb(249 242 244/var(--tw-bg-opacity))}.bg-\[\#fcf8e3\]{--tw-bg-opacity:1;background-color:rgb(252 248 227/var(--tw-bg-opacity))}.bg-background{background-color:hsl(var(--background))}.bg-black{background-color:var(--color-black)}.bg-blue-100{background-color:var(--color-blue-100)}.bg-blue-500{background-color:var(--color-blue-500)}.bg-blue-700{background-color:var(--color-blue-700)}.bg-border{background-color:hsl(var(--border))}.bg-destructive{background-color:hsl(var(--destructive))}.bg-gray-100{background-color:var(--color-gray-100)}.bg-gray-200{background-color:var(--color-gray-200)}.bg-gray-300{background-color:var(--color-gray-300)}.bg-gray-700{background-color:var(--color-gray-700)}.bg-green-100{background-color:var(--color-green-100)}.bg-green-300{background-color:var(--color-green-300)}.bg-green-500{background-color:var(--color-green-500)}.bg-light-blue{--tw-bg-opacity:1;background-color:rgb(3 126 161/var(--tw-bg-opacity))}.bg-light-green{--tw-bg-opacity:1;background-color:rgb(61 132 63/var(--tw-bg-opacity))}.bg-muted{background-color:hsl(var(--muted))}.bg-popover{background-color:hsl(var(--popover))}.bg-primary{background-color:hsl(var(--primary))}.bg-red-500{background-color:var(--color-red-500)}.bg-secondary{background-color:hsl(var(--secondary))}.bg-sensorWiki{background-color:var(--color-yellow-sensorWiki)}.bg-slate-100{--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity))}.bg-slate-200{--tw-bg-opacity:1;background-color:rgb(226 232 240/var(--tw-bg-opacity))}.bg-slate-500{--tw-bg-opacity:1;background-color:rgb(100 116 139/var(--tw-bg-opacity))}.bg-slate-900{--tw-bg-opacity:1;background-color:rgb(15 23 42/var(--tw-bg-opacity))}.bg-transparent{background-color:transparent}.bg-white{background-color:var(--color-white)}.fill-\[\#1877f2\]{fill:#1877f2}.fill-\[\#1d9bf0\]{fill:#1d9bf0}.fill-\[\#229ED9\]{fill:#229ed9}.fill-\[\#25D366\]{fill:#25d366}.fill-\[\#bc2a8d\]{fill:#bc2a8d}.fill-current{fill:currentColor}.object-contain{-o-object-fit:contain;object-fit:contain}.object-cover{-o-object-fit:cover;object-fit:cover}.p-0{padding:0}.p-1{padding:.25rem}.p-10{padding:2.5rem}.p-2{padding:.5rem}.p-20{padding:5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.p-8{padding:2rem}.p-\[0\.2rem\]{padding:.2rem}.p-\[1px\]{padding:1px}.p-\[3px\]{padding:3px}.px-0{padding-left:0;padding-right:0}.px-0\.5{padding-left:.125rem;padding-right:.125rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-10{padding-left:2.5rem;padding-right:2.5rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-8{padding-left:2rem;padding-right:2rem}.px-9{padding-left:2.25rem;padding-right:2.25rem}.px-\[10px\]{padding-left:10px;padding-right:10px}.px-\[12px\]{padding-left:12px;padding-right:12px}.px-\[1px\]{padding-left:1px;padding-right:1px}.px-\[2px\]{padding-left:2px;padding-right:2px}.px-\[4px\]{padding-left:4px;padding-right:4px}.px-\[5px\]{padding-left:5px;padding-right:5px}.py-0{padding-bottom:0;padding-top:0}.py-0\.5{padding-bottom:.125rem;padding-top:.125rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.py-1\.5{padding-bottom:.375rem;padding-top:.375rem}.py-10{padding-bottom:2.5rem;padding-top:2.5rem}.py-12{padding-bottom:3rem;padding-top:3rem}.py-16{padding-bottom:4rem;padding-top:4rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.py-20{padding-bottom:5rem;padding-top:5rem}.py-3{padding-bottom:.75rem;padding-top:.75rem}.py-4{padding-bottom:1rem;padding-top:1rem}.py-6{padding-bottom:1.5rem;padding-top:1.5rem}.py-8{padding-bottom:2rem;padding-top:2rem}.py-\[15px\]{padding-bottom:15px;padding-top:15px}.py-\[1px\]{padding-bottom:1px;padding-top:1px}.py-\[2px\]{padding-bottom:2px;padding-top:2px}.py-\[3px\]{padding-bottom:3px;padding-top:3px}.py-\[6px\]{padding-bottom:6px;padding-top:6px}.pb-0{padding-bottom:0}.pb-10{padding-bottom:2.5rem}.pb-14{padding-bottom:3.5rem}.pb-16{padding-bottom:4rem}.pb-2{padding-bottom:.5rem}.pb-3{padding-bottom:.75rem}.pb-4{padding-bottom:1rem}.pb-6{padding-bottom:1.5rem}.pb-\[4px\]{padding-bottom:4px}.pl-0{padding-left:0}.pl-3{padding-left:.75rem}.pl-8{padding-left:2rem}.pr-1{padding-right:.25rem}.pr-2{padding-right:.5rem}.pr-4{padding-right:1rem}.pr-8{padding-right:2rem}.pt-0{padding-top:0}.pt-1{padding-top:.25rem}.pt-10{padding-top:2.5rem}.pt-16{padding-top:4rem}.pt-2{padding-top:.5rem}.pt-20{padding-top:5rem}.pt-4{padding-top:1rem}.pt-5{padding-top:1.25rem}.pt-6{padding-top:1.5rem}.pt-8{padding-top:2rem}.pt-\[5px\]{padding-top:5px}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.text-justify{text-align:justify}.align-middle{vertical-align:middle}.align-bottom{vertical-align:bottom}.align-text-bottom{vertical-align:text-bottom}.align-sub{vertical-align:sub}.font-helvetica{font-family:Helvetica,Arial,sans-serif}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.font-monospace{font-family:Courier New,Courier,monospace}.font-serif{font-family:RobotoSlab,ui-serif,Georgia,Cambria,Times New Roman,Times,serif}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-4xl{font-size:2.25rem;line-height:2.5rem}.text-5xl{font-size:3rem;line-height:1}.text-6xl{font-size:3.75rem;line-height:1}.text-\[0\.6875rem\]{font-size:.6875rem}.text-\[0\.8rem\]{font-size:.8rem}.text-\[11px\]{font-size:11px}.text-\[14px\]{font-size:14px}.text-\[15px\]{font-size:15px}.text-\[18px\]{font-size:18px}.text-\[90\%\]{font-size:90%}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-\[700\]{font-weight:700}.font-black{font-weight:900}.font-bold{font-weight:700}.font-extrabold{font-weight:800}.font-light{font-weight:300}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.font-thin{font-weight:100}.capitalize{text-transform:capitalize}.leading-4{line-height:1rem}.leading-5{line-height:1.25rem}.leading-6{line-height:1.5rem}.leading-\[1\.6\]{line-height:1.6}.leading-\[1\.75\]{line-height:1.75}.leading-\[2\.2\]{line-height:2.2}.leading-none{line-height:1}.leading-tight{line-height:1.25}.tracking-normal{letter-spacing:0}.tracking-tight{letter-spacing:-.025em}.tracking-wide{letter-spacing:.025em}.tracking-wider{letter-spacing:.05em}.tracking-widest{letter-spacing:.1em}.text-\[\#31708f\]{--tw-text-opacity:1;color:rgb(49 112 143/var(--tw-text-opacity))}.text-\[\#333\]{--tw-text-opacity:1;color:rgb(51 51 51/var(--tw-text-opacity))}.text-\[\#337ab7\]{--tw-text-opacity:1;color:rgb(51 122 183/var(--tw-text-opacity))}.text-\[\#4eaf47\]{--tw-text-opacity:1;color:rgb(78 175 71/var(--tw-text-opacity))}.text-\[\#626161\]{--tw-text-opacity:1;color:rgb(98 97 97/var(--tw-text-opacity))}.text-\[\#676767\]{--tw-text-opacity:1;color:rgb(103 103 103/var(--tw-text-opacity))}.text-\[\#818a91\]{--tw-text-opacity:1;color:rgb(129 138 145/var(--tw-text-opacity))}.text-\[\#8a6d3b\]{--tw-text-opacity:1;color:rgb(138 109 59/var(--tw-text-opacity))}.text-\[\#FF0000\]{--tw-text-opacity:1;color:rgb(255 0 0/var(--tw-text-opacity))}.text-\[\#FF4136\]{--tw-text-opacity:1;color:rgb(255 65 54/var(--tw-text-opacity))}.text-\[\#c7254e\]{--tw-text-opacity:1;color:rgb(199 37 78/var(--tw-text-opacity))}.text-\[\#fa5252\]{--tw-text-opacity:1;color:rgb(250 82 82/var(--tw-text-opacity))}.text-\[\#fff\]{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-black{color:var(--color-black)}.text-blue-100{color:var(--color-blue-100)}.text-blue-500{color:var(--color-blue-500)}.text-current{color:currentColor}.text-destructive{color:hsl(var(--destructive))}.text-destructive-foreground{color:hsl(var(--destructive-foreground))}.text-foreground\/50{color:hsl(var(--foreground)/.5)}.text-gray-200{color:var(--color-gray-200)}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-900{color:var(--color-gray-900)}.text-green-100{color:var(--color-green-100)}.text-green-700{color:var(--color-green-700)}.text-light-blue{--tw-text-opacity:1;color:rgb(3 126 161/var(--tw-text-opacity))}.text-light-green{--tw-text-opacity:1;color:rgb(61 132 63/var(--tw-text-opacity))}.text-muted-foreground{color:hsl(var(--muted-foreground))}.text-popover-foreground{color:hsl(var(--popover-foreground))}.text-primary{color:hsl(var(--primary))}.text-primary-foreground{color:hsl(var(--primary-foreground))}.text-red-500{color:var(--color-red-500)}.text-secondary-foreground{color:hsl(var(--secondary-foreground))}.text-slate-50{--tw-text-opacity:1;color:rgb(248 250 252/var(--tw-text-opacity))}.text-slate-500{--tw-text-opacity:1;color:rgb(100 116 139/var(--tw-text-opacity))}.text-slate-900{--tw-text-opacity:1;color:rgb(15 23 42/var(--tw-text-opacity))}.text-slate-950{--tw-text-opacity:1;color:rgb(2 6 23/var(--tw-text-opacity))}.text-white{color:var(--color-white)}.text-zinc-600{color:var(--color-zinc-600)}.text-zinc-800{color:var(--color-zinc-800)}.underline{text-decoration-line:underline}.underline-offset-4{text-underline-offset:4px}.subpixel-antialiased{-webkit-font-smoothing:auto;-moz-osx-font-smoothing:auto}.placeholder-\[\#999\]::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(153 153 153/var(--tw-placeholder-opacity))}.placeholder-\[\#999\]::placeholder{--tw-placeholder-opacity:1;color:rgb(153 153 153/var(--tw-placeholder-opacity))}.opacity-0{opacity:0}.opacity-100{opacity:1}.opacity-25{opacity:.25}.opacity-50{opacity:.5}.opacity-60{opacity:.6}.opacity-70{opacity:.7}.opacity-90{opacity:.9}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-\[5px_5px_rgba\(0\2c _98\2c _90\2c _0\.4\)\2c _8px_8px_rgba\(0\2c _98\2c _90\2c _0\.3\)\2c _11px_11px_rgba\(0\2c _98\2c _90\2c _0\.2\)\2c _14px_14px_rgba\(0\2c _98\2c _90\2c _0\.1\)\2c _17px_17px_rgba\(0\2c _98\2c _90\2c _0\.05\)\]{--tw-shadow:5px 5px rgba(0,98,90,.4),8px 8px rgba(0,98,90,.3),11px 11px rgba(0,98,90,.2),14px 14px rgba(0,98,90,.1),17px 17px rgba(0,98,90,.05);--tw-shadow-colored:5px 5px var(--tw-shadow-color),8px 8px var(--tw-shadow-color),11px 11px var(--tw-shadow-color),14px 14px var(--tw-shadow-color),17px 17px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-lg,.shadow-md{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px rgba(0,0,0,.1),0 2px 4px -2px rgba(0,0,0,.1);--tw-shadow-colored:0 4px 6px -1px var(--tw-shadow-color),0 2px 4px -2px var(--tw-shadow-color)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.shadow-sm,.shadow-xl{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color)}.shadow-\[\#FF0000\]{--tw-shadow-color:red;--tw-shadow:var(--tw-shadow-colored)}.outline-none{outline:2px solid transparent;outline-offset:2px}.outline{outline-style:solid}.ring{--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(3px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring,.ring-0{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-0{--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(var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring-1{--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(1px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring-1,.ring-2{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-2{--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(2px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.ring-black{--tw-ring-color:var(--color-black)}.ring-slate-200{--tw-ring-opacity:1;--tw-ring-color:rgb(226 232 240/var(--tw-ring-opacity))}.ring-opacity-5{--tw-ring-opacity:0.05}.ring-offset-2{--tw-ring-offset-width:2px}.ring-offset-background{--tw-ring-offset-color:hsl(var(--background))}.ring-offset-white{--tw-ring-offset-color:var(--color-white)}.blur{--tw-blur:blur(8px)}.blur,.drop-shadow{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)}.drop-shadow{--tw-drop-shadow:drop-shadow(0 1px 2px rgba(0,0,0,.1)) drop-shadow(0 1px 1px rgba(0,0,0,.06))}.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)}.backdrop-blur-\[1\.5px\]{--tw-backdrop-blur:blur(1.5px)}.backdrop-blur-\[1\.5px\],.backdrop-blur-sm{-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.backdrop-blur-sm{--tw-backdrop-blur:blur(4px)}.transition{transition-duration:.15s;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-timing-function:cubic-bezier(.4,0,.2,1)}.transition-all{transition-duration:.15s;transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-colors{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-opacity{transition-duration:.15s;transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1)}.transition-transform{transition-duration:.15s;transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-100{transition-duration:.1s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}@keyframes enter{0%{opacity:var(--tw-enter-opacity,1);transform:translate3d(var(--tw-enter-translate-x,0),var(--tw-enter-translate-y,0),0) scale3d(var(--tw-enter-scale,1),var(--tw-enter-scale,1),var(--tw-enter-scale,1)) rotate(var(--tw-enter-rotate,0))}}@keyframes exit{to{opacity:var(--tw-exit-opacity,1);transform:translate3d(var(--tw-exit-translate-x,0),var(--tw-exit-translate-y,0),0) scale3d(var(--tw-exit-scale,1),var(--tw-exit-scale,1),var(--tw-exit-scale,1)) rotate(var(--tw-exit-rotate,0))}}.animate-in{--tw-enter-opacity:initial;--tw-enter-scale:initial;--tw-enter-rotate:initial;--tw-enter-translate-x:initial;--tw-enter-translate-y:initial;animation-duration:.15s;animation-name:enter}.fade-in-0{--tw-enter-opacity:0}.zoom-in-95{--tw-enter-scale:.95}.duration-100{animation-duration:.1s}.duration-200{animation-duration:.2s}.duration-300{animation-duration:.3s}.ease-in-out{animation-timing-function:cubic-bezier(.4,0,.2,1)}.\[mask-image\:linear-gradient\(to_bottom\2c white_20\%\2c transparent_75\%\)\]{-webkit-mask-image:linear-gradient(180deg,#fff 20%,transparent 75%);mask-image:linear-gradient(180deg,#fff 20%,transparent 75%)}.\[mask-image\:linear-gradient\(to_bottom\2c white_60\%\2c transparent\)\]{-webkit-mask-image:linear-gradient(180deg,#fff 60%,transparent);mask-image:linear-gradient(180deg,#fff 60%,transparent)}:root{--color-black:#121212;--color-white:#fff;--logo-green:#4fae48;--logo-blue:#00b4e4;--light-menu:#727373;--light-text:#363636;--light-green:#3d843f;--light-blue:#037ea1;--dark-menu:#d2d1d0;--dark-text:#d2d1d0;--dark-green:#6fa161;--dark-blue:#0386aa;--dark-background:#242424;--dark-boxes:#3b3a3a;--color-slate-50:#f8fafc;--color-slate-100:#f1f5f9;--color-slate-200:#e2e8f0;--color-slate-300:#cbd5e1;--color-slate-400:#94a3b8;--color-slate-500:#64748b;--color-slate-600:#475569;--color-slate-700:#334155;--color-slate-800:#1e293b;--color-slate-900:#0f172a;--color-zinc-50:#fafafa;--color-zinc-100:#f4f4f5;--color-zinc-200:#e4e4e7;--color-zinc-300:#d4d4d8;--color-zinc-400:#a1a1aa;--color-zinc-500:#71717a;--color-zinc-600:#52525b;--color-zinc-700:#3f3f46;--color-zinc-800:#27272a;--color-zinc-900:#18181b;--color-blue-100:#3d9cce;--color-blue-200:#1879af;--color-blue-500:#3b82f6;--color-blue-700:#1d4ed8;--color-blue-50:#f8fafc;--color-gray-50:#f9fafb;--color-gray-100:#f3f4f6;--color-gray-200:#e5e7eb;--color-gray-300:#d1d5db;--color-gray-400:#9ca3af;--color-gray-500:#6b7280;--color-gray-600:#4b5563;--color-gray-700:#374151;--color-gray-800:#1f2937;--color-gray-900:#111827;--color-gray-950:#030712;--color-green-50:#f0fdf4;--color-green-100:#dcfce7;--color-green-200:#bbf7d0;--color-green-300:#86efac;--color-green-400:#4ade80;--color-green-500:#22c55e;--color-green-600:#16a34a;--color-green-700:#15803d;--color-green-800:#166534;--color-green-900:#14532d;--color-green-950:#052e16;--color-red-500:#f10000;--color-red-700:#b91c1c;--color-orange-500:#f97316;--color-violet-500:#7f00ff;--color-yellow-500:#eab308;--color-yellow-sensorWiki:#ffdd57;--color-headerBorder:#e3e3e3;--background:0 0% 100%;--foreground:222.2 47.4% 11.2%;--muted:210 40% 96.1%;--muted-foreground:215.4 16.3% 46.9%;--popover:0 0% 100%;--popover-foreground:222.2 47.4% 11.2%;--card:0 0% 100%;--card-foreground:222.2 47.4% 11.2%;--border:214.3 31.8% 91.4%;--input:214.3 31.8% 91.4%;--primary:222.2 47.4% 11.2%;--primary-foreground:210 40% 98%;--secondary:210 40% 96.1%;--secondary-foreground:222.2 47.4% 11.2%;--accent:210 40% 96.1%;--accent-foreground:222.2 47.4% 11.2%;--destructive:0 100% 50%;--destructive-foreground:210 40% 98%;--ring:215 20.2% 65.1%;--radius:0.5rem}.dark{--background:224 71% 4%;--foreground:213 31% 91%;--muted:223 47% 11%;--muted-foreground:215.4 16.3% 56.9%;--popover:224 71% 4%;--popover-foreground:215 20.2% 65.1%;--card:0 0% 100%;--card-foreground:222.2 47.4% 11.2%;--border:216 34% 17%;--input:216 34% 17%;--primary:210 40% 98%;--primary-foreground:222.2 47.4% 1.2%;--secondary:222.2 47.4% 11.2%;--secondary-foreground:210 40% 98%;--accent:216 34% 17%;--accent-foreground:210 40% 98%;--destructive:0 63% 31%;--destructive-foreground:210 40% 98%;--ring:216 34% 17%;--radius:0.5rem}kbd{background-color:#eee;border:1px solid #b4b4b4;border-radius:3px;box-shadow:0 1px 1px rgba(0,0,0,.2),inset 0 2px 0 0 hsla(0,0%,100%,.7);color:#333;display:inline-block;font-size:.85em;font-weight:700;line-height:1;margin-bottom:4px;margin-top:2px;padding:2px 4px;vertical-align:middle;white-space:nowrap}.mapboxgl-ctrl-geocoder{width:500px!important}@media (min-width:1024px){.lg\:prose{color:var(--tw-prose-body);max-width:65ch}.lg\:prose :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em;margin-top:1.25em}.lg\:prose :where([class~=lead]):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-lead);font-size:1.25em;line-height:1.6;margin-bottom:1.2em;margin-top:1.2em}.lg\:prose :where(a):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-links);font-weight:500;text-decoration:underline}.lg\:prose :where(strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-bold);font-weight:600}.lg\:prose :where(a strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.lg\:prose :where(blockquote strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.lg\:prose :where(thead th strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.lg\:prose :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:decimal;margin-bottom:1.25em;margin-top:1.25em;padding-inline-start:1.625em}.lg\:prose :where(ol[type=A]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.lg\:prose :where(ol[type=a]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.lg\:prose :where(ol[type=A s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.lg\:prose :where(ol[type=a s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.lg\:prose :where(ol[type=I]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.lg\:prose :where(ol[type=i]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.lg\:prose :where(ol[type=I s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.lg\:prose :where(ol[type=i s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.lg\:prose :where(ol[type="1"]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:decimal}.lg\:prose :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:disc;margin-bottom:1.25em;margin-top:1.25em;padding-inline-start:1.625em}.lg\:prose :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{color:var(--tw-prose-counters);font-weight:400}.lg\:prose :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{color:var(--tw-prose-bullets)}.lg\:prose :where(dt):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;margin-top:1.25em}.lg\:prose :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:var(--tw-prose-hr);border-top-width:1px;margin-bottom:3em;margin-top:3em}.lg\:prose :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)){border-inline-start-color:var(--tw-prose-quote-borders);border-inline-start-width:.25rem;color:var(--tw-prose-quotes);font-style:italic;font-weight:500;margin-bottom:1.6em;margin-top:1.6em;padding-inline-start:1em;quotes:"\201C""\201D""\2018""\2019"}.lg\:prose :where(blockquote p:first-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:open-quote}.lg\:prose :where(blockquote p:last-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:close-quote}.lg\:prose :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-size:2.25em;font-weight:800;line-height:1.1111111;margin-bottom:.8888889em;margin-top:0}.lg\:prose :where(h1 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:900}.lg\:prose :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-size:1.5em;font-weight:700;line-height:1.3333333;margin-bottom:1em;margin-top:2em}.lg\:prose :where(h2 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:800}.lg\:prose :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-size:1.25em;font-weight:600;line-height:1.6;margin-bottom:.6em;margin-top:1.6em}.lg\:prose :where(h3 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:700}.lg\:prose :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;line-height:1.5;margin-bottom:.5em;margin-top:1.5em}.lg\:prose :where(h4 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:700}.lg\:prose :where(img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:2em;margin-top:2em}.lg\:prose :where(picture):not(:where([class~=not-prose],[class~=not-prose] *)){display:block;margin-bottom:2em;margin-top:2em}.lg\:prose :where(video):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:2em;margin-top:2em}.lg\:prose :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){border-radius:.3125rem;box-shadow:0 0 0 1px rgb(var(--tw-prose-kbd-shadows)/10%),0 3px 0 rgb(var(--tw-prose-kbd-shadows)/10%);color:var(--tw-prose-kbd);font-family:inherit;font-size:.875em;font-weight:500;padding-inline-end:.375em;padding-bottom:.1875em;padding-top:.1875em;padding-inline-start:.375em}.lg\:prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-code);font-size:.875em;font-weight:600}.lg\:prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:"`"}.lg\:prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:"`"}.lg\:prose :where(a code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.lg\:prose :where(h1 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.lg\:prose :where(h2 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.875em}.lg\:prose :where(h3 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.9em}.lg\:prose :where(h4 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.lg\:prose :where(blockquote code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.lg\:prose :where(thead th code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.lg\:prose :where(pre):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:var(--tw-prose-pre-bg);border-radius:.375rem;color:var(--tw-prose-pre-code);font-size:.875em;font-weight:400;line-height:1.7142857;margin-bottom:1.7142857em;margin-top:1.7142857em;overflow-x:auto;padding-inline-end:1.1428571em;padding-bottom:.8571429em;padding-top:.8571429em;padding-inline-start:1.1428571em}.lg\:prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)){background-color:transparent;border-radius:0;border-width:0;color:inherit;font-family:inherit;font-size:inherit;font-weight:inherit;line-height:inherit;padding:0}.lg\:prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:none}.lg\:prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:none}.lg\:prose :where(table):not(:where([class~=not-prose],[class~=not-prose] *)){font-size:.875em;line-height:1.7142857;margin-bottom:2em;margin-top:2em;table-layout:auto;text-align:start;width:100%}.lg\:prose :where(thead):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-color:var(--tw-prose-th-borders);border-bottom-width:1px}.lg\:prose :where(thead th):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);font-weight:600;padding-inline-end:.5714286em;padding-bottom:.5714286em;padding-inline-start:.5714286em;vertical-align:bottom}.lg\:prose :where(tbody tr):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-color:var(--tw-prose-td-borders);border-bottom-width:1px}.lg\:prose :where(tbody tr:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:0}.lg\:prose :where(tbody td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:baseline}.lg\:prose :where(tfoot):not(:where([class~=not-prose],[class~=not-prose] *)){border-top-color:var(--tw-prose-th-borders);border-top-width:1px}.lg\:prose :where(tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:top}.lg\:prose :where(figure>*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0;margin-top:0}.lg\:prose :where(figcaption):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-captions);font-size:.875em;line-height:1.4285714;margin-top:.8571429em}.lg\:prose{--tw-prose-body:#374151;--tw-prose-headings:#111827;--tw-prose-lead:#4b5563;--tw-prose-links:#111827;--tw-prose-bold:#111827;--tw-prose-counters:#6b7280;--tw-prose-bullets:#d1d5db;--tw-prose-hr:#e5e7eb;--tw-prose-quotes:#111827;--tw-prose-quote-borders:#e5e7eb;--tw-prose-captions:#6b7280;--tw-prose-kbd:#111827;--tw-prose-kbd-shadows:17 24 39;--tw-prose-code:#111827;--tw-prose-pre-code:#e5e7eb;--tw-prose-pre-bg:#1f2937;--tw-prose-th-borders:#d1d5db;--tw-prose-td-borders:#e5e7eb;--tw-prose-invert-body:#d1d5db;--tw-prose-invert-headings:#fff;--tw-prose-invert-lead:#9ca3af;--tw-prose-invert-links:#fff;--tw-prose-invert-bold:#fff;--tw-prose-invert-counters:#9ca3af;--tw-prose-invert-bullets:#4b5563;--tw-prose-invert-hr:#374151;--tw-prose-invert-quotes:#f3f4f6;--tw-prose-invert-quote-borders:#374151;--tw-prose-invert-captions:#9ca3af;--tw-prose-invert-kbd:#fff;--tw-prose-invert-kbd-shadows:255 255 255;--tw-prose-invert-code:#fff;--tw-prose-invert-pre-code:#d1d5db;--tw-prose-invert-pre-bg:rgba(0,0,0,.5);--tw-prose-invert-th-borders:#4b5563;--tw-prose-invert-td-borders:#374151;font-size:1rem;line-height:1.75}.lg\:prose :where(picture>img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0;margin-top:0}.lg\:prose :where(li):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.5em;margin-top:.5em}.lg\:prose :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.375em}.lg\:prose :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.375em}.lg\:prose :where(.lg\:prose>ul>li p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.75em;margin-top:.75em}.lg\:prose :where(.lg\:prose>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.lg\:prose :where(.lg\:prose>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.lg\:prose :where(.lg\:prose>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.lg\:prose :where(.lg\:prose>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.lg\:prose :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:.75em;margin-top:.75em}.lg\:prose :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em;margin-top:1.25em}.lg\:prose :where(dd):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.5em;padding-inline-start:1.625em}.lg\:prose :where(hr+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.lg\:prose :where(h2+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.lg\:prose :where(h3+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.lg\:prose :where(h4+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.lg\:prose :where(thead th:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.lg\:prose :where(thead th:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.lg\:prose :where(tbody td,tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:.5714286em;padding-bottom:.5714286em;padding-top:.5714286em;padding-inline-start:.5714286em}.lg\:prose :where(tbody td:first-child,tfoot td:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.lg\:prose :where(tbody td:last-child,tfoot td:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.lg\:prose :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:2em;margin-top:2em}.lg\:prose :where(.lg\:prose>:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.lg\:prose :where(.lg\:prose>:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0}}.file\:border-0::file-selector-button{border-width:0}.file\:bg-transparent::file-selector-button{background-color:transparent}.file\:text-sm::file-selector-button{font-size:.875rem;line-height:1.25rem}.file\:font-medium::file-selector-button{font-weight:500}.placeholder\:text-slate-500::-moz-placeholder{--tw-text-opacity:1;color:rgb(100 116 139/var(--tw-text-opacity))}.placeholder\:text-slate-500::placeholder{--tw-text-opacity:1;color:rgb(100 116 139/var(--tw-text-opacity))}.after\:content-\[\'_\D7\'\]:after{--tw-content:" ×";content:var(--tw-content)}.first-of-type\:ml-0:first-of-type{margin-left:0}.focus-within\:relative:focus-within{position:relative}.focus-within\:z-20:focus-within{z-index:20}.focus-within\:border-\[3px\]:focus-within{border-width:3px}.focus-within\:border-blue-700:focus-within{border-color:var(--color-blue-700)}.hover\:z-10:hover{z-index:10}.hover\:-translate-y-1:hover{--tw-translate-y:-0.25rem}.hover\:-translate-y-1:hover,.hover\:scale-105:hover{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))}.hover\:scale-105:hover{--tw-scale-x:1.05;--tw-scale-y:1.05}.hover\:cursor-pointer:hover{cursor:pointer}.hover\:border-\[\#204d74\]:hover{--tw-border-opacity:1;border-color:rgb(32 77 116/var(--tw-border-opacity))}.hover\:border-\[\#ac2925\]:hover{--tw-border-opacity:1;border-color:rgb(172 41 37/var(--tw-border-opacity))}.hover\:bg-\[\#1877f2\]:hover{--tw-bg-opacity:1;background-color:rgb(24 119 242/var(--tw-bg-opacity))}.hover\:bg-\[\#1d9bf0\]:hover{--tw-bg-opacity:1;background-color:rgb(29 155 240/var(--tw-bg-opacity))}.hover\:bg-\[\#229ED9\]:hover{--tw-bg-opacity:1;background-color:rgb(34 158 217/var(--tw-bg-opacity))}.hover\:bg-\[\#25D366\]:hover{--tw-bg-opacity:1;background-color:rgb(37 211 102/var(--tw-bg-opacity))}.hover\:bg-\[\#286090\]:hover{--tw-bg-opacity:1;background-color:rgb(40 96 144/var(--tw-bg-opacity))}.hover\:bg-\[\#bc2a8d\]:hover{--tw-bg-opacity:1;background-color:rgb(188 42 141/var(--tw-bg-opacity))}.hover\:bg-\[\#c9302c\]:hover{--tw-bg-opacity:1;background-color:rgb(201 48 44/var(--tw-bg-opacity))}.hover\:bg-\[\#e6e6e6\]:hover{--tw-bg-opacity:1;background-color:rgb(230 230 230/var(--tw-bg-opacity))}.hover\:bg-\[\#e7e6e6\]:hover{--tw-bg-opacity:1;background-color:rgb(231 230 230/var(--tw-bg-opacity))}.hover\:bg-\[\#eee\]:hover{--tw-bg-opacity:1;background-color:rgb(238 238 238/var(--tw-bg-opacity))}.hover\:bg-accent:hover{background-color:hsl(var(--accent))}.hover\:bg-destructive\/90:hover{background-color:hsl(var(--destructive)/.9)}.hover\:bg-gray-100:hover{background-color:var(--color-gray-100)}.hover\:bg-gray-50:hover{background-color:var(--color-gray-50)}.hover\:bg-light-blue:hover{--tw-bg-opacity:1;background-color:rgb(3 126 161/var(--tw-bg-opacity))}.hover\:bg-light-green:hover{--tw-bg-opacity:1;background-color:rgb(61 132 63/var(--tw-bg-opacity))}.hover\:bg-muted:hover{background-color:hsl(var(--muted))}.hover\:bg-muted\/0:hover{background-color:hsl(var(--muted)/0)}.hover\:bg-muted\/50:hover{background-color:hsl(var(--muted)/.5)}.hover\:bg-primary\/90:hover{background-color:hsl(var(--primary)/.9)}.hover\:bg-secondary:hover{background-color:hsl(var(--secondary))}.hover\:bg-secondary\/80:hover{background-color:hsl(var(--secondary)/.8)}.hover\:bg-slate-100:hover{--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity))}.hover\:bg-slate-100\/80:hover{background-color:rgba(241,245,249,.8)}.hover\:bg-slate-900:hover{--tw-bg-opacity:1;background-color:rgb(15 23 42/var(--tw-bg-opacity))}.hover\:bg-slate-900\/80:hover{background-color:rgba(15,23,42,.8)}.hover\:bg-transparent:hover{background-color:transparent}.hover\:fill-white:hover{fill:var(--color-white)}.hover\:text-\[\#23527c\]:hover{--tw-text-opacity:1;color:rgb(35 82 124/var(--tw-text-opacity))}.hover\:text-accent-foreground:hover{color:hsl(var(--accent-foreground))}.hover\:text-blue-700:hover{color:var(--color-blue-700)}.hover\:text-destructive\/90:hover{color:hsl(var(--destructive)/.9)}.hover\:text-foreground:hover{color:hsl(var(--foreground))}.hover\:text-light-green:hover{--tw-text-opacity:1;color:rgb(61 132 63/var(--tw-text-opacity))}.hover\:text-orange-500:hover{color:var(--color-orange-500)}.hover\:text-red-500:hover{color:var(--color-red-500)}.hover\:text-slate-50:hover{--tw-text-opacity:1;color:rgb(248 250 252/var(--tw-text-opacity))}.hover\:text-slate-500:hover{--tw-text-opacity:1;color:rgb(100 116 139/var(--tw-text-opacity))}.hover\:text-slate-900:hover{--tw-text-opacity:1;color:rgb(15 23 42/var(--tw-text-opacity))}.hover\:text-white:hover{color:var(--color-white)}.hover\:underline:hover{text-decoration-line:underline}.hover\:opacity-100:hover{opacity:1}.hover\:shadow-lg:hover{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.hover\:shadow-lg:hover,.hover\:shadow-xl:hover{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.hover\:shadow-xl:hover{--tw-shadow:0 20px 25px -5px rgba(0,0,0,.1),0 8px 10px -6px rgba(0,0,0,.1);--tw-shadow-colored:0 20px 25px -5px var(--tw-shadow-color),0 8px 10px -6px var(--tw-shadow-color)}.hover\:ring-2:hover{--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(2px + 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)}.hover\:ring-light-green:hover{--tw-ring-opacity:1;--tw-ring-color:rgb(61 132 63/var(--tw-ring-opacity))}.hover\:brightness-90:hover{--tw-brightness:brightness(.9);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)}.focus\:border-0:focus{border-width:0}.focus\:border-none:focus{border-style:none}.focus\:border-\[\#FF0000\]:focus{--tw-border-opacity:1;border-color:rgb(255 0 0/var(--tw-border-opacity))}.focus\:bg-accent:focus{background-color:hsl(var(--accent))}.focus\:bg-slate-100:focus{--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity))}.focus\:bg-slate-900:focus{--tw-bg-opacity:1;background-color:rgb(15 23 42/var(--tw-bg-opacity))}.focus\:text-accent-foreground:focus{color:hsl(var(--accent-foreground))}.focus\:text-slate-50:focus{--tw-text-opacity:1;color:rgb(248 250 252/var(--tw-text-opacity))}.focus\:text-slate-900:focus{--tw-text-opacity:1;color:rgb(15 23 42/var(--tw-text-opacity))}.focus\:opacity-100:focus{opacity:1}.focus\:shadow:focus{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.focus\:shadow-\[\#FF0000\]:focus{--tw-shadow-color:red;--tw-shadow:var(--tw-shadow-colored)}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-0:focus{--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(var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring-0:focus,.focus\:ring-2:focus{box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-2:focus{--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(2px + var(--tw-ring-offset-width)) var(--tw-ring-color)}.focus\:ring-\[\#3c763d\]:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(60 118 61/var(--tw-ring-opacity))}.focus\:ring-blue-500:focus{--tw-ring-color:var(--color-blue-500)}.focus\:ring-gray-200:focus{--tw-ring-color:var(--color-gray-200)}.focus\:ring-ring:focus{--tw-ring-color:hsl(var(--ring))}.focus\:ring-slate-400:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(148 163 184/var(--tw-ring-opacity))}.focus\:ring-zinc-400:focus{--tw-ring-color:var(--color-zinc-400)}.focus\:ring-offset-0:focus{--tw-ring-offset-width:0px}.focus\:ring-offset-2:focus{--tw-ring-offset-width:2px}.focus-visible\:outline-none:focus-visible{outline:2px solid transparent;outline-offset:2px}.focus-visible\:ring:focus-visible{--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(3px + 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)}.focus-visible\:ring-2:focus-visible{--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(2px + 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)}.focus-visible\:ring-ring:focus-visible{--tw-ring-color:hsl(var(--ring))}.focus-visible\:ring-slate-400:focus-visible{--tw-ring-opacity:1;--tw-ring-color:rgb(148 163 184/var(--tw-ring-opacity))}.focus-visible\:ring-slate-950:focus-visible{--tw-ring-opacity:1;--tw-ring-color:rgb(2 6 23/var(--tw-ring-opacity))}.focus-visible\:ring-opacity-75:focus-visible{--tw-ring-opacity:0.75}.focus-visible\:ring-offset-2:focus-visible{--tw-ring-offset-width:2px}.focus-visible\:ring-offset-background:focus-visible{--tw-ring-offset-color:hsl(var(--background))}.active\:scale-75:active{--tw-scale-x:.75;--tw-scale-y:.75;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))}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:border-\[\#ccc\]:disabled{--tw-border-opacity:1;border-color:rgb(204 204 204/var(--tw-border-opacity))}.disabled\:bg-\[\#e9e9ed\]:disabled{--tw-bg-opacity:1;background-color:rgb(233 233 237/var(--tw-bg-opacity))}.disabled\:bg-\[\#eee\]:disabled{--tw-bg-opacity:1;background-color:rgb(238 238 238/var(--tw-bg-opacity))}.disabled\:text-\[\#8a8989\]:disabled{--tw-text-opacity:1;color:rgb(138 137 137/var(--tw-text-opacity))}.disabled\:text-muted-foreground:disabled{color:hsl(var(--muted-foreground))}.disabled\:opacity-50:disabled{opacity:.5}.disabled\:opacity-70:disabled{opacity:.7}.disabled\:hover\:cursor-not-allowed:hover:disabled{cursor:not-allowed}.group:hover .group-hover\:top-0{top:0}.group:hover .group-hover\:h-full{height:100%}.group:hover .group-hover\:max-w-fit{max-width:-moz-fit-content;max-width:fit-content}.group:hover .group-hover\:translate-x-4{--tw-translate-x:1rem;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))}.group:hover .group-hover\:overflow-auto{overflow:auto}.group:hover .group-hover\:opacity-100{opacity:1}.group.destructive .group-\[\.destructive\]\:border-destructive\/30,.group.destructive .group-\[\.destructive\]\:hover\:border-destructive\/30:hover{border-color:hsl(var(--destructive)/.3)}.group.destructive .group-\[\.destructive\]\:hover\:bg-destructive:hover{background-color:hsl(var(--destructive))}.group.destructive .group-\[\.destructive\]\:hover\:text-destructive-foreground:hover{color:hsl(var(--destructive-foreground))}.group.destructive .group-\[\.destructive\]\:focus\:ring-destructive:focus{--tw-ring-color:hsl(var(--destructive))}.peer:disabled~.peer-disabled\:cursor-not-allowed{cursor:not-allowed}.peer:disabled~.peer-disabled\:opacity-70{opacity:.7}.aria-selected\:bg-slate-100[aria-selected=true]{--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity))}.aria-selected\:bg-slate-100\/50[aria-selected=true]{background-color:rgba(241,245,249,.5)}.aria-selected\:text-slate-500[aria-selected=true]{--tw-text-opacity:1;color:rgb(100 116 139/var(--tw-text-opacity))}.aria-selected\:text-slate-900[aria-selected=true]{--tw-text-opacity:1;color:rgb(15 23 42/var(--tw-text-opacity))}.aria-selected\:opacity-100[aria-selected=true]{opacity:1}.aria-selected\:opacity-30[aria-selected=true]{opacity:.3}.data-\[disabled\=\'true\'\]\:pointer-events-none[data-disabled=true],.data-\[disabled\=true\]\:pointer-events-none[data-disabled=true],.data-\[disabled\]\:pointer-events-none[data-disabled]{pointer-events:none}.data-\[error\=false\]\:mt-6[data-error=false],.data-\[exposureerror\=false\]\:mt-6[data-exposureerror=false]{margin-top:1.5rem}.data-\[groupiderror\=false\]\:mb-6[data-groupiderror=false],.data-\[mqttconnectoptionserror\=false\]\:mb-6[data-mqttconnectoptionserror=false]{margin-bottom:1.5rem}.data-\[mqttdecodeoptionserror\=false\]\:mt-6[data-mqttdecodeoptionserror=false]{margin-top:1.5rem}.data-\[side\=bottom\]\:translate-y-1[data-side=bottom]{--tw-translate-y:0.25rem}.data-\[side\=bottom\]\:translate-y-1[data-side=bottom],.data-\[side\=left\]\:-translate-x-1[data-side=left]{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))}.data-\[side\=left\]\:-translate-x-1[data-side=left]{--tw-translate-x:-0.25rem}.data-\[side\=right\]\:translate-x-1[data-side=right]{--tw-translate-x:0.25rem}.data-\[side\=right\]\:translate-x-1[data-side=right],.data-\[side\=top\]\:-translate-y-1[data-side=top]{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))}.data-\[side\=top\]\:-translate-y-1[data-side=top]{--tw-translate-y:-0.25rem}.data-\[state\=checked\]\:translate-x-5[data-state=checked]{--tw-translate-x:1.25rem;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))}.data-\[state\=unchecked\]\:translate-x-0[data-state=unchecked],.data-\[swipe\=cancel\]\:translate-x-0[data-swipe=cancel]{--tw-translate-x:0px;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))}.data-\[swipe\=end\]\:translate-x-\[var\(--radix-toast-swipe-end-x\)\][data-swipe=end]{--tw-translate-x:var(--radix-toast-swipe-end-x)}.data-\[swipe\=end\]\:translate-x-\[var\(--radix-toast-swipe-end-x\)\][data-swipe=end],.data-\[swipe\=move\]\:translate-x-\[var\(--radix-toast-swipe-move-x\)\][data-swipe=move]{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))}.data-\[swipe\=move\]\:translate-x-\[var\(--radix-toast-swipe-move-x\)\][data-swipe=move]{--tw-translate-x:var(--radix-toast-swipe-move-x)}@keyframes accordion-up{0%{height:var(--radix-accordion-content-height)}to{height:0}}.data-\[state\=closed\]\:animate-accordion-up[data-state=closed]{animation:accordion-up .2s ease-out}@keyframes accordion-down{0%{height:0}to{height:var(--radix-accordion-content-height)}}.data-\[state\=open\]\:animate-accordion-down[data-state=open]{animation:accordion-down .2s ease-out}.data-\[state\=active\]\:border-dark-green[data-state=active]{--tw-border-opacity:1;border-color:rgb(111 161 97/var(--tw-border-opacity))}.data-\[state\=inactive\]\:border-transparent[data-state=inactive]{border-color:transparent}.data-\[active\=true\]\:bg-light-green[data-active=true]{--tw-bg-opacity:1;background-color:rgb(61 132 63/var(--tw-bg-opacity))}.data-\[state\=checked\]\:bg-primary[data-state=checked]{background-color:hsl(var(--primary))}.data-\[state\=checked\]\:bg-slate-900[data-state=checked]{--tw-bg-opacity:1;background-color:rgb(15 23 42/var(--tw-bg-opacity))}.data-\[state\=on\]\:bg-slate-100[data-state=on]{--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity))}.data-\[state\=open\]\:bg-accent[data-state=open]{background-color:hsl(var(--accent))}.data-\[state\=open\]\:bg-slate-100[data-state=open]{--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity))}.data-\[state\=selected\]\:bg-muted[data-state=selected]{background-color:hsl(var(--muted))}.data-\[state\=unchecked\]\:bg-input[data-state=unchecked]{background-color:hsl(var(--input))}.data-\[active\=true\]\:text-white[data-active=true]{color:var(--color-white)}.data-\[state\=active\]\:text-light-green[data-state=active]{--tw-text-opacity:1;color:rgb(61 132 63/var(--tw-text-opacity))}.data-\[state\=checked\]\:text-slate-50[data-state=checked]{--tw-text-opacity:1;color:rgb(248 250 252/var(--tw-text-opacity))}.data-\[state\=on\]\:text-slate-900[data-state=on]{--tw-text-opacity:1;color:rgb(15 23 42/var(--tw-text-opacity))}.data-\[state\=open\]\:text-muted-foreground[data-state=open]{color:hsl(var(--muted-foreground))}.data-\[disabled\=\'true\'\]\:opacity-50[data-disabled=true],.data-\[disabled\=true\]\:opacity-50[data-disabled=true],.data-\[disabled\]\:opacity-50[data-disabled]{opacity:.5}.data-\[checked\=true\]\:ring-2[data-checked=true]{--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(2px + 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)}.data-\[checked\=true\]\:ring-4[data-checked=true]{--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(4px + 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)}.data-\[checked\=true\]\:ring-light-green[data-checked=true]{--tw-ring-opacity:1;--tw-ring-color:rgb(61 132 63/var(--tw-ring-opacity))}.data-\[swipe\=move\]\:transition-none[data-swipe=move]{transition-property:none}.data-\[state\=closed\]\:duration-300[data-state=closed]{transition-duration:.3s}.data-\[state\=open\]\:duration-500[data-state=open]{transition-duration:.5s}.data-\[state\=open\]\:animate-in[data-state=open]{--tw-enter-opacity:initial;--tw-enter-scale:initial;--tw-enter-rotate:initial;--tw-enter-translate-x:initial;--tw-enter-translate-y:initial;animation-duration:.15s;animation-name:enter}.data-\[state\=closed\]\:animate-out[data-state=closed],.data-\[swipe\=end\]\:animate-out[data-swipe=end]{--tw-exit-opacity:initial;--tw-exit-scale:initial;--tw-exit-rotate:initial;--tw-exit-translate-x:initial;--tw-exit-translate-y:initial;animation-duration:.15s;animation-name:exit}.data-\[state\=closed\]\:fade-out-0[data-state=closed],.data-\[state\=closed\]\:fade-out[data-state=closed]{--tw-exit-opacity:0}.data-\[state\=closed\]\:fade-out-80[data-state=closed]{--tw-exit-opacity:0.8}.data-\[state\=open\]\:fade-in-0[data-state=open],.data-\[state\=open\]\:fade-in[data-state=open]{--tw-enter-opacity:0}.data-\[state\=open\]\:fade-in-90[data-state=open]{--tw-enter-opacity:0.9}.data-\[state\=closed\]\:zoom-out-95[data-state=closed]{--tw-exit-scale:.95}.data-\[state\=open\]\:zoom-in-95[data-state=open]{--tw-enter-scale:.95}.data-\[side\=bottom\]\:slide-in-from-top-1[data-side=bottom]{--tw-enter-translate-y:-0.25rem}.data-\[side\=bottom\]\:slide-in-from-top-2[data-side=bottom]{--tw-enter-translate-y:-0.5rem}.data-\[side\=left\]\:slide-in-from-right-1[data-side=left]{--tw-enter-translate-x:0.25rem}.data-\[side\=left\]\:slide-in-from-right-2[data-side=left]{--tw-enter-translate-x:0.5rem}.data-\[side\=right\]\:slide-in-from-left-1[data-side=right]{--tw-enter-translate-x:-0.25rem}.data-\[side\=right\]\:slide-in-from-left-2[data-side=right]{--tw-enter-translate-x:-0.5rem}.data-\[side\=top\]\:slide-in-from-bottom-1[data-side=top]{--tw-enter-translate-y:0.25rem}.data-\[side\=top\]\:slide-in-from-bottom-2[data-side=top]{--tw-enter-translate-y:0.5rem}.data-\[state\=closed\]\:slide-out-to-bottom[data-state=closed]{--tw-exit-translate-y:100%}.data-\[state\=closed\]\:slide-out-to-left[data-state=closed]{--tw-exit-translate-x:-100%}.data-\[state\=closed\]\:slide-out-to-left-1\/2[data-state=closed]{--tw-exit-translate-x:-50%}.data-\[state\=closed\]\:slide-out-to-right-full[data-state=closed],.data-\[state\=closed\]\:slide-out-to-right[data-state=closed]{--tw-exit-translate-x:100%}.data-\[state\=closed\]\:slide-out-to-top[data-state=closed]{--tw-exit-translate-y:-100%}.data-\[state\=closed\]\:slide-out-to-top-\[48\%\][data-state=closed]{--tw-exit-translate-y:-48%}.data-\[state\=open\]\:slide-in-from-bottom[data-state=open]{--tw-enter-translate-y:100%}.data-\[state\=open\]\:slide-in-from-bottom-10[data-state=open]{--tw-enter-translate-y:2.5rem}.data-\[state\=open\]\:slide-in-from-left[data-state=open]{--tw-enter-translate-x:-100%}.data-\[state\=open\]\:slide-in-from-left-1\/2[data-state=open]{--tw-enter-translate-x:-50%}.data-\[state\=open\]\:slide-in-from-right[data-state=open]{--tw-enter-translate-x:100%}.data-\[state\=open\]\:slide-in-from-top[data-state=open]{--tw-enter-translate-y:-100%}.data-\[state\=open\]\:slide-in-from-top-\[48\%\][data-state=open]{--tw-enter-translate-y:-48%}.data-\[state\=open\]\:slide-in-from-top-full[data-state=open]{--tw-enter-translate-y:-100%}.data-\[state\=closed\]\:duration-300[data-state=closed]{animation-duration:.3s}.data-\[state\=open\]\:duration-500[data-state=open]{animation-duration:.5s}.dark\:border-\[\#ffffff\]:is(.dark *){--tw-border-opacity:1;border-color:rgb(255 255 255/var(--tw-border-opacity))}.dark\:border-blue-500:is(.dark *){border-color:var(--color-blue-500)}.dark\:border-dark-blue:is(.dark *){--tw-border-opacity:1;border-color:rgb(3 134 170/var(--tw-border-opacity))}.dark\:border-dark-green:is(.dark *){--tw-border-opacity:1;border-color:rgb(111 161 97/var(--tw-border-opacity))}.dark\:border-dark-text:is(.dark *){--tw-border-opacity:1;border-color:rgb(210 209 208/var(--tw-border-opacity))}.dark\:border-gray-100:is(.dark *){border-color:var(--color-gray-100)}.dark\:border-gray-300:is(.dark *){border-color:var(--color-gray-300)}.dark\:border-gray-800:is(.dark *){border-color:var(--color-gray-800)}.dark\:border-red-500:is(.dark *){border-color:var(--color-red-500)}.dark\:border-slate-50:is(.dark *){--tw-border-opacity:1;border-color:rgb(248 250 252/var(--tw-border-opacity))}.dark\:border-slate-800:is(.dark *){--tw-border-opacity:1;border-color:rgb(30 41 59/var(--tw-border-opacity))}.dark\:border-white:is(.dark *){border-color:var(--color-white)}.dark\:bg-black:is(.dark *){background-color:var(--color-black)}.dark\:bg-dark-background:is(.dark *){--tw-bg-opacity:1;background-color:rgb(36 36 36/var(--tw-bg-opacity))}.dark\:bg-dark-blue:is(.dark *){--tw-bg-opacity:1;background-color:rgb(3 134 170/var(--tw-bg-opacity))}.dark\:bg-dark-boxes:is(.dark *){--tw-bg-opacity:1;background-color:rgb(59 58 58/var(--tw-bg-opacity))}.dark\:bg-dark-green:is(.dark *){--tw-bg-opacity:1;background-color:rgb(111 161 97/var(--tw-bg-opacity))}.dark\:bg-dark-text:is(.dark *){--tw-bg-opacity:1;background-color:rgb(210 209 208/var(--tw-bg-opacity))}.dark\:bg-gray-700:is(.dark *){background-color:var(--color-gray-700)}.dark\:bg-gray-800:is(.dark *){background-color:var(--color-gray-800)}.dark\:bg-gray-900:is(.dark *){background-color:var(--color-gray-900)}.dark\:bg-green-900:is(.dark *){background-color:var(--color-green-900)}.dark\:bg-green-950:is(.dark *){background-color:var(--color-green-950)}.dark\:bg-slate-50:is(.dark *){--tw-bg-opacity:1;background-color:rgb(248 250 252/var(--tw-bg-opacity))}.dark\:bg-slate-800:is(.dark *){--tw-bg-opacity:1;background-color:rgb(30 41 59/var(--tw-bg-opacity))}.dark\:bg-slate-950:is(.dark *){--tw-bg-opacity:1;background-color:rgb(2 6 23/var(--tw-bg-opacity))}.dark\:bg-slate-950\/80:is(.dark *){background-color:rgba(2,6,23,.8)}.dark\:bg-transparent:is(.dark *){background-color:transparent}.dark\:bg-zinc-800:is(.dark *){background-color:var(--color-zinc-800)}.dark\:dark\:text-zinc-100:is(.dark *):is(.dark *){color:var(--color-zinc-100)}.dark\:text-dark-boxes:is(.dark *){--tw-text-opacity:1;color:rgb(59 58 58/var(--tw-text-opacity))}.dark\:text-dark-green:is(.dark *){--tw-text-opacity:1;color:rgb(111 161 97/var(--tw-text-opacity))}.dark\:text-dark-text:is(.dark *){--tw-text-opacity:1;color:rgb(210 209 208/var(--tw-text-opacity))}.dark\:text-gray-100:is(.dark *){color:var(--color-gray-100)}.dark\:text-gray-300:is(.dark *){color:var(--color-gray-300)}.dark\:text-gray-400:is(.dark *){color:var(--color-gray-400)}.dark\:text-green-200:is(.dark *){color:var(--color-green-200)}.dark\:text-green-300:is(.dark *){color:var(--color-green-300)}.dark\:text-light-green:is(.dark *){--tw-text-opacity:1;color:rgb(61 132 63/var(--tw-text-opacity))}.dark\:text-slate-400:is(.dark *){--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity))}.dark\:text-slate-50:is(.dark *){--tw-text-opacity:1;color:rgb(248 250 252/var(--tw-text-opacity))}.dark\:text-slate-900:is(.dark *){--tw-text-opacity:1;color:rgb(15 23 42/var(--tw-text-opacity))}.dark\:text-white:is(.dark *){color:var(--color-white)}.dark\:text-zinc-200:is(.dark *){color:var(--color-zinc-200)}.dark\:text-zinc-50:is(.dark *){color:var(--color-zinc-50)}.dark\:opacity-90:is(.dark *){opacity:.9}.dark\:opacity-95:is(.dark *){opacity:.95}.dark\:shadow-none:is(.dark *){--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.dark\:ring-white:is(.dark *){--tw-ring-color:var(--color-white)}.dark\:ring-offset-slate-950:is(.dark *){--tw-ring-offset-color:#020617}.dark\:backdrop-blur-sm:is(.dark *){--tw-backdrop-blur:blur(4px);-webkit-backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);backdrop-filter:var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia)}.dark\:placeholder\:text-slate-400:is(.dark *)::-moz-placeholder{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity))}.dark\:placeholder\:text-slate-400:is(.dark *)::placeholder{--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity))}.dark\:hover\:border-dark-green:hover:is(.dark *){--tw-border-opacity:1;border-color:rgb(111 161 97/var(--tw-border-opacity))}.dark\:hover\:bg-slate-50:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgb(248 250 252/var(--tw-bg-opacity))}.dark\:hover\:bg-slate-50\/80:hover:is(.dark *){background-color:rgba(248,250,252,.8)}.dark\:hover\:bg-slate-800:hover:is(.dark *){--tw-bg-opacity:1;background-color:rgb(30 41 59/var(--tw-bg-opacity))}.dark\:hover\:bg-slate-800\/80:hover:is(.dark *){background-color:rgba(30,41,59,.8)}.dark\:hover\:text-dark-green:hover:is(.dark *){--tw-text-opacity:1;color:rgb(111 161 97/var(--tw-text-opacity))}.dark\:hover\:text-green-200:hover:is(.dark *){color:var(--color-green-200)}.dark\:hover\:text-slate-400:hover:is(.dark *){--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity))}.dark\:hover\:text-slate-50:hover:is(.dark *){--tw-text-opacity:1;color:rgb(248 250 252/var(--tw-text-opacity))}.dark\:hover\:text-slate-900:hover:is(.dark *){--tw-text-opacity:1;color:rgb(15 23 42/var(--tw-text-opacity))}.dark\:focus\:bg-slate-50:focus:is(.dark *){--tw-bg-opacity:1;background-color:rgb(248 250 252/var(--tw-bg-opacity))}.dark\:focus\:bg-slate-800:focus:is(.dark *){--tw-bg-opacity:1;background-color:rgb(30 41 59/var(--tw-bg-opacity))}.dark\:focus\:text-slate-50:focus:is(.dark *){--tw-text-opacity:1;color:rgb(248 250 252/var(--tw-text-opacity))}.dark\:focus\:text-slate-900:focus:is(.dark *){--tw-text-opacity:1;color:rgb(15 23 42/var(--tw-text-opacity))}.dark\:focus\:ring-slate-800:focus:is(.dark *){--tw-ring-opacity:1;--tw-ring-color:rgb(30 41 59/var(--tw-ring-opacity))}.dark\:focus\:ring-zinc-800:focus:is(.dark *){--tw-ring-color:var(--color-zinc-800)}.dark\:focus-visible\:ring-slate-300:focus-visible:is(.dark *){--tw-ring-opacity:1;--tw-ring-color:rgb(203 213 225/var(--tw-ring-opacity))}.dark\:focus-visible\:ring-slate-800:focus-visible:is(.dark *){--tw-ring-opacity:1;--tw-ring-color:rgb(30 41 59/var(--tw-ring-opacity))}.dark\:aria-selected\:bg-slate-800[aria-selected=true]:is(.dark *){--tw-bg-opacity:1;background-color:rgb(30 41 59/var(--tw-bg-opacity))}.dark\:aria-selected\:bg-slate-800\/50[aria-selected=true]:is(.dark *){background-color:rgba(30,41,59,.5)}.dark\:aria-selected\:text-slate-400[aria-selected=true]:is(.dark *){--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity))}.dark\:aria-selected\:text-slate-50[aria-selected=true]:is(.dark *){--tw-text-opacity:1;color:rgb(248 250 252/var(--tw-text-opacity))}.dark\:data-\[state\=checked\]\:bg-dark-green[data-state=checked]:is(.dark *){--tw-bg-opacity:1;background-color:rgb(111 161 97/var(--tw-bg-opacity))}.dark\:data-\[state\=checked\]\:bg-slate-50[data-state=checked]:is(.dark *){--tw-bg-opacity:1;background-color:rgb(248 250 252/var(--tw-bg-opacity))}.dark\:data-\[state\=on\]\:bg-slate-800[data-state=on]:is(.dark *){--tw-bg-opacity:1;background-color:rgb(30 41 59/var(--tw-bg-opacity))}.dark\:data-\[state\=open\]\:bg-slate-800[data-state=open]:is(.dark *){--tw-bg-opacity:1;background-color:rgb(30 41 59/var(--tw-bg-opacity))}.dark\:data-\[state\=active\]\:text-dark-green[data-state=active]:is(.dark *){--tw-text-opacity:1;color:rgb(111 161 97/var(--tw-text-opacity))}.dark\:data-\[state\=checked\]\:text-slate-900[data-state=checked]:is(.dark *){--tw-text-opacity:1;color:rgb(15 23 42/var(--tw-text-opacity))}.dark\:data-\[state\=on\]\:text-slate-50[data-state=on]:is(.dark *){--tw-text-opacity:1;color:rgb(248 250 252/var(--tw-text-opacity))}.dark\:data-\[checked\=true\]\:ring-dark-green[data-checked=true]:is(.dark *){--tw-ring-opacity:1;--tw-ring-color:rgb(111 161 97/var(--tw-ring-opacity))}@media not all and (min-width:768px){.max-md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width:640px){.sm\:bottom-0{bottom:0}.sm\:bottom-\[30px\]{bottom:30px}.sm\:left-\[10px\]{left:10px}.sm\:right-0{right:0}.sm\:top-16{top:4rem}.sm\:top-auto{top:auto}.sm\:col-span-2{grid-column:span 2/span 2}.sm\:col-span-8{grid-column:span 8/span 8}.sm\:mx-0{margin-left:0;margin-right:0}.sm\:mx-auto{margin-left:auto;margin-right:auto}.sm\:ml-4{margin-left:1rem}.sm\:mt-0{margin-top:0}.sm\:mt-20{margin-top:5rem}.sm\:mt-5{margin-top:1.25rem}.sm\:mt-px{margin-top:1px}.sm\:flex{display:flex}.sm\:grid{display:grid}.sm\:h-9{height:2.25rem}.sm\:max-h-\[calc\(100vh-8rem\)\]{max-height:calc(100vh - 8rem)}.sm\:w-1\/3{width:33.333333%}.sm\:max-w-lg{max-width:32rem}.sm\:max-w-sm{max-width:24rem}.sm\:max-w-xl{max-width:36rem}.sm\:translate-x-1\/2{--tw-translate-x:50%;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))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:flex-col{flex-direction:column}.sm\:items-start{align-items:flex-start}.sm\:items-center{align-items:center}.sm\:justify-end{justify-content:flex-end}.sm\:gap-4{gap:1rem}.sm\:gap-6{gap:1.5rem}.sm\:space-x-12>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(3rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(3rem*var(--tw-space-x-reverse))}.sm\:space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(.5rem*var(--tw-space-x-reverse))}.sm\:space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(1rem*var(--tw-space-x-reverse))}.sm\:space-y-0>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(0px*var(--tw-space-y-reverse));margin-top:calc(0px*(1 - var(--tw-space-y-reverse)))}.sm\:space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1.25rem*var(--tw-space-y-reverse));margin-top:calc(1.25rem*(1 - var(--tw-space-y-reverse)))}.sm\:rounded-lg{border-radius:var(--radius)}.sm\:border-t{border-top-width:1px}.sm\:border-gray-200{border-color:var(--color-gray-200)}.sm\:p-4{padding:1rem}.sm\:p-6{padding:1.5rem}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:py-20{padding-bottom:5rem;padding-top:5rem}.sm\:pt-10{padding-top:2.5rem}.sm\:pt-2{padding-top:.5rem}.sm\:pt-20{padding-top:5rem}.sm\:pt-5{padding-top:1.25rem}.sm\:text-left{text-align:left}.sm\:text-center{text-align:center}.sm\:text-base{font-size:1rem;line-height:1.5rem}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}.sm\:text-gray-700{color:var(--color-gray-700)}.sm\:zoom-in-90{--tw-enter-scale:.9}.data-\[state\=open\]\:sm\:slide-in-from-bottom-0[data-state=open]{--tw-enter-translate-y:0px}.data-\[state\=open\]\:sm\:slide-in-from-bottom-full[data-state=open]{--tw-enter-translate-y:100%}}@media (min-width:768px){.md\:bottom-\[30px\]{bottom:30px}.md\:bottom-auto{bottom:auto}.md\:left-\[10px\]{left:10px}.md\:left-auto{left:auto}.md\:right-4{right:1rem}.md\:top-4{top:1rem}.md\:top-auto{top:auto}.md\:order-2{order:2}.md\:col-span-2{grid-column:span 2/span 2}.md\:col-span-6{grid-column:span 6/span 6}.md\:m-10{margin:2.5rem}.md\:mt-0{margin-top:0}.md\:block{display:block}.md\:flex{display:flex}.md\:h-6{height:1.5rem}.md\:h-\[35\%\]{height:35%}.md\:h-full{height:100%}.md\:max-h-\[35\%\]{max-height:35%}.md\:max-h-\[calc\(100vh-8rem\)\]{max-height:calc(100vh - 8rem)}.md\:w-1\/2{width:50%}.md\:w-1\/3{width:33.333333%}.md\:w-2\/3{width:66.666667%}.md\:w-6{width:1.5rem}.md\:w-64{width:16rem}.md\:w-\[60vw\]{width:60vw}.md\:w-full{width:100%}.md\:max-w-\[420px\]{max-width:420px}.md\:max-w-full{max-width:100%}.md\:flex-1{flex:1 1 0%}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:flex-col{flex-direction:column}.md\:items-center{align-items:center}.md\:justify-between{justify-content:space-between}.md\:gap-4{gap:1rem}.md\:gap-8{gap:2rem}.md\:space-x-8>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(2rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(2rem*var(--tw-space-x-reverse))}.md\:p-0{padding:0}.md\:px-24{padding-left:6rem;padding-right:6rem}.md\:px-3{padding-left:.75rem;padding-right:.75rem}.md\:px-4{padding-left:1rem;padding-right:1rem}.md\:px-8{padding-left:2rem;padding-right:2rem}.md\:py-16{padding-bottom:4rem;padding-top:4rem}.md\:py-3{padding-bottom:.75rem;padding-top:.75rem}.md\:py-8{padding-bottom:2rem;padding-top:2rem}.md\:pr-10{padding-right:2.5rem}.md\:pr-4{padding-right:1rem}.md\:pt-4{padding-top:1rem}.md\:pt-6{padding-top:1.5rem}.md\:text-lg{font-size:1.125rem;line-height:1.75rem}.md\:font-thin{font-weight:100}.md\:hover\:text-light-green:hover{--tw-text-opacity:1;color:rgb(61 132 63/var(--tw-text-opacity))}.dark\:md\:hover\:text-green-200:hover:is(.dark *){color:var(--color-green-200)}}@media (min-width:1024px){.lg\:absolute{position:absolute}.lg\:-inset-x-10{left:-2.5rem;right:-2.5rem}.lg\:-bottom-20{bottom:-5rem}.lg\:-top-10{top:-2.5rem}.lg\:-top-16{top:-4rem}.lg\:order-1{order:1}.lg\:col-span-5{grid-column:span 5/span 5}.lg\:col-span-7{grid-column:span 7/span 7}.lg\:row-span-2{grid-row:span 2/span 2}.lg\:my-8{margin-bottom:2rem;margin-top:2rem}.lg\:mr-12{margin-right:3rem}.lg\:mt-0{margin-top:0}.lg\:flex{display:flex}.lg\:grid{display:grid}.lg\:hidden{display:none}.lg\:h-7{height:1.75rem}.lg\:h-auto{height:auto}.lg\:w-1\/5{width:20%}.lg\:w-7{width:1.75rem}.lg\:w-auto{width:auto}.lg\:max-w-none{max-width:none}.lg\:max-w-screen-xl{max-width:1280px}.lg\:grid-cols-12{grid-template-columns:repeat(12,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:grid-cols-8{grid-template-columns:repeat(8,minmax(0,1fr))}.lg\:grid-rows-1{grid-template-rows:repeat(1,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:flex-col{flex-direction:column}.lg\:gap-x-8{-moz-column-gap:2rem;column-gap:2rem}.lg\:gap-y-20{row-gap:5rem}.lg\:space-x-0>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(0px*(1 - var(--tw-space-x-reverse)));margin-right:calc(0px*var(--tw-space-x-reverse))}.lg\:space-x-12>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-left:calc(3rem*(1 - var(--tw-space-x-reverse)));margin-right:calc(3rem*var(--tw-space-x-reverse))}.lg\:space-y-0>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(0px*var(--tw-space-y-reverse));margin-top:calc(0px*(1 - var(--tw-space-y-reverse)))}.lg\:space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(.25rem*var(--tw-space-y-reverse));margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)))}.lg\:px-0{padding-left:0;padding-right:0}.lg\:px-8{padding-left:2rem;padding-right:2rem}.lg\:py-20{padding-bottom:5rem;padding-top:5rem}.lg\:py-24{padding-bottom:6rem;padding-top:6rem}.lg\:pt-10{padding-top:2.5rem}.lg\:pt-6{padding-top:1.5rem}.lg\:text-lg{font-size:1.125rem;line-height:1.75rem}}@media (min-width:1280px){.xl\:-bottom-32{bottom:-8rem}.xl\:-top-14{top:-3.5rem}.xl\:col-span-6{grid-column:span 6/span 6}.xl\:mr-0{margin-right:0}}@media (min-width:1536px){.\32xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}.\[\&\:has\(\[aria-selected\]\)\]\:bg-slate-100:has([aria-selected]){--tw-bg-opacity:1;background-color:rgb(241 245 249/var(--tw-bg-opacity))}.first\:\[\&\:has\(\[aria-selected\]\)\]\:rounded-l-md:has([aria-selected]):first-child{border-bottom-left-radius:calc(var(--radius) - 2px);border-top-left-radius:calc(var(--radius) - 2px)}.last\:\[\&\:has\(\[aria-selected\]\)\]\:rounded-r-md:has([aria-selected]):last-child{border-bottom-right-radius:calc(var(--radius) - 2px);border-top-right-radius:calc(var(--radius) - 2px)}.dark\:\[\&\:has\(\[aria-selected\]\)\]\:bg-slate-800:has([aria-selected]):is(.dark *){--tw-bg-opacity:1;background-color:rgb(30 41 59/var(--tw-bg-opacity))}.\[\&\:has\(\[aria-selected\]\.day-outside\)\]\:bg-slate-100\/50:has([aria-selected].day-outside){background-color:rgba(241,245,249,.5)}.dark\:\[\&\:has\(\[aria-selected\]\.day-outside\)\]\:bg-slate-800\/50:has([aria-selected].day-outside):is(.dark *){background-color:rgba(30,41,59,.5)}.\[\&\:has\(\[aria-selected\]\.day-range-end\)\]\:rounded-r-md:has([aria-selected].day-range-end){border-bottom-right-radius:calc(var(--radius) - 2px);border-top-right-radius:calc(var(--radius) - 2px)}.\[\&\:has\(\[role\=checkbox\]\)\]\:pr-0:has([role=checkbox]){padding-right:0}.\[\&\:has\(svg\)\]\:pl-11:has(svg){padding-left:2.75rem}.\[\&\>svg\+div\]\:translate-y-\[-3px\]>svg+div{--tw-translate-y:-3px;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))}.\[\&\>svg\]\:absolute>svg{position:absolute}.\[\&\>svg\]\:left-4>svg{left:1rem}.\[\&\>svg\]\:top-4>svg{top:1rem}.\[\&\>svg\]\:text-red-500>svg{color:var(--color-red-500)}.\[\&\>svg\]\:text-slate-950>svg{--tw-text-opacity:1;color:rgb(2 6 23/var(--tw-text-opacity))}.dark\:\[\&\>svg\]\:text-slate-50>svg:is(.dark *){--tw-text-opacity:1;color:rgb(248 250 252/var(--tw-text-opacity))}.\[\&\[data-state\=open\]\>svg\]\:rotate-180[data-state=open]>svg{--tw-rotate:180deg;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))}.\[\&_\[cmdk-group-heading\]\]\:px-2 [cmdk-group-heading]{padding-left:.5rem;padding-right:.5rem}.\[\&_\[cmdk-group-heading\]\]\:py-1\.5 [cmdk-group-heading]{padding-bottom:.375rem;padding-top:.375rem}.\[\&_\[cmdk-group-heading\]\]\:text-xs [cmdk-group-heading]{font-size:.75rem;line-height:1rem}.\[\&_\[cmdk-group-heading\]\]\:font-medium [cmdk-group-heading]{font-weight:500}.\[\&_\[cmdk-group-heading\]\]\:text-slate-500 [cmdk-group-heading]{--tw-text-opacity:1;color:rgb(100 116 139/var(--tw-text-opacity))}.dark\:\[\&_\[cmdk-group-heading\]\]\:text-slate-400 [cmdk-group-heading]:is(.dark *){--tw-text-opacity:1;color:rgb(148 163 184/var(--tw-text-opacity))}.\[\&_\[cmdk-group\]\:not\(\[hidden\]\)_\~\[cmdk-group\]\]\:pt-0 [cmdk-group]:not([hidden])~[cmdk-group]{padding-top:0}.\[\&_\[cmdk-group\]\]\:px-2 [cmdk-group]{padding-left:.5rem;padding-right:.5rem}.\[\&_\[cmdk-input-wrapper\]_svg\]\:h-5 [cmdk-input-wrapper] svg{height:1.25rem}.\[\&_\[cmdk-input-wrapper\]_svg\]\:w-5 [cmdk-input-wrapper] svg{width:1.25rem}.\[\&_\[cmdk-input\]\]\:h-12 [cmdk-input]{height:3rem}.\[\&_\[cmdk-item\]\]\:px-2 [cmdk-item]{padding-left:.5rem;padding-right:.5rem}.\[\&_\[cmdk-item\]\]\:py-3 [cmdk-item]{padding-bottom:.75rem;padding-top:.75rem}.\[\&_\[cmdk-item\]_svg\]\:h-5 [cmdk-item] svg{height:1.25rem}.\[\&_\[cmdk-item\]_svg\]\:w-5 [cmdk-item] svg{width:1.25rem}.\[\&_p\]\:leading-relaxed p{line-height:1.625}.\[\&_tr\:last-child\]\:border-0 tr:last-child{border-width:0}.\[\&_tr\]\:border-b tr{border-bottom-width:1px} \ No newline at end of file diff --git a/app/components/device-detail/device-detail-box.tsx b/app/components/device-detail/device-detail-box.tsx index 7b810263..e4f8b5b7 100644 --- a/app/components/device-detail/device-detail-box.tsx +++ b/app/components/device-detail/device-detail-box.tsx @@ -1,675 +1,688 @@ -import clsx from "clsx"; -import { format, formatDistanceToNow } from "date-fns"; +import clsx from 'clsx' +import { format, formatDistanceToNow } from 'date-fns' import { - ChevronUp, - Minus, - Share2, - XSquare, - EllipsisVertical, - X, - ExternalLink, - Scale, - Archive, - Cpu, - Rss, - CalendarPlus, - Hash, - LandPlot, - Image as ImageIcon, -} from "lucide-react"; -import { Fragment, useEffect, useRef, useState } from "react"; -import { isTablet, isBrowser } from "react-device-detect"; -import Draggable, { type DraggableData } from "react-draggable"; + ChevronUp, + Minus, + Share2, + XSquare, + EllipsisVertical, + X, + ExternalLink, + Scale, + Archive, + Cpu, + Rss, + CalendarPlus, + Hash, + LandPlot, + Image as ImageIcon, +} from 'lucide-react' +import { Fragment, useEffect, useRef, useState } from 'react' +import { isTablet, isBrowser } from 'react-device-detect' +import Draggable, { type DraggableData } from 'react-draggable' import { - useLoaderData, - useMatches, - useNavigate, - useNavigation, - useParams, - useSearchParams, - Link, -} from "react-router"; -import SensorIcon from "../sensor-icon"; -import Spinner from "../spinner"; + useLoaderData, + useMatches, + useNavigate, + useNavigation, + useParams, + useSearchParams, + Link, +} from 'react-router' +import SensorIcon from '../sensor-icon' +import Spinner from '../spinner' import { - Accordion, - AccordionContent, - AccordionItem, - AccordionTrigger, -} from "../ui/accordion"; -import { Alert, AlertDescription, AlertTitle } from "../ui/alert"; + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from '../ui/accordion' +import { Alert, AlertDescription, AlertTitle } from '../ui/alert' import { - AlertDialog, - AlertDialogCancel, - AlertDialogContent, - AlertDialogFooter, - AlertDialogHeader, - AlertDialogTitle, - AlertDialogTrigger, -} from "../ui/alert-dialog"; -import { Badge } from "../ui/badge"; -import { Button } from "../ui/button"; + AlertDialog, + AlertDialogCancel, + AlertDialogContent, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from '../ui/alert-dialog' +import { Badge } from '../ui/badge' +import { Button } from '../ui/button' import { - Card, - CardContent, - CardFooter, - CardHeader, - CardTitle, -} from "../ui/card"; + Card, + CardContent, + CardFooter, + CardHeader, + CardTitle, +} from '../ui/card' import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuLabel, - DropdownMenuSeparator, - DropdownMenuTrigger, -} from "../ui/dropdown-menu"; -import { Separator } from "../ui/separator"; + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from '../ui/dropdown-menu' +import { Separator } from '../ui/separator' import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "../ui/tooltip"; -import { useToast } from "../ui/use-toast"; -import EntryLogs from "./entry-logs"; -import ShareLink from "./share-link"; -import { useGlobalCompareMode } from "./useGlobalCompareMode"; -import { type loader } from "~/routes/explore.$deviceId"; -import { type SensorWithLatestMeasurement } from "~/schema"; -import { getArchiveLink } from "~/utils/device"; + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '../ui/tooltip' +import { useToast } from '../ui/use-toast' +import EntryLogs from './entry-logs' +import ShareLink from './share-link' +import { useGlobalCompareMode } from './useGlobalCompareMode' +import { type loader } from '~/routes/explore.$deviceId' +import { type SensorWithLatestMeasurement } from '~/schema' +import { getArchiveLink } from '~/utils/device' export interface MeasurementProps { - sensorId: string; - time: Date; - value: string; - min_value: string; - max_value: string; + sensorId: string + time: Date + value: string + min_value: string + max_value: string } export default function DeviceDetailBox() { - const navigation = useNavigation(); - const navigate = useNavigate(); - const matches = useMatches(); - const { toast } = useToast(); + const navigation = useNavigation() + const navigate = useNavigate() + const matches = useMatches() + const { toast } = useToast() - const sensorIds = new Set(); + const sensorIds = new Set() - const data = useLoaderData(); - const nodeRef = useRef(null); - // state variables - const [open, setOpen] = useState(true); - const [offsetPositionX, setOffsetPositionX] = useState(0); - const [offsetPositionY, setOffsetPositionY] = useState(0); - const [compareMode, setCompareMode] = useGlobalCompareMode(); - const [refreshOn] = useState(false); - const [refreshSecond, setRefreshSecond] = useState(59); + const data = useLoaderData() + const nodeRef = useRef(null) + // state variables + const [open, setOpen] = useState(true) + const [offsetPositionX, setOffsetPositionX] = useState(0) + const [offsetPositionY, setOffsetPositionY] = useState(0) + const [compareMode, setCompareMode] = useGlobalCompareMode() + const [refreshOn] = useState(false) + const [refreshSecond, setRefreshSecond] = useState(59) - const [sensors, setSensors] = useState(); - useEffect(() => { - const sortedSensors = [...(data.sensors as any)].sort( - (a, b) => (a.id as unknown as number) - (b.id as unknown as number) - ); - setSensors(sortedSensors); - }, [data]); + const [sensors, setSensors] = useState() + useEffect(() => { + const sortedSensors = [...(data.sensors as any)].sort( + (a, b) => (a.id as unknown as number) - (b.id as unknown as number), + ) + setSensors(sortedSensors) + }, [data]) - const [searchParams] = useSearchParams(); + const [searchParams] = useSearchParams() - const { deviceId } = useParams(); // Get the deviceId from the URL params + const { deviceId } = useParams() // Get the deviceId from the URL params - const createSensorLink = (sensorIdToBeSelected: string) => { - const lastSegment = matches[matches.length - 1]?.params?.["*"]; - if (lastSegment) { - const secondLastSegment = matches[matches.length - 2]?.params?.sensorId; - sensorIds.add(secondLastSegment); - sensorIds.add(lastSegment); - } else { - const lastSegment = matches[matches.length - 1]?.params?.sensorId; - if (lastSegment) { - sensorIds.add(lastSegment); - } - } + const createSensorLink = (sensorIdToBeSelected: string) => { + const lastSegment = matches[matches.length - 1]?.params?.['*'] + if (lastSegment) { + const secondLastSegment = matches[matches.length - 2]?.params?.sensorId + sensorIds.add(secondLastSegment) + sensorIds.add(lastSegment) + } else { + const lastSegment = matches[matches.length - 1]?.params?.sensorId + if (lastSegment) { + sensorIds.add(lastSegment) + } + } - // If sensorIdToBeSelected is second selected sensor - if (sensorIds.has(sensorIdToBeSelected) && sensorIds.size === 2) { - const clonedSet = new Set(sensorIds); - clonedSet.delete(sensorIdToBeSelected); - return `/explore/${deviceId}/${Array.from(clonedSet).join("/")}?${searchParams.toString()}`; - } else if (sensorIds.has(sensorIdToBeSelected) && sensorIds.size === 1) { - return `/explore/${deviceId}?${searchParams.toString()}`; - } else if (sensorIds.size === 0) { - return `/explore/${deviceId}/${sensorIdToBeSelected}?${searchParams.toString()}`; - } else if (sensorIds.size === 1) { - return `/explore/${deviceId}/${Array.from(sensorIds).join("/")}/${sensorIdToBeSelected}?${searchParams.toString()}`; - } + // If sensorIdToBeSelected is second selected sensor + if (sensorIds.has(sensorIdToBeSelected) && sensorIds.size === 2) { + const clonedSet = new Set(sensorIds) + clonedSet.delete(sensorIdToBeSelected) + return `/explore/${deviceId}/${Array.from(clonedSet).join('/')}?${searchParams.toString()}` + } else if (sensorIds.has(sensorIdToBeSelected) && sensorIds.size === 1) { + return `/explore/${deviceId}?${searchParams.toString()}` + } else if (sensorIds.size === 0) { + return `/explore/${deviceId}/${sensorIdToBeSelected}?${searchParams.toString()}` + } else if (sensorIds.size === 1) { + return `/explore/${deviceId}/${Array.from(sensorIds).join('/')}/${sensorIdToBeSelected}?${searchParams.toString()}` + } - return ""; - }; + return '' + } - const isSensorActive = (sensorId: string) => { - if (sensorIds.has(sensorId)) { - return "bg-green-100 dark:bg-dark-green"; - } + const isSensorActive = (sensorId: string) => { + if (sensorIds.has(sensorId)) { + return 'bg-green-100 dark:bg-dark-green' + } - return "hover:bg-muted"; - }; + return 'hover:bg-muted' + } - function handleDrag(_e: any, data: DraggableData) { - setOffsetPositionX(data.x); - setOffsetPositionY(data.y); - } + function handleDrag(_e: any, data: DraggableData) { + setOffsetPositionX(data.x) + setOffsetPositionY(data.y) + } - const addLineBreaks = (text: string) => - text.split("\\n").map((text, index) => ( - - {text} -
-
- )); + const addLineBreaks = (text: string) => + text.split('\\n').map((text, index) => ( + + {text} +
+
+ )) - useEffect(() => { - let interval: any = null; - if (refreshOn) { - if (refreshSecond == 0) { - setRefreshSecond(59); - } - interval = setInterval(() => { - setRefreshSecond((refreshSecond) => refreshSecond - 1); - }, 1000); - } else if (!refreshOn) { - clearInterval(interval); - } - return () => clearInterval(interval); - }, [refreshOn, refreshSecond]); + useEffect(() => { + let interval: any = null + if (refreshOn) { + if (refreshSecond == 0) { + setRefreshSecond(59) + } + interval = setInterval(() => { + setRefreshSecond((refreshSecond) => refreshSecond - 1) + }, 1000) + } else if (!refreshOn) { + clearInterval(interval) + } + return () => clearInterval(interval) + }, [refreshOn, refreshSecond]) - if (!data.device) return null; + if (!data.device) return null - return ( - <> - {open && ( - } - defaultPosition={{ x: offsetPositionX, y: offsetPositionY }} - onDrag={handleDrag} - bounds="#osem" - handle="#deviceDetailBoxTop" - disabled={!isBrowser && !isTablet} - > -
-
- {navigation.state === "loading" && ( -
- -
- )} -
-
-
- {data.device.name} -
- - - - - - - Share this link - - - - Close - - - - - - - - - Actions - - - - Compare - - - - - - Archive - - - - - - - - External Link - - - - - + return ( + <> + {open && ( + } + defaultPosition={{ x: offsetPositionX, y: offsetPositionY }} + onDrag={handleDrag} + bounds="#osem" + handle="#deviceDetailBoxTop" + disabled={!isBrowser && !isTablet} + > +
+
+ {navigation.state === 'loading' && ( +
+ +
+ )} +
+
+
+ {data.device.name} +
+ + + + + + + Share this link + + + + Close + + + + + + + + + Actions + + + + Compare + + + + + + Archive + + + + + + + + External Link + + + + + - setOpen(false)} - /> - { - void navigate({ - pathname: "/explore", - search: searchParams.toString(), - }); - }} - /> -
-
-
-
- {data.device.image ? ( - device_image - ) : ( -
- -
- )} -
-
- - - - - - - {data.device.expiresAt && ( - <> - - - - )} -
-
- {data.device.tags && data.device.tags.length > 0 && ( -
-
-
- Tags -
-
- -
- {data.device.tags.map((tag: string) => ( - { - event.stopPropagation(); + setOpen(false)} + /> + { + void navigate({ + pathname: '/explore', + search: searchParams.toString(), + }) + }} + /> +
+
+
+
+ {data.device.image ? ( + device_image + ) : ( +
+ +
+ )} +
+
+ + + + + + + {data.device.expiresAt && ( + <> + + + + )} +
+
+ {data.device.tags && data.device.tags.length > 0 && ( +
+
+
+ Tags +
+
+ +
+ {data.device.tags.map((tag: string) => ( + { + event.stopPropagation() - const currentParams = new URLSearchParams( - searchParams.toString() - ); + const currentParams = new URLSearchParams( + searchParams.toString(), + ) - // Safely retrieve and parse the current tags - const currentTags = - currentParams.get("tags")?.split(",") || []; + // Safely retrieve and parse the current tags + const currentTags = + currentParams.get('tags')?.split(',') || [] - // Toggle the tag in the list - const updatedTags = currentTags.includes(tag) - ? currentTags.filter((t) => t !== tag) // Remove if already present - : [...currentTags, tag]; // Add if not present + // Toggle the tag in the list + const updatedTags = currentTags.includes(tag) + ? currentTags.filter((t) => t !== tag) // Remove if already present + : [...currentTags, tag] // Add if not present - // Update the tags parameter or remove it if empty - if (updatedTags.length > 0) { - currentParams.set( - "tags", - updatedTags.join(",") - ); - } else { - currentParams.delete("tags"); - } + // Update the tags parameter or remove it if empty + if (updatedTags.length > 0) { + currentParams.set( + 'tags', + updatedTags.join(','), + ) + } else { + currentParams.delete('tags') + } - // Update the URL with the new search params - void navigate({ - search: currentParams.toString(), - }); - }} - > - {tag} - - ))} -
-
-
-
- )} - - {data.device.logEntries.length > 0 && ( - <> - - - - )} - {data.device.description && ( - - - - Description - - - {addLineBreaks(data.device.description)} - - - - )} - - - - Sensors - - -
-
- {sensors && - sensors.map( - (sensor: SensorWithLatestMeasurement) => { - const sensorLink = createSensorLink(sensor.id); - if (sensorLink === "") { - return ( - - toast({ - title: - "Cant select more than 2 sensors", - description: - "Deselect one sensor to select another", - variant: "destructive", - }) - } - > - - - ); - } - return ( - - - - - - ); - } - )} -
-
-
-
-
-
-
-
- - )} - {compareMode && ( - - { - setCompareMode(!compareMode); - setOpen(true); - }} - /> - Compare devices - - Choose a device from the map to compare with. - - - )} - {!open && ( -
{ - setOpen(true); - }} - className="absolute bottom-[10px] left-4 flex cursor-pointer rounded-xl border border-gray-100 bg-white shadow-lg transition-colors duration-300 ease-in-out hover:brightness-90 dark:bg-zinc-800 dark:text-zinc-200 dark:opacity-90 sm:bottom-[30px] sm:left-[10px]" - > - - - -
- -
-
- -

Open device details

-
-
-
-
- )} - - ); + // Update the URL with the new search params + void navigate({ + search: currentParams.toString(), + }) + }} + > + {tag} + + ))} +
+
+
+
+ )} + + {data.device.logEntries.length > 0 && ( + <> + + + + )} + {data.device.description && ( + + + + Description + + + {addLineBreaks(data.device.description)} + + + + )} + + + + Sensors + + +
+
+ {sensors && + sensors.map( + (sensor: SensorWithLatestMeasurement) => { + const sensorLink = createSensorLink(sensor.id) + if (sensorLink === '') { + return ( + + toast({ + title: + 'Cant select more than 2 sensors', + description: + 'Deselect one sensor to select another', + variant: 'destructive', + }) + } + > + + + ) + } + return ( + + + + + + ) + }, + )} +
+
+
+
+
+
+
+
+
+ )} + {compareMode && ( + + { + setCompareMode(!compareMode) + setOpen(true) + }} + /> + Compare devices + + Choose a device from the map to compare with. + + + )} + {!open && ( +
{ + setOpen(true) + }} + className="absolute bottom-[10px] left-4 flex cursor-pointer rounded-xl border border-gray-100 bg-white shadow-lg transition-colors duration-300 ease-in-out hover:brightness-90 dark:bg-zinc-800 dark:text-zinc-200 dark:opacity-90 sm:bottom-[30px] sm:left-[10px]" + > + + + +
+ +
+
+ +

Open device details

+
+
+
+
+ )} + + ) } const InfoItem = ({ - icon: Icon, - title, - text, + icon: Icon, + title, + text, }: { - icon: React.ElementType; - title: string; - text?: string; + icon: React.ElementType + title: string + text?: string }) => - text && ( -
-
{title}
-
- - {text} -
-
- ); + text && ( +
+
{title}
+
+ + {text} +
+
+ ) diff --git a/app/components/device/new/device-info.tsx b/app/components/device/new/device-info.tsx index d1485c73..ddd94b28 100644 --- a/app/components/device/new/device-info.tsx +++ b/app/components/device/new/device-info.tsx @@ -25,7 +25,7 @@ const devices = [ "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLXdpbmQiPjxwYXRoIGQ9Ik0xMi44IDE5LjZBMiAyIDAgMSAwIDE0IDE2SDIiLz48cGF0aCBkPSJNMTcuNSA4YTIuNSAyLjUgMCAxIDEgMiA0SDIiLz48cGF0aCBkPSJNOS44IDQuNEEyIDIgMCAxIDEgMTEgOEgyIi8+PC9zdmc+", }, { - name: "Custom", + name: "custom", image: "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLXdyZW5jaCI+PHBhdGggZD0iTTE0LjcgNi4zYTEgMSAwIDAgMCAwIDEuNGwxLjYgMS42YTEgMSAwIDAgMCAxLjQgMGwzLjc3LTMuNzdhNiA2IDAgMCAxLTcuOTQgNy45NGwtNi45MSA2LjkxYTIuMTIgMi4xMiAwIDAgMS0zLTNsNi45MS02LjkxYTYgNiAwIDAgMSA3Ljk0LTcuOTRsLTMuNzYgMy43NnoiLz48L3N2Zz4=", }, diff --git a/app/components/device/new/general-info.tsx b/app/components/device/new/general-info.tsx index 3f0e518f..3af5395d 100644 --- a/app/components/device/new/general-info.tsx +++ b/app/components/device/new/general-info.tsx @@ -1,200 +1,206 @@ -import { Plus, Cloud, Home, HelpCircle, Bike, X, Info } from "lucide-react"; -import React, { useState } from "react"; -import { useFormContext, useFieldArray } from "react-hook-form"; -import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { Badge } from "~/components/ui/badge"; -import { Checkbox } from "~/components/ui/checkbox"; -import { Label } from "~/components/ui/label"; +import { Plus, Cloud, Home, HelpCircle, Bike, X, Info } from 'lucide-react' +import React, { useState } from 'react' +import { useFormContext, useFieldArray } from 'react-hook-form' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Badge } from '~/components/ui/badge' +import { Checkbox } from '~/components/ui/checkbox' +import { Label } from '~/components/ui/label' import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "~/components/ui/tooltip"; + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '~/components/ui/tooltip' -type ExposureOption = "outdoor" | "indoor" | "mobile" | "unknown"; +type ExposureOption = 'outdoor' | 'indoor' | 'mobile' | 'unknown' export function GeneralInfoStep() { - const { register, control, setValue, getValues, watch } = useFormContext(); - const { fields, append, remove } = useFieldArray({ - control, - name: "tags", // Tags array - }); + const { register, control, setValue, getValues, watch } = useFormContext() + const { fields, append, remove } = useFieldArray({ + control, + name: 'tags', // Tags array + }) - const currentExposure = watch("exposure"); // Watch exposure value + const currentExposure = watch('exposure') // Watch exposure value - // State for temporary expiration date - const [temporaryExpirationDate, setTemporaryExpirationDate] = useState< - string | null - >(watch("temporaryExpirationDate") || null); + // State for temporary expiration date + const [temporaryExpirationDate, setTemporaryExpirationDate] = useState< + string | null + >(watch('temporaryExpirationDate') || null) - const maxExpirationDate = new Date(); - maxExpirationDate.setMonth(maxExpirationDate.getMonth() + 1); + const maxExpirationDate = new Date() + maxExpirationDate.setMonth(maxExpirationDate.getMonth() + 1) - const handleTemporaryChange = (checked: boolean) => { - if (checked) { - const newDate = maxExpirationDate.toISOString().split("T")[0]; - setTemporaryExpirationDate(newDate); // Update local state - setValue("temporaryExpirationDate", newDate); // Update form value - } else { - setTemporaryExpirationDate(null); // Clear local state - setValue("temporaryExpirationDate", ""); // Clear form value - } - }; + const handleTemporaryChange = (checked: boolean) => { + if (checked) { + const newDate = maxExpirationDate.toISOString().split('T')[0] + setTemporaryExpirationDate(newDate) // Update local state + setValue('temporaryExpirationDate', newDate) // Update form value + } else { + setTemporaryExpirationDate(null) // Clear local state + setValue('temporaryExpirationDate', '') // Clear form value + } + } - const handleExpirationDateChange = (date: string) => { - setTemporaryExpirationDate(date); // Update local state - setValue("temporaryExpirationDate", date); // Update form value - }; + const handleExpirationDateChange = (date: string) => { + setTemporaryExpirationDate(date) // Update local state + setValue('temporaryExpirationDate', date) // Update form value + } - const addTag = (event: React.FormEvent) => { - event.preventDefault(); - const tagInput = document.getElementById("tag-input") as HTMLInputElement; - if (tagInput?.value.trim()) { - append({ value: tagInput.value.trim() }); // Append a new tag object - tagInput.value = ""; // Clear input - } - }; + const addTag = (event: React.FormEvent) => { + event.preventDefault() + const tagInput = document.getElementById('tag-input') as HTMLInputElement + if (tagInput?.value.trim()) { + append({ value: tagInput.value.trim() }) // Append a new tag object + tagInput.value = '' // Clear input + } + } - const exposureOptions: { - value: ExposureOption; - icon: React.ReactNode; - label: string; - }[] = [ - { value: "outdoor", icon: , label: "Outdoor" }, - { value: "indoor", icon: , label: "Indoor" }, - { - value: "mobile", - icon: , - label: "Mobile", - }, - { - value: "unknown", - icon: , - label: "Unknown", - }, - ]; + const exposureOptions: { + value: ExposureOption + icon: React.ReactNode + label: string + }[] = [ + { value: 'outdoor', icon: , label: 'Outdoor' }, + { value: 'indoor', icon: , label: 'Indoor' }, + { + value: 'mobile', + icon: , + label: 'Mobile', + }, + { + value: 'unknown', + icon: , + label: 'Unknown', + }, + ] - return ( -
-
- - -
-
- -
- {exposureOptions.map((option) => ( - - ))} -
-
-
-
-
- - - - - - - - - { -

- Temporary devices will be automatically deleted after a - maximum of one month. -

- } -
-
-
-
- {temporaryExpirationDate && ( -
- - handleExpirationDateChange(e.target.value)} - min={new Date().toISOString().split("T")[0]} - max={maxExpirationDate.toISOString().split("T")[0]} - className="flex-grow p-2 border rounded-md" - /> -
- )} -
-
-
- -
- { - if (e.key === "Enter") { - e.preventDefault(); - addTag(e); // Call addTag on Enter key - } - }} - /> - -
-
- {fields.map((field, index) => ( - - {getValues(`tags.${index}.value`)} - - - ))} -
-
-
- ); + return ( +
+
+ + +
+
+ +
+ {exposureOptions.map((option) => ( + + ))} +
+
+
+
+
+ + + + + { + e.preventDefault() + e.stopPropagation() + }} + > + + + + { +

+ Temporary devices will be automatically deleted after a + maximum of one month. +

+ } +
+
+
+
+ {temporaryExpirationDate && ( +
+ + handleExpirationDateChange(e.target.value)} + min={new Date().toISOString().split('T')[0]} + max={maxExpirationDate.toISOString().split('T')[0]} + className="flex-grow rounded-md border p-2" + /> +
+ )} +
+
+
+ +
+ { + if (e.key === 'Enter') { + e.preventDefault() + addTag(e) // Call addTag on Enter key + } + }} + /> + +
+
+ {fields.map((field, index) => ( + + {getValues(`tags.${index}.value`)} + + + ))} +
+
+
+ ) } diff --git a/app/components/device/new/new-device-stepper.tsx b/app/components/device/new/new-device-stepper.tsx index e65a3fdd..af5f2201 100644 --- a/app/components/device/new/new-device-stepper.tsx +++ b/app/components/device/new/new-device-stepper.tsx @@ -1,359 +1,375 @@ -import { zodResolver } from "@hookform/resolvers/zod"; -import { defineStepper } from "@stepperize/react"; -import { Info, Slash } from "lucide-react"; -import { useEffect, useState } from "react"; -import { type FieldErrors, FormProvider, useForm } from "react-hook-form"; -import { Form, useSubmit } from "react-router"; -import { z } from "zod"; -import { AdvancedStep } from "./advanced-info"; -import { DeviceSelectionStep } from "./device-info"; -import { GeneralInfoStep } from "./general-info"; -import { LocationStep } from "./location-info"; -import { sensorSchema, SensorSelectionStep } from "./sensors-info"; -import { SummaryInfo } from "./summary-info"; +import { zodResolver } from '@hookform/resolvers/zod' +import { defineStepper } from '@stepperize/react' +import { Info, Slash } from 'lucide-react' +import { useEffect, useState } from 'react' +import { type FieldErrors, FormProvider, useForm } from 'react-hook-form' +import { Form, useSubmit } from 'react-router' +import { z } from 'zod' +import { AdvancedStep } from './advanced-info' +import { DeviceSelectionStep } from './device-info' +import { GeneralInfoStep } from './general-info' +import { LocationStep } from './location-info' +import { sensorSchema, SensorSelectionStep } from './sensors-info' +import { SummaryInfo } from './summary-info' import { - Breadcrumb, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbList, - BreadcrumbSeparator, -} from "~/components/ui/breadcrumb"; -import { Button } from "~/components/ui/button"; + Breadcrumb, + BreadcrumbItem, + BreadcrumbLink, + BreadcrumbList, + BreadcrumbSeparator, +} from '~/components/ui/breadcrumb' +import { Button } from '~/components/ui/button' import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "~/components/ui/tooltip"; -import { useToast } from "~/components/ui/use-toast"; -import { DeviceModelEnum } from "~/schema/enum"; + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '~/components/ui/tooltip' +import { useToast } from '~/components/ui/use-toast' +import { DeviceModelEnum } from '~/schema/enum' const generalInfoSchema = z.object({ - name: z - .string() - .min(2, "Name must be at least 2 characters") - .min(1, "Name is required"), - exposure: z.enum(["indoor", "outdoor", "mobile", "unknown"], { - errorMap: () => ({ message: "Exposure is required" }), - }), - temporaryExpirationDate: z - .string() - .optional() - .transform((date) => (date ? new Date(date) : undefined)) // Transform string to Date - .refine( - (date) => - !date || date <= new Date(Date.now() + 31 * 24 * 60 * 60 * 1000), - { - message: "Temporary expiration date must be within 1 month from now", - }, - ), - tags: z - .array( - z.object({ - value: z.string(), - }), - ) - .optional(), -}); + name: z + .string() + .min(2, 'Name must be at least 2 characters') + .min(1, 'Name is required'), + exposure: z.enum(['indoor', 'outdoor', 'mobile', 'unknown'], { + errorMap: () => ({ message: 'Exposure is required' }), + }), + temporaryExpirationDate: z + .string() + .optional() + .transform((date) => (date ? new Date(date) : undefined)) // Transform string to Date + .refine( + (date) => + !date || date <= new Date(Date.now() + 31 * 24 * 60 * 60 * 1000), + { + message: 'Temporary expiration date must be within 1 month from now', + }, + ), + tags: z + .array( + z.object({ + value: z.string(), + }), + ) + .optional(), +}) const locationSchema = z.object({ - latitude: z.coerce - .number({ - invalid_type_error: "Latitude must be a valid number", - required_error: "Latitude is required", - }) - .min(-90, "Latitude must be greater than or equal to -90") - .max(90, "Latitude must be less than or equal to 90"), - longitude: z.coerce - .number({ - invalid_type_error: "Longitude must be a valid number", - required_error: "Longitude is required", - }) - .min(-180, "Longitude must be greater than or equal to -180") - .max(180, "Longitude must be less than or equal to 180"), -}); + latitude: z.coerce + .number({ + invalid_type_error: 'Latitude must be a valid number', + required_error: 'Latitude is required', + }) + .min(-90, 'Latitude must be greater than or equal to -90') + .max(90, 'Latitude must be less than or equal to 90'), + longitude: z.coerce + .number({ + invalid_type_error: 'Longitude must be a valid number', + required_error: 'Longitude is required', + }) + .min(-180, 'Longitude must be greater than or equal to -180') + .max(180, 'Longitude must be less than or equal to 180'), +}) const deviceSchema = z.object({ - model: z.enum(DeviceModelEnum.enumValues, { - errorMap: () => ({ message: "Please select a device." }), - }), -}); + model: z.enum(DeviceModelEnum.enumValues, { + errorMap: () => ({ message: 'Please select a device.' }), + }), +}) // selectedSensors can be an array of sensors const sensorsSchema = z.object({ - selectedSensors: z - .array(sensorSchema) - .min(1, "Please select at least one sensor"), -}); + selectedSensors: z + .array(sensorSchema) + .min(1, 'Please select at least one sensor'), +}) const mqttSchema = z - .object({ - mqttEnabled: z.boolean().default(false), - url: z.string().optional(), - topic: z.string().optional(), - messageFormat: z.enum(["json", "csv"]).optional(), - decodeOptions: z.string().optional(), - connectionOptions: z.string().optional(), - }) - .superRefine((data, ctx) => { - if (data.mqttEnabled) { - // Check required fields when enabled is true - if (!data.url) { - ctx.addIssue({ - path: ["url"], - message: "URL is required when MQTT is enabled.", - code: "custom", - }); - } - if (!data.topic) { - ctx.addIssue({ - path: ["topic"], - message: "Topic is required when MQTT is enabled.", - code: "custom", - }); - } - if (!data.messageFormat) { - ctx.addIssue({ - path: ["messageFormat"], - message: "Message format is required when MQTT is enabled.", - code: "custom", - }); - } - } - }); + .object({ + mqttEnabled: z.boolean().default(false), + url: z.string().optional(), + topic: z.string().optional(), + messageFormat: z.enum(['json', 'csv']).optional(), + decodeOptions: z.string().optional(), + connectionOptions: z.string().optional(), + }) + .superRefine((data, ctx) => { + if (data.mqttEnabled) { + // Check required fields when enabled is true + if (!data.url) { + ctx.addIssue({ + path: ['url'], + message: 'URL is required when MQTT is enabled.', + code: 'custom', + }) + } + if (!data.topic) { + ctx.addIssue({ + path: ['topic'], + message: 'Topic is required when MQTT is enabled.', + code: 'custom', + }) + } + if (!data.messageFormat) { + ctx.addIssue({ + path: ['messageFormat'], + message: 'Message format is required when MQTT is enabled.', + code: 'custom', + }) + } + } + }) const ttnSchema = z - .object({ - ttnEnabled: z.boolean().default(false), - dev_id: z.string().optional(), - app_id: z.string().optional(), - profile: z - .enum([ - "lora-serialization", - "sensebox/home", - "json", - "debug", - "cayenne-lpp", - ]) - .optional(), - decodeOptions: z.string().optional(), - port: z.number().optional(), - }) - .superRefine((data, ctx) => { - if (data.ttnEnabled) { - if (!data.dev_id) { - ctx.addIssue({ - path: ["dev_id"], - message: "Device ID is required when TTN is enabled.", - code: "custom", - }); - } + .object({ + ttnEnabled: z.boolean().default(false), + dev_id: z.string().optional(), + app_id: z.string().optional(), + profile: z + .enum([ + 'lora-serialization', + 'sensebox/home', + 'json', + 'debug', + 'cayenne-lpp', + ]) + .optional(), + decodeOptions: z.string().optional(), + port: z.number().optional(), + }) + .superRefine((data, ctx) => { + if (data.ttnEnabled) { + if (!data.dev_id) { + ctx.addIssue({ + path: ['dev_id'], + message: 'Device ID is required when TTN is enabled.', + code: 'custom', + }) + } - if (!data.app_id) { - ctx.addIssue({ - path: ["app_id"], - message: "Application ID is required when TTN is enabled.", - code: "custom", - }); - } + if (!data.app_id) { + ctx.addIssue({ + path: ['app_id'], + message: 'Application ID is required when TTN is enabled.', + code: 'custom', + }) + } - if (!data.profile) { - ctx.addIssue({ - path: ["profile"], - message: "Profile is required when TTN is enabled.", - code: "custom", - }); - } - } - }); + if (!data.profile) { + ctx.addIssue({ + path: ['profile'], + message: 'Profile is required when TTN is enabled.', + code: 'custom', + }) + } + } + }) -const advancedSchema = z.intersection(mqttSchema, ttnSchema); +const advancedSchema = z.intersection(mqttSchema, ttnSchema) export const Stepper = defineStepper( - { - id: "general-info", - label: "General Info", - info: "Provide a unique name for your device, select its operating environment (outdoor, indoor, mobile, or unknown), and add relevant tags (optional).", - schema: generalInfoSchema, - index: 0 - }, - { - id: "location", - label: "Location", - info: "Select the device's location by clicking on the map or entering latitude and longitude coordinates manually. Drag the marker on the map to adjust the location if needed.", - schema: locationSchema, - index: 1 - }, - { - id: "device-selection", - label: "Device Selection", - info: "Select a device model from the available options", - schema: deviceSchema, - index: 2 - }, - { - id: "sensor-selection", - label: "Sensor Selection", - info: "Select sensors for your device by choosing from predefined groups or individual sensors based on your device model. If using a custom device, configure sensors manually.", - schema: sensorsSchema, - index: 3 - }, - { id: "advanced", label: "Advanced", info: null, schema: advancedSchema, index: 4 }, - { id: "summary", label: "Summary", info: null, schema: z.object({}), index: 5 }, -); + { + id: 'general-info', + label: 'General Info', + info: 'Provide a unique name for your device, select its operating environment (outdoor, indoor, mobile, or unknown), and add relevant tags (optional).', + schema: generalInfoSchema, + index: 0, + }, + { + id: 'location', + label: 'Location', + info: "Select the device's location by clicking on the map or entering latitude and longitude coordinates manually. Drag the marker on the map to adjust the location if needed.", + schema: locationSchema, + index: 1, + }, + { + id: 'device-selection', + label: 'Device Selection', + info: 'Select a device model from the available options', + schema: deviceSchema, + index: 2, + }, + { + id: 'sensor-selection', + label: 'Sensor Selection', + info: 'Select sensors for your device by choosing from predefined groups or individual sensors based on your device model. If using a custom device, configure sensors manually.', + schema: sensorsSchema, + index: 3, + }, + { + id: 'advanced', + label: 'Advanced', + info: null, + schema: advancedSchema, + index: 4, + }, + { + id: 'summary', + label: 'Summary', + info: null, + schema: z.object({}), + index: 5, + }, +) -type GeneralInfoData = z.infer; -type LocationData = z.infer; -type DeviceData = z.infer; -type SensorData = z.infer; -type MqttData = z.infer; -type TtnData = z.infer; +type GeneralInfoData = z.infer +type LocationData = z.infer +type DeviceData = z.infer +type SensorData = z.infer +type MqttData = z.infer +type TtnData = z.infer type FormData = GeneralInfoData & - LocationData & - DeviceData & - SensorData & - MqttData & - TtnData; + LocationData & + DeviceData & + SensorData & + MqttData & + TtnData export default function NewDeviceStepper() { - const submit = useSubmit(); - const [formData, setFormData] = useState>({}); - const stepper = Stepper.useStepper(); - const form = useForm({ - mode: "onTouched", - resolver: zodResolver(stepper.current.schema), - }); - const { toast } = useToast(); - const [isFirst, setIsFirst] = useState(false); + const submit = useSubmit() + const [formData, setFormData] = useState>({}) + const stepper = Stepper.useStepper() + const form = useForm({ + mode: 'onTouched', + resolver: zodResolver(stepper.current.schema), + }) + const { toast } = useToast() + const [isFirst, setIsFirst] = useState(false) - useEffect(() => { - setIsFirst(stepper.isFirst); - }, [stepper.isFirst]); + useEffect(() => { + setIsFirst(stepper.isFirst) + }, [stepper.isFirst]) - const onSubmit = (data: FormData) => { - console.log("🚀 ~ onSubmit ~ data:", data); - const updatedData = { - ...formData, - [stepper.current.id]: data, - }; + const onSubmit = (data: FormData) => { + console.log('🚀 ~ onSubmit ~ data:', data) + const updatedData = { + ...formData, + [stepper.current.id]: data, + } - setFormData(updatedData); + setFormData(updatedData) - if (stepper.isLast) { - console.log("Complete! Final Data:", updatedData); + if (stepper.isLast) { + console.log('Complete! Final Data:', updatedData) - // Submit form data as JSON - void submit( - { - formData: JSON.stringify(updatedData), // Serialize the data - }, - { method: "post" }, - ); - } else { - stepper.next(); - } - }; + // Submit form data as JSON + void submit( + { + formData: JSON.stringify(updatedData), // Serialize the data + }, + { method: 'post' }, + ) + } else { + stepper.next() + } + } - const onError = (errors: FieldErrors) => { - const firstErrorMessage = Object.values(errors)?.[0]?.message; - if (firstErrorMessage) { - toast({ - title: "Form Error", - description: firstErrorMessage, - variant: "destructive", - duration: 2000, - }); - } - }; + const onError = (errors: FieldErrors) => { + const firstErrorMessage = Object.values(errors)?.[0]?.message + if (firstErrorMessage) { + toast({ + title: 'Form Error', + description: firstErrorMessage, + variant: 'destructive', + duration: 2000, + }) + } + } - return ( - - -
-
- {/* Breadcrumb Navigation */} - - - {Stepper.steps.map((step, index) => { - return ( -
- - stepper.goTo(step.id)} - className={` - ${ - stepper.current.index === step.index - ? "font-bold text-black" - : "text-gray-500 cursor-pointer hover:text-black" - } - `} - > - {step.label} - - + return ( + + + +
+ {/* Breadcrumb Navigation */} + + + {Stepper.steps.map((step, index) => { + return ( +
+ + stepper.goTo(step.id)} + className={` ${ + stepper.current.index === step.index + ? 'font-bold text-black' + : 'cursor-pointer text-gray-500 hover:text-black' + } `} + > + {step.label} + + - {index < Stepper.steps.length - 1 && ( - - - - )} -
- ); - })} -
-
+ {index < Stepper.steps.length - 1 && ( + + + + )} +
+ ) + })} + + - {/* Step Header with Info */} -
-

- Step {stepper.current.index + 1} of {Stepper.steps.length}:{" "} - {stepper.current.label} -

- {stepper.current.info && ( - - - e.preventDefault()}> - - - {stepper.current.info} - - - )} -
-
+ {/* Step Header with Info */} +
+

+ Step {stepper.current.index + 1} of {Stepper.steps.length}:{' '} + {stepper.current.label} +

+ {stepper.current.info && ( + + + { + e.preventDefault() + e.stopPropagation() + }} + > + + + {stepper.current.info} + + + )} +
+
- {/* Form Content */} -
- {stepper.switch({ - advanced: () => , - "general-info": () => , - location: () => , - "device-selection": () => , - "sensor-selection": () => , - summary: () => , - })} -
+ {/* Form Content */} +
+ {stepper.switch({ + advanced: () => , + 'general-info': () => , + location: () => , + 'device-selection': () => , + 'sensor-selection': () => , + summary: () => , + })} +
- {/* Navigation Buttons */} -
- - -
-
-
-
- ); + {/* Navigation Buttons */} +
+ + +
+ + + + ) } diff --git a/app/components/device/new/sensors-info.tsx b/app/components/device/new/sensors-info.tsx index a069cf13..115b7c61 100644 --- a/app/components/device/new/sensors-info.tsx +++ b/app/components/device/new/sensors-info.tsx @@ -38,11 +38,13 @@ export function SensorSelectionStep() { : selectedDevice; setSelectedDeviceModel(deviceModel); - const fetchSensors = () => { - const fetchedSensors = getSensorsForModel(deviceModel); - setSensors(fetchedSensors); - }; - fetchSensors(); + if (deviceModel !== "custom") { + const fetchedSensors = getSensorsForModel(deviceModel); + setSensors(fetchedSensors); + } else { + setSensors([]); + } + } }, [selectedDevice]); @@ -123,7 +125,7 @@ export function SensorSelectionStep() { return

Please select a device first.

; } - if (selectedDevice === "Custom") { + if (selectedDevice === "custom") { return ; } diff --git a/app/components/header/menu/index.tsx b/app/components/header/menu/index.tsx index fe31a44a..64389ff9 100644 --- a/app/components/header/menu/index.tsx +++ b/app/components/header/menu/index.tsx @@ -97,7 +97,7 @@ export default function Menu() { - {"Explore"} + {t("explore_label")} )} @@ -105,7 +105,7 @@ export default function Menu() { - Profile + {t("profile_label")} )} @@ -114,7 +114,7 @@ export default function Menu() { - {"Settings"} + {t("settings_label")} diff --git a/app/components/landing/footer.tsx b/app/components/landing/footer.tsx index 71388542..2d0178f9 100644 --- a/app/components/landing/footer.tsx +++ b/app/components/landing/footer.tsx @@ -1,100 +1,106 @@ +import { useTranslation } from 'react-i18next' + export default function Footer() { - return ( - - ); + const { t } = useTranslation('footer') + return ( + + ) } diff --git a/app/components/landing/header/header.tsx b/app/components/landing/header/header.tsx index cc61b57c..e11e23ef 100644 --- a/app/components/landing/header/header.tsx +++ b/app/components/landing/header/header.tsx @@ -2,6 +2,7 @@ import { useState } from "react"; import { Link } from "react-router"; // import { ModeToggle } from "../../mode-toggle"; import LanguageSelector from "./language-selector"; +import { useTranslation } from "react-i18next"; const links = [ { @@ -33,6 +34,8 @@ const links = [ export default function Header() { const [openMenu, setOpenMenu] = useState(false); + const { t } = useTranslation("header"); + return (