diff --git a/.gitignore b/.gitignore
index d74f88e..3ddc139 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,5 +13,8 @@
# OS
.DS_Store
+# local notes
+TODO.md
+
# protobuf generated
-internal/grpc/pb/*.pb.go
\ No newline at end of file
+internal/grpc/pb/*.pb.goa
\ No newline at end of file
diff --git a/gui/wails-standalone/README.md b/gui/wails-standalone/README.md
new file mode 100644
index 0000000..397b08b
--- /dev/null
+++ b/gui/wails-standalone/README.md
@@ -0,0 +1,19 @@
+# README
+
+## About
+
+This is the official Wails Vanilla template.
+
+You can configure the project by editing `wails.json`. More information about the project settings can be found
+here: https://wails.io/docs/reference/project-config
+
+## Live Development
+
+To run in live development mode, run `wails dev` in the project directory. This will run a Vite development
+server that will provide very fast hot reload of your frontend changes. If you want to develop in a browser
+and have access to your Go methods, there is also a dev server that runs on http://localhost:34115. Connect
+to this in your browser, and you can call your Go code from devtools.
+
+## Building
+
+To build a redistributable, production mode package, use `wails build`.
diff --git a/gui/wails-standalone/frontend/dist/assets/index.02c817da.js b/gui/wails-standalone/frontend/dist/assets/index.02c817da.js
new file mode 100644
index 0000000..dc2b94c
--- /dev/null
+++ b/gui/wails-standalone/frontend/dist/assets/index.02c817da.js
@@ -0,0 +1 @@
+(function(){const d=document.createElement("link").relList;if(d&&d.supports&&d.supports("modulepreload"))return;for(const e of document.querySelectorAll('link[rel="modulepreload"]'))f(e);new MutationObserver(e=>{for(const n of e)if(n.type==="childList")for(const s of n.addedNodes)s.tagName==="LINK"&&s.rel==="modulepreload"&&f(s)}).observe(document,{childList:!0,subtree:!0});function m(e){const n={};return e.integrity&&(n.integrity=e.integrity),e.referrerpolicy&&(n.referrerPolicy=e.referrerpolicy),e.crossorigin==="use-credentials"?n.credentials="include":e.crossorigin==="anonymous"?n.credentials="omit":n.credentials="same-origin",n}function f(e){if(e.ep)return;e.ep=!0;const n=m(e);fetch(e.href,n)}})();function N(){return window.go.main.App.CheckConnection()}function F(){return window.go.main.App.Quit()}function $(a){return window.go.main.App.SetFrequency(a)}const D="/assets/logo-dark.ff9a8bc7.jpg";(function(){const a=document.getElementById("theme-toggle");a.addEventListener("click",()=>{const c=document.body.getAttribute("data-theme")==="light"?"dark":"light";document.body.setAttribute("data-theme",c),a.textContent=c==="light"?"\u263E":"\u2600"});const d=640,m=480,f=312,e=D,n=document.getElementById("intro-overlay"),s=document.querySelector("#intro-overlay .door-left"),l=document.querySelector("#intro-overlay .door-right");function L(){const t=window.innerWidth,c=window.innerHeight,o=Math.max(t/d,c/m),h=d*o,p=m*o,v=(t-h)/2,q=(c-p)/2,W=v+f*o,g=Math.max(0,Math.min(t,W)),_=t-g;s.style.width=g+"px",s.style.backgroundImage=`url(${e})`,s.style.backgroundSize=h+"px "+p+"px",s.style.backgroundPosition=v+"px "+q+"px",l.style.left=g+"px",l.style.width=_+"px",l.style.backgroundImage=`url(${e})`,l.style.backgroundSize=h+"px "+p+"px",l.style.backgroundPosition=v-g+"px "+q+"px"}const b=2100,C=100;function A(){n.style.display="",n.classList.remove("appear","open"),n.offsetWidth,L(),setTimeout(()=>n.classList.add("appear"),C),setTimeout(()=>n.classList.add("open"),b),setTimeout(()=>{n.style.display="none"},b+1170)}window.addEventListener("resize",L),A();const E=document.querySelectorAll(".tab-button"),B=document.querySelectorAll(".tab-content");E.forEach(t=>{t.addEventListener("click",()=>{E.forEach(o=>o.classList.remove("active")),B.forEach(o=>o.classList.remove("active")),t.classList.add("active");const c=t.getAttribute("data-tab");document.getElementById(`tab-${c}`).classList.add("active")})});const w=document.getElementById("btn-check"),S=document.getElementById("btn-freq"),T=document.getElementById("btn-quit"),x=document.getElementById("status-indicator"),I=document.getElementById("status-text"),k=document.getElementById("frequency-label"),y=document.getElementById("frequency-value"),u=document.getElementById("freq-modal"),O=document.getElementById("close-modal"),i=document.getElementById("freq-input"),P=document.getElementById("btn-minus"),M=document.getElementById("btn-plus"),R=document.getElementById("btn-set-freq"),r=document.getElementById("modal-error");w.addEventListener("click",()=>{N().then(t=>{x.classList.remove("indicator-red"),x.classList.add("indicator-green"),I.textContent=`\u0421\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E! ${t.status}`,k.textContent="\u0422\u0435\u043A\u0443\u0449\u0430\u044F \u0447\u0430\u0441\u0442\u043E\u0442\u0430:",y.textContent=t.frequency}).catch(t=>{console.error("\u041E\u0448\u0438\u0431\u043A\u0430 CheckConnection:",t),I.textContent="\u041E\u0448\u0438\u0431\u043A\u0430: \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u044C \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443"})}),S.addEventListener("click",()=>{u.classList.remove("hidden");const t=y.textContent!=="---"?y.textContent:"145";i.value=t,r.classList.add("hidden")}),O.addEventListener("click",()=>{u.classList.add("hidden")}),window.addEventListener("click",t=>{t.target===u&&u.classList.add("hidden")}),P.addEventListener("click",()=>{let t=parseInt(i.value,10)||1;t=Math.max(1,t-1),i.value=t.toString()}),M.addEventListener("click",()=>{let t=parseInt(i.value,10)||1;t+=1,i.value=t.toString()}),R.addEventListener("click",()=>{const t=i.value.trim();if(!/^\d+$/.test(t)){r.textContent="\u0412\u0432\u043E\u0434\u0438\u043C\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u0446\u0435\u043B\u044B\u043C \u043F\u043E\u043B\u043E\u0436\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u043C \u0447\u0438\u0441\u043B\u043E\u043C",r.classList.remove("hidden");return}if(parseInt(t,10)<=0){r.textContent="\u0412\u0432\u043E\u0434\u0438\u043C\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u0446\u0435\u043B\u044B\u043C \u043F\u043E\u043B\u043E\u0436\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u043C \u0447\u0438\u0441\u043B\u043E\u043C",r.classList.remove("hidden");return}$(t).then(o=>{k.textContent="\u0423\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u0430\u044F \u0447\u0430\u0441\u0442\u043E\u0442\u0430:",y.textContent=o,u.classList.add("hidden")}).catch(o=>{r.textContent=o,r.classList.remove("hidden")})}),T.addEventListener("click",()=>{F()})})();
diff --git a/gui/wails-standalone/frontend/dist/assets/index.41fde905.css b/gui/wails-standalone/frontend/dist/assets/index.41fde905.css
new file mode 100644
index 0000000..b457202
--- /dev/null
+++ b/gui/wails-standalone/frontend/dist/assets/index.41fde905.css
@@ -0,0 +1 @@
+*{margin:0;padding:0;box-sizing:border-box}html,body{width:100%;height:100%;display:flex;justify-content:center;align-items:center;font-family:Arial,sans-serif;overflow:hidden}body{background:radial-gradient(circle at 50% 18%,#1a1d24 0%,#0d0f13 70%);transition:background .3s ease}body[data-theme=light]{background:radial-gradient(circle at 50% 18%,#ffffff 0%,#dde1e7 70%)}#theme-toggle{position:fixed;top:16px;right:18px;z-index:500;width:34px;height:34px;border-radius:50%;border:1px solid #333a46;background:#222a37;color:#ccc;cursor:pointer;font-size:14px;display:flex;align-items:center;justify-content:center}body[data-theme=light] #theme-toggle{border-color:#c7cbd1;background:#ffffff;color:#555}#app{width:360px;border-radius:14px;box-shadow:0 8px 28px #0000004d;overflow:hidden;background:#1b1e24;transition:background .3s ease}body[data-theme=light] #app{background:#ffffff}#brand{display:flex;flex-direction:column;align-items:center;padding:26px 24px 16px;gap:6px}.logo-icon{position:relative;width:64px;height:48px;margin-bottom:6px}.logo-icon img{position:absolute;inset:0;width:100%;height:100%;object-fit:contain}.logo-icon .logo-dark{mix-blend-mode:screen;display:block}.logo-icon .logo-light{mix-blend-mode:multiply;display:none}body[data-theme=light] .logo-icon .logo-dark{display:none}body[data-theme=light] .logo-icon .logo-light{display:block}#brand h1{color:#eee;font-size:21px;letter-spacing:3.5px;font-weight:700}#brand .tag{color:#7a7f89;font-size:11px;letter-spacing:1.2px}body[data-theme=light] #brand h1{color:#1b1d22}body[data-theme=light] #brand .tag{color:#888d97}.tabs{display:flex;background:transparent}.tabs[hidden]{display:none}.tab-button{flex:1;padding:12px;background:transparent;border:none;cursor:pointer;font-size:14px;color:#9aa0aa;transition:background .2s}.tab-button:hover{background:rgba(255,255,255,.05)}.tab-button.active{color:#eee;border-bottom:2px solid #b8362a;font-weight:700}body[data-theme=light] .tab-button{color:#7a7f89}body[data-theme=light] .tab-button.active{color:#1b1d22}.tab-content{display:none;padding:4px 22px 24px}.tab-content.active{display:block}.buttons-vertical{display:flex;flex-direction:column;gap:10px;margin-bottom:12px}.action-button{padding:11px;font-size:14px;cursor:pointer;border:1px solid #2a2d34;border-radius:8px;background-color:#15171c;color:#e8e8e8;transition:background-color .2s,border-color .2s}.action-button:hover{background-color:#1d2027;border-color:#3a3e46}body[data-theme=light] .action-button{background-color:#fff;border-color:#ccd1d9;color:#22242a}body[data-theme=light] .action-button:hover{background-color:#f0f2f5;border-color:#aeb4bd}.close-button{background-color:#b8362a;color:#fff;border-color:#8f2a20}.close-button:hover{background-color:#c73f31}body[data-theme=light] .close-button{background-color:#e74c3c;border-color:#c0392b;color:#fff}body[data-theme=light] .close-button:hover{background-color:#d8432f}#frequency-panel{display:flex;justify-content:space-between;align-items:center;padding:9px 13px;background-color:#15171c;border:1px solid #2a2d34;border-radius:8px;margin-bottom:9px}#frequency-label{font-weight:700;color:#9aa0aa;font-size:13px}#frequency-value{font-size:17px;color:#f2f2f2}body[data-theme=light] #frequency-panel{background-color:#fff;border-color:#ccd1d9}body[data-theme=light] #frequency-label{color:#7a7f89}body[data-theme=light] #frequency-value{color:#1b1d22}#status-panel{display:flex;align-items:center;gap:9px;padding:9px 13px;background-color:#15171c;border:1px solid #2a2d34;border-radius:8px}body[data-theme=light] #status-panel{background-color:#fff;border-color:#ccd1d9}.indicator{width:14px;height:14px;border-radius:50%;display:inline-block}.indicator-red{background-color:#e74c3c}.indicator-green{background-color:#2ecc71}#status-text{font-size:14px;color:#ddd}body[data-theme=light] #status-text{color:#333}#intro-overlay{position:fixed;inset:0;z-index:1000;background:#000;transform:scale(1.04)}#intro-overlay.appear{animation:introPop .45s cubic-bezier(.2,.9,.3,1) forwards}@keyframes introPop{to{transform:scale(1)}}.door{position:absolute;top:0;bottom:0;overflow:hidden;background-color:#000;background-repeat:no-repeat;transition:transform 1.17s cubic-bezier(.65,0,.35,1)}.door-left,.door-right{left:0}#intro-overlay.open .door-left{transform:translate(-100%)}#intro-overlay.open .door-right{transform:translate(100%)}#intro-overlay.open{transition:opacity .45s ease .65s;opacity:0;pointer-events:none}.modal{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.5);display:flex;justify-content:center;align-items:center;z-index:100}.modal.hidden{display:none}.modal-content{background:#1b1e24;padding:20px;border-radius:8px;width:300px;box-shadow:0 5px 15px #0006;position:relative;color:#eee}body[data-theme=light] .modal-content{background:#ffffff;color:#222}.close-modal{position:absolute;top:10px;right:15px;font-size:22px;cursor:pointer;color:#999}.close-modal:hover{color:#eee}body[data-theme=light] .close-modal{color:#777}body[data-theme=light] .close-modal:hover{color:#333}.modal-content h3{margin-bottom:15px;text-align:center}.freq-input-group{display:flex;align-items:center;justify-content:center;gap:10px;margin-bottom:15px}.freq-btn{width:40px;height:40px;font-size:20px;background:#b8362a;color:#fff;border:none;border-radius:4px;cursor:pointer;transition:background .2s}.freq-btn:hover{background:#c73f31}#freq-input{width:100px;padding:8px;font-size:18px;text-align:center;border:1px solid #444;border-radius:4px;background:#15171c;color:#eee}body[data-theme=light] #freq-input{border-color:#ccc;background:#fff;color:#222}#btn-set-freq{display:block;width:100%;margin-top:10px}.error-msg{color:#e74c3c;margin-top:8px;text-align:center;font-size:14px}.error-msg.hidden{display:none}
diff --git a/gui/wails-standalone/frontend/dist/assets/logo-dark.ff9a8bc7.jpg b/gui/wails-standalone/frontend/dist/assets/logo-dark.ff9a8bc7.jpg
new file mode 100644
index 0000000..6856a0a
Binary files /dev/null and b/gui/wails-standalone/frontend/dist/assets/logo-dark.ff9a8bc7.jpg differ
diff --git a/gui/wails-standalone/frontend/dist/assets/logo-light.d5e577a8.png b/gui/wails-standalone/frontend/dist/assets/logo-light.d5e577a8.png
new file mode 100644
index 0000000..5c56201
Binary files /dev/null and b/gui/wails-standalone/frontend/dist/assets/logo-light.d5e577a8.png differ
diff --git a/gui/wails-standalone/frontend/dist/index.html b/gui/wails-standalone/frontend/dist/index.html
new file mode 100644
index 0000000..a5f458f
--- /dev/null
+++ b/gui/wails-standalone/frontend/dist/index.html
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+ Спектр
+
+
+
+
+
+
+ ☀
+
+
+
+
+
+
+
+
+
+
+
+
+
СПЕКТР
+
видеоприёмник · панель управления
+
+
+
+
+ Режим приемника
+ Режим детектора
+ Информация
+
+
+
+
+
+ Проверка подключения
+ Установка частоты
+ Закрыть приложение
+
+
+
+
+ Текущая частота:
+ ---
+
+
+
+
+
+ Нет соединения
+
+
+
+
+
Здесь будет проводиться сканирование доступных частот!
+
+
+
+
О программе: Версия 0.01, сделано на Wails в тэч дм!
+
+
+
+
+
+
+
×
+
Укажите желаемую частоту
+
+ -
+
+ +
+
+
Установить
+
+
+
+
+
+
+
diff --git a/gui/wails-standalone/frontend/index.html b/gui/wails-standalone/frontend/index.html
new file mode 100644
index 0000000..0c7e040
--- /dev/null
+++ b/gui/wails-standalone/frontend/index.html
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+ Спектр
+
+
+
+
+ ☀
+
+
+
+
+
+
+
+
+
+
+
+
+
СПЕКТР
+
видеоприёмник · панель управления
+
+
+
+
+ Режим приемника
+ Режим детектора
+ Информация
+
+
+
+
+
+ Проверка подключения
+ Установка частоты
+ Закрыть приложение
+
+
+
+
+ Текущая частота:
+ ---
+
+
+
+
+
+ Нет соединения
+
+
+
+
+
Здесь будет проводиться сканирование доступных частот!
+
+
+
+
О программе: Версия 0.01, сделано на Wails в тэч дм!
+
+
+
+
+
+
+
×
+
Укажите желаемую частоту
+
+ -
+
+ +
+
+
Установить
+
+
+
+
+
+
+
diff --git a/gui/wails-standalone/frontend/package-lock.json b/gui/wails-standalone/frontend/package-lock.json
new file mode 100644
index 0000000..062fb25
--- /dev/null
+++ b/gui/wails-standalone/frontend/package-lock.json
@@ -0,0 +1,664 @@
+{
+ "name": "frontend",
+ "version": "0.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "frontend",
+ "version": "0.0.0",
+ "devDependencies": {
+ "vite": "^3.0.7"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz",
+ "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz",
+ "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz",
+ "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/android-arm": "0.15.18",
+ "@esbuild/linux-loong64": "0.15.18",
+ "esbuild-android-64": "0.15.18",
+ "esbuild-android-arm64": "0.15.18",
+ "esbuild-darwin-64": "0.15.18",
+ "esbuild-darwin-arm64": "0.15.18",
+ "esbuild-freebsd-64": "0.15.18",
+ "esbuild-freebsd-arm64": "0.15.18",
+ "esbuild-linux-32": "0.15.18",
+ "esbuild-linux-64": "0.15.18",
+ "esbuild-linux-arm": "0.15.18",
+ "esbuild-linux-arm64": "0.15.18",
+ "esbuild-linux-mips64le": "0.15.18",
+ "esbuild-linux-ppc64le": "0.15.18",
+ "esbuild-linux-riscv64": "0.15.18",
+ "esbuild-linux-s390x": "0.15.18",
+ "esbuild-netbsd-64": "0.15.18",
+ "esbuild-openbsd-64": "0.15.18",
+ "esbuild-sunos-64": "0.15.18",
+ "esbuild-windows-32": "0.15.18",
+ "esbuild-windows-64": "0.15.18",
+ "esbuild-windows-arm64": "0.15.18"
+ }
+ },
+ "node_modules/esbuild-android-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz",
+ "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-android-arm64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz",
+ "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-darwin-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz",
+ "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-darwin-arm64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz",
+ "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-freebsd-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz",
+ "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-freebsd-arm64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz",
+ "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-32": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz",
+ "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz",
+ "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-arm": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz",
+ "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-arm64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz",
+ "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-mips64le": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz",
+ "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-ppc64le": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz",
+ "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-riscv64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz",
+ "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-linux-s390x": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz",
+ "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-netbsd-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz",
+ "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-openbsd-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz",
+ "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-sunos-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz",
+ "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-windows-32": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz",
+ "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-windows-64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz",
+ "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/esbuild-windows-arm64": {
+ "version": "0.15.18",
+ "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz",
+ "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
+ "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.2",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz",
+ "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.15",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz",
+ "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/postcss": {
+ "version": "8.5.15",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
+ "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.12",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.12",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz",
+ "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "is-core-module": "^2.16.1",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "2.80.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.80.0.tgz",
+ "integrity": "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/vite": {
+ "version": "3.2.11",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-3.2.11.tgz",
+ "integrity": "sha512-K/jGKL/PgbIgKCiJo5QbASQhFiV02X9Jh+Qq0AKCRCRKZtOTVi4t6wh75FDpGf2N9rYOnzH87OEFQNaFy6pdxQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.15.9",
+ "postcss": "^8.4.18",
+ "resolve": "^1.22.1",
+ "rollup": "^2.79.1"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ },
+ "peerDependencies": {
+ "@types/node": ">= 14",
+ "less": "*",
+ "sass": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/gui/wails-standalone/frontend/package.json b/gui/wails-standalone/frontend/package.json
new file mode 100644
index 0000000..a031aa9
--- /dev/null
+++ b/gui/wails-standalone/frontend/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "frontend",
+ "private": true,
+ "version": "0.0.0",
+ "scripts": {
+ "dev": "node ./node_modules/vite/bin/vite.js",
+ "build": "node ./node_modules/vite/bin/vite.js build",
+ "preview": "vite preview"
+ },
+ "devDependencies": {
+ "vite": "^3.0.7"
+ }
+}
\ No newline at end of file
diff --git a/gui/wails-standalone/frontend/src/app.css b/gui/wails-standalone/frontend/src/app.css
new file mode 100644
index 0000000..59d06f6
--- /dev/null
+++ b/gui/wails-standalone/frontend/src/app.css
@@ -0,0 +1,54 @@
+#logo {
+ display: block;
+ width: 50%;
+ height: 50%;
+ margin: auto;
+ padding: 10% 0 0;
+ background-position: center;
+ background-repeat: no-repeat;
+ background-size: 100% 100%;
+ background-origin: content-box;
+}
+
+.result {
+ height: 20px;
+ line-height: 20px;
+ margin: 1.5rem auto;
+}
+
+.input-box .btn {
+ width: 60px;
+ height: 30px;
+ line-height: 30px;
+ border-radius: 3px;
+ border: none;
+ margin: 0 0 0 20px;
+ padding: 0 8px;
+ cursor: pointer;
+}
+
+.input-box .btn:hover {
+ background-image: linear-gradient(to top, #cfd9df 0%, #e2ebf0 100%);
+ color: #333333;
+}
+
+.input-box .input {
+ border: none;
+ border-radius: 3px;
+ outline: none;
+ height: 30px;
+ line-height: 30px;
+ padding: 0 10px;
+ background-color: rgba(240, 240, 240, 1);
+ -webkit-font-smoothing: antialiased;
+}
+
+.input-box .input:hover {
+ border: none;
+ background-color: rgba(255, 255, 255, 1);
+}
+
+.input-box .input:focus {
+ border: none;
+ background-color: rgba(255, 255, 255, 1);
+}
\ No newline at end of file
diff --git a/gui/wails-standalone/frontend/src/assets/fonts/OFL.txt b/gui/wails-standalone/frontend/src/assets/fonts/OFL.txt
new file mode 100644
index 0000000..9cac04c
--- /dev/null
+++ b/gui/wails-standalone/frontend/src/assets/fonts/OFL.txt
@@ -0,0 +1,93 @@
+Copyright 2016 The Nunito Project Authors (contact@sansoxygen.com),
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/gui/wails-standalone/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 b/gui/wails-standalone/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2
new file mode 100644
index 0000000..2f9cc59
Binary files /dev/null and b/gui/wails-standalone/frontend/src/assets/fonts/nunito-v16-latin-regular.woff2 differ
diff --git a/gui/wails-standalone/frontend/src/assets/images/logo-dark.jpg b/gui/wails-standalone/frontend/src/assets/images/logo-dark.jpg
new file mode 100644
index 0000000..6856a0a
Binary files /dev/null and b/gui/wails-standalone/frontend/src/assets/images/logo-dark.jpg differ
diff --git a/gui/wails-standalone/frontend/src/assets/images/logo-light.png b/gui/wails-standalone/frontend/src/assets/images/logo-light.png
new file mode 100644
index 0000000..5c56201
Binary files /dev/null and b/gui/wails-standalone/frontend/src/assets/images/logo-light.png differ
diff --git a/gui/wails-standalone/frontend/src/assets/images/logo-universal.png b/gui/wails-standalone/frontend/src/assets/images/logo-universal.png
new file mode 100644
index 0000000..d63303b
Binary files /dev/null and b/gui/wails-standalone/frontend/src/assets/images/logo-universal.png differ
diff --git a/gui/wails-standalone/frontend/src/main.js b/gui/wails-standalone/frontend/src/main.js
new file mode 100644
index 0000000..05e467e
--- /dev/null
+++ b/gui/wails-standalone/frontend/src/main.js
@@ -0,0 +1,189 @@
+import {
+ CheckConnection,
+ SetFrequency,
+ Quit,
+} from "../wailsjs/go/main/App";
+
+// импорт через ES-модуль, а не строкой-путём — иначе vite не подставит
+// правильный хэшированный путь в собранном билде (сломается в проде)
+import logoDarkUrl from "./assets/images/logo-dark.jpg";
+
+(function() {
+ // === Тема (тёмная/светлая) ===
+ const themeToggle = document.getElementById('theme-toggle');
+ themeToggle.addEventListener('click', () => {
+ const cur = document.body.getAttribute('data-theme');
+ const next = cur === 'light' ? 'dark' : 'light';
+ document.body.setAttribute('data-theme', next);
+ themeToggle.textContent = next === 'light' ? '☾' : '☀';
+ });
+
+ // === Интро при запуске (лого "двери" разъезжаются) ===
+ const NATURAL_W = 640, NATURAL_H = 480;
+ const AXIS_X = 312; // ось раздела — середина носа черепа / верхнего зубца шестерни
+ const LOGO_DARK_URL = logoDarkUrl;
+
+ const introOverlay = document.getElementById('intro-overlay');
+ const doorLeft = document.querySelector('#intro-overlay .door-left');
+ const doorRight = document.querySelector('#intro-overlay .door-right');
+
+ function layoutDoors() {
+ const W = window.innerWidth;
+ const H = window.innerHeight;
+ const scale = Math.max(W / NATURAL_W, H / NATURAL_H); // object-fit: cover на весь размер окна
+ const renderW = NATURAL_W * scale;
+ const renderH = NATURAL_H * scale;
+ const offsetX = (W - renderW) / 2;
+ const offsetY = (H - renderH) / 2;
+ const axisDisplayX = offsetX + AXIS_X * scale;
+
+ const leftW = Math.max(0, Math.min(W, axisDisplayX));
+ const rightW = W - leftW;
+
+ doorLeft.style.width = leftW + 'px';
+ doorLeft.style.backgroundImage = `url(${LOGO_DARK_URL})`;
+ doorLeft.style.backgroundSize = renderW + 'px ' + renderH + 'px';
+ doorLeft.style.backgroundPosition = offsetX + 'px ' + offsetY + 'px';
+
+ doorRight.style.left = leftW + 'px';
+ doorRight.style.width = rightW + 'px';
+ doorRight.style.backgroundImage = `url(${LOGO_DARK_URL})`;
+ doorRight.style.backgroundSize = renderW + 'px ' + renderH + 'px';
+ doorRight.style.backgroundPosition = (offsetX - leftW) + 'px ' + offsetY + 'px';
+ }
+
+ const OPEN_AT = 2100; // момент начала разъезда дверей от старта интро, мс
+ const APPEAR_START = 100;
+ const APPEAR_DUR = 450;
+
+ function playIntro() {
+ introOverlay.style.display = '';
+ introOverlay.classList.remove('appear', 'open');
+ void introOverlay.offsetWidth; // reflow reset
+ layoutDoors();
+
+ setTimeout(() => introOverlay.classList.add('appear'), APPEAR_START);
+ setTimeout(() => introOverlay.classList.add('open'), OPEN_AT);
+ // прячем оверлей из потока/кликов после того как двери разъехались и погасли
+ setTimeout(() => { introOverlay.style.display = 'none'; }, OPEN_AT + 1170);
+ }
+
+ window.addEventListener('resize', layoutDoors);
+ playIntro(); // main.js подключён как type="module" — DOM уже готов на этот момент
+
+ // === Переключение вкладок ===
+ const tabButtons = document.querySelectorAll('.tab-button');
+ const tabContents = document.querySelectorAll('.tab-content');
+
+ tabButtons.forEach(button => {
+ button.addEventListener('click', () => {
+ tabButtons.forEach(btn => btn.classList.remove('active'));
+ tabContents.forEach(content => content.classList.remove('active'));
+ button.classList.add('active');
+ const tabId = button.getAttribute('data-tab');
+ document.getElementById(`tab-${tabId}`).classList.add('active');
+ });
+ });
+
+ // === Элементы интерфейса ===
+ const btnCheck = document.getElementById('btn-check');
+ const btnFreq = document.getElementById('btn-freq');
+ const btnQuit = document.getElementById('btn-quit');
+ const statusIndicator = document.getElementById('status-indicator');
+ const statusText = document.getElementById('status-text');
+ const freqLabel = document.getElementById('frequency-label');
+ const freqValue = document.getElementById('frequency-value');
+
+ // Модальное окно
+ const modal = document.getElementById('freq-modal');
+ const closeModal = document.getElementById('close-modal');
+ const freqInput = document.getElementById('freq-input');
+ const btnMinus = document.getElementById('btn-minus');
+ const btnPlus = document.getElementById('btn-plus');
+ const btnSetFreq = document.getElementById('btn-set-freq');
+ const modalError = document.getElementById('modal-error');
+
+ // === Проверка подключения ===
+ btnCheck.addEventListener('click', () => {
+ CheckConnection()
+ .then((response) => {
+ // Обновление индикатора соединения
+ statusIndicator.classList.remove('indicator-red');
+ statusIndicator.classList.add('indicator-green');
+ statusText.textContent = `Соединение установлено! ${response.status}`;
+
+ // Обновление панели частоты (текущая)
+ freqLabel.textContent = 'Текущая частота:';
+ freqValue.textContent = response.frequency;
+ })
+ .catch((err) => {
+ console.error('Ошибка CheckConnection:', err);
+ statusText.textContent = 'Ошибка: не удалось выполнить проверку';
+ });
+ });
+
+ // === Установка частоты (открытие модального окна) ===
+ btnFreq.addEventListener('click', () => {
+ modal.classList.remove('hidden');
+ // Показываем текущую частоту из панели (или 145)
+ const current = freqValue.textContent !== '---' ? freqValue.textContent : '145';
+ freqInput.value = current;
+ modalError.classList.add('hidden');
+ });
+
+ // Закрытие модального окна
+ closeModal.addEventListener('click', () => {
+ modal.classList.add('hidden');
+ });
+ window.addEventListener('click', (e) => {
+ if (e.target === modal) {
+ modal.classList.add('hidden');
+ }
+ });
+
+ // Кнопки +/- для изменения значения на 1
+ btnMinus.addEventListener('click', () => {
+ let val = parseInt(freqInput.value, 10) || 1;
+ val = Math.max(1, val - 1); // минимум 1
+ freqInput.value = val.toString();
+ });
+ btnPlus.addEventListener('click', () => {
+ let val = parseInt(freqInput.value, 10) || 1;
+ val += 1;
+ freqInput.value = val.toString();
+ });
+
+ // Кнопка "Установить"
+ btnSetFreq.addEventListener('click', () => {
+ const inputVal = freqInput.value.trim();
+ // Проверка: только цифры
+ if (!/^\d+$/.test(inputVal)) {
+ modalError.textContent = 'Вводимое значение должно быть целым положительным числом';
+ modalError.classList.remove('hidden');
+ return;
+ }
+ const num = parseInt(inputVal, 10);
+ if (num <= 0) {
+ modalError.textContent = 'Вводимое значение должно быть целым положительным числом';
+ modalError.classList.remove('hidden');
+ return;
+ }
+
+ SetFrequency(inputVal)
+ .then((newFreq) => {
+ // Обновляем панель частоты
+ freqLabel.textContent = 'Установленная частота:';
+ freqValue.textContent = newFreq;
+ modal.classList.add('hidden');
+ })
+ .catch((err) => {
+ modalError.textContent = err;
+ modalError.classList.remove('hidden');
+ });
+ });
+
+ // === Закрыть приложение ===
+ btnQuit.addEventListener('click', () => {
+ Quit();
+ });
+})();
\ No newline at end of file
diff --git a/gui/wails-standalone/frontend/src/style.css b/gui/wails-standalone/frontend/src/style.css
new file mode 100644
index 0000000..fb0ca91
--- /dev/null
+++ b/gui/wails-standalone/frontend/src/style.css
@@ -0,0 +1,343 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+html, body {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-family: Arial, sans-serif;
+ overflow: hidden;
+}
+
+/* ===================== ТЕМА (тёмная/светлая) ===================== */
+body {
+ background: radial-gradient(circle at 50% 18%, #1a1d24 0%, #0d0f13 70%);
+ transition: background 0.3s ease;
+}
+body[data-theme="light"] {
+ background: radial-gradient(circle at 50% 18%, #ffffff 0%, #dde1e7 70%);
+}
+
+#theme-toggle {
+ position: fixed;
+ top: 16px; right: 18px;
+ z-index: 500;
+ width: 34px; height: 34px;
+ border-radius: 50%;
+ border: 1px solid #333a46;
+ background: #222a37;
+ color: #ccc;
+ cursor: pointer;
+ font-size: 14px;
+ display: flex; align-items: center; justify-content: center;
+}
+body[data-theme="light"] #theme-toggle {
+ border-color: #c7cbd1;
+ background: #ffffff;
+ color: #555;
+}
+
+/* ===================== КАРТОЧКА ПРИЛОЖЕНИЯ ===================== */
+#app {
+ width: 360px;
+ border-radius: 14px;
+ box-shadow: 0 8px 28px rgba(0, 0, 0, 0.3);
+ overflow: hidden;
+ background: #1b1e24;
+ transition: background 0.3s ease;
+}
+body[data-theme="light"] #app {
+ background: #ffffff;
+}
+
+/* ---- бренд-блок ---- */
+#brand {
+ display: flex; flex-direction: column; align-items: center;
+ padding: 26px 24px 16px;
+ gap: 6px;
+}
+.logo-icon {
+ position: relative;
+ width: 64px; height: 48px; /* пропорции 640:480 исходного лого */
+ margin-bottom: 6px;
+}
+.logo-icon img {
+ position: absolute; inset: 0;
+ width: 100%; height: 100%;
+ object-fit: contain;
+}
+.logo-icon .logo-dark { mix-blend-mode: screen; display: block; }
+.logo-icon .logo-light { mix-blend-mode: multiply; display: none; }
+body[data-theme="light"] .logo-icon .logo-dark { display: none; }
+body[data-theme="light"] .logo-icon .logo-light { display: block; }
+
+#brand h1 {
+ color: #eeeeee;
+ font-size: 21px;
+ letter-spacing: 3.5px;
+ font-weight: 700;
+}
+#brand .tag {
+ color: #7a7f89;
+ font-size: 11px;
+ letter-spacing: 1.2px;
+}
+body[data-theme="light"] #brand h1 { color: #1b1d22; }
+body[data-theme="light"] #brand .tag { color: #888d97; }
+
+/* Панель вкладок (пока скрыта атрибутом hidden в HTML) */
+.tabs {
+ display: flex;
+ background: transparent;
+}
+.tabs[hidden] {
+ display: none; /* без этого .tabs{display:flex} выше перебивает атрибут hidden */
+}
+.tab-button {
+ flex: 1;
+ padding: 12px;
+ background: transparent;
+ border: none;
+ cursor: pointer;
+ font-size: 14px;
+ color: #9aa0aa;
+ transition: background 0.2s;
+}
+.tab-button:hover { background: rgba(255,255,255,0.05); }
+.tab-button.active {
+ color: #eee;
+ border-bottom: 2px solid #b8362a;
+ font-weight: bold;
+}
+body[data-theme="light"] .tab-button { color: #7a7f89; }
+body[data-theme="light"] .tab-button.active { color: #1b1d22; }
+
+/* Содержимое вкладок */
+.tab-content { display: none; padding: 4px 22px 24px; }
+.tab-content.active { display: block; }
+
+/* Вертикально расположенные кнопки */
+.buttons-vertical {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ margin-bottom: 12px;
+}
+
+.action-button {
+ padding: 11px;
+ font-size: 14px;
+ cursor: pointer;
+ border: 1px solid #2a2d34;
+ border-radius: 8px;
+ background-color: #15171c;
+ color: #e8e8e8;
+ transition: background-color 0.2s, border-color 0.2s;
+}
+.action-button:hover {
+ background-color: #1d2027;
+ border-color: #3a3e46;
+}
+body[data-theme="light"] .action-button {
+ background-color: #ffffff;
+ border-color: #ccd1d9;
+ color: #22242a;
+}
+body[data-theme="light"] .action-button:hover {
+ background-color: #f0f2f5;
+ border-color: #aeb4bd;
+}
+
+.close-button {
+ background-color: #b8362a;
+ color: white;
+ border-color: #8f2a20;
+}
+.close-button:hover { background-color: #c73f31; }
+body[data-theme="light"] .close-button {
+ background-color: #e74c3c;
+ border-color: #c0392b;
+ color: #fff;
+}
+body[data-theme="light"] .close-button:hover { background-color: #d8432f; }
+
+/* Панель частоты */
+#frequency-panel {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 9px 13px;
+ background-color: #15171c;
+ border: 1px solid #2a2d34;
+ border-radius: 8px;
+ margin-bottom: 9px;
+}
+#frequency-label { font-weight: bold; color: #9aa0aa; font-size: 13px; }
+#frequency-value { font-size: 17px; color: #f2f2f2; }
+body[data-theme="light"] #frequency-panel { background-color: #ffffff; border-color: #ccd1d9; }
+body[data-theme="light"] #frequency-label { color: #7a7f89; }
+body[data-theme="light"] #frequency-value { color: #1b1d22; }
+
+/* Панель статуса */
+#status-panel {
+ display: flex;
+ align-items: center;
+ gap: 9px;
+ padding: 9px 13px;
+ background-color: #15171c;
+ border: 1px solid #2a2d34;
+ border-radius: 8px;
+}
+body[data-theme="light"] #status-panel { background-color: #ffffff; border-color: #ccd1d9; }
+
+.indicator {
+ width: 14px;
+ height: 14px;
+ border-radius: 50%;
+ display: inline-block;
+}
+.indicator-red { background-color: #e74c3c; }
+.indicator-green { background-color: #2ecc71; }
+
+#status-text { font-size: 14px; color: #ddd; }
+body[data-theme="light"] #status-text { color: #333; }
+
+/* ===================== ИНТРО (двери) ===================== */
+#intro-overlay {
+ position: fixed;
+ inset: 0;
+ z-index: 1000;
+ background: #000; /* подстраховка: перекрывает всё, даже пока JS ещё не выставил размеры дверей */
+ transform: scale(1.04);
+}
+#intro-overlay.appear {
+ animation: introPop 0.45s cubic-bezier(.2,.9,.3,1) forwards;
+}
+@keyframes introPop { to { transform: scale(1); } }
+
+.door {
+ position: absolute;
+ top: 0; bottom: 0;
+ overflow: hidden;
+ background-color: #000;
+ background-repeat: no-repeat;
+ transition: transform 1.17s cubic-bezier(.65,0,.35,1);
+}
+.door-left { left: 0; }
+.door-right { left: 0; } /* left/width выставляются из JS под реальный размер окна */
+
+#intro-overlay.open .door-left { transform: translateX(-100%); }
+#intro-overlay.open .door-right { transform: translateX(100%); }
+#intro-overlay.open {
+ transition: opacity 0.45s ease 0.65s;
+ opacity: 0;
+ pointer-events: none;
+}
+
+/* Модальное окно */
+.modal {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0,0,0,0.5);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 100;
+}
+
+.modal.hidden {
+ display: none;
+}
+
+.modal-content {
+ background: #1b1e24;
+ padding: 20px;
+ border-radius: 8px;
+ width: 300px;
+ box-shadow: 0 5px 15px rgba(0,0,0,0.4);
+ position: relative;
+ color: #eee;
+}
+body[data-theme="light"] .modal-content {
+ background: #ffffff;
+ color: #222;
+}
+
+.close-modal {
+ position: absolute;
+ top: 10px;
+ right: 15px;
+ font-size: 22px;
+ cursor: pointer;
+ color: #999;
+}
+.close-modal:hover { color: #eee; }
+body[data-theme="light"] .close-modal { color: #777; }
+body[data-theme="light"] .close-modal:hover { color: #333; }
+
+.modal-content h3 {
+ margin-bottom: 15px;
+ text-align: center;
+}
+
+.freq-input-group {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+ margin-bottom: 15px;
+}
+
+.freq-btn {
+ width: 40px;
+ height: 40px;
+ font-size: 20px;
+ background: #b8362a;
+ color: white;
+ border: none;
+ border-radius: 4px;
+ cursor: pointer;
+ transition: background 0.2s;
+}
+.freq-btn:hover { background: #c73f31; }
+
+#freq-input {
+ width: 100px;
+ padding: 8px;
+ font-size: 18px;
+ text-align: center;
+ border: 1px solid #444;
+ border-radius: 4px;
+ background: #15171c;
+ color: #eee;
+}
+body[data-theme="light"] #freq-input {
+ border-color: #ccc;
+ background: #fff;
+ color: #222;
+}
+
+#btn-set-freq {
+ display: block;
+ width: 100%;
+ margin-top: 10px;
+}
+
+.error-msg {
+ color: #e74c3c;
+ margin-top: 8px;
+ text-align: center;
+ font-size: 14px;
+}
+
+.error-msg.hidden {
+ display: none;
+}
diff --git a/gui/wails-standalone/frontend/wailsjs/go/main/App.d.ts b/gui/wails-standalone/frontend/wailsjs/go/main/App.d.ts
new file mode 100755
index 0000000..677227e
--- /dev/null
+++ b/gui/wails-standalone/frontend/wailsjs/go/main/App.d.ts
@@ -0,0 +1,9 @@
+// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
+// This file is automatically generated. DO NOT EDIT
+import {main} from '../models';
+
+export function CheckConnection():Promise;
+
+export function Quit():Promise;
+
+export function SetFrequency(arg1:string):Promise;
diff --git a/gui/wails-standalone/frontend/wailsjs/go/main/App.js b/gui/wails-standalone/frontend/wailsjs/go/main/App.js
new file mode 100755
index 0000000..5839bb1
--- /dev/null
+++ b/gui/wails-standalone/frontend/wailsjs/go/main/App.js
@@ -0,0 +1,15 @@
+// @ts-check
+// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
+// This file is automatically generated. DO NOT EDIT
+
+export function CheckConnection() {
+ return window['go']['main']['App']['CheckConnection']();
+}
+
+export function Quit() {
+ return window['go']['main']['App']['Quit']();
+}
+
+export function SetFrequency(arg1) {
+ return window['go']['main']['App']['SetFrequency'](arg1);
+}
diff --git a/gui/wails-standalone/frontend/wailsjs/go/models.ts b/gui/wails-standalone/frontend/wailsjs/go/models.ts
new file mode 100755
index 0000000..29e2d9b
--- /dev/null
+++ b/gui/wails-standalone/frontend/wailsjs/go/models.ts
@@ -0,0 +1,19 @@
+export namespace main {
+
+ export class DeviceResponse {
+ status: string;
+ frequency: string;
+
+ static createFrom(source: any = {}) {
+ return new DeviceResponse(source);
+ }
+
+ constructor(source: any = {}) {
+ if ('string' === typeof source) source = JSON.parse(source);
+ this.status = source["status"];
+ this.frequency = source["frequency"];
+ }
+ }
+
+}
+
diff --git a/gui/wails-standalone/frontend/wailsjs/runtime/package.json b/gui/wails-standalone/frontend/wailsjs/runtime/package.json
new file mode 100644
index 0000000..1e7c8a5
--- /dev/null
+++ b/gui/wails-standalone/frontend/wailsjs/runtime/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "@wailsapp/runtime",
+ "version": "2.0.0",
+ "description": "Wails Javascript runtime library",
+ "main": "runtime.js",
+ "types": "runtime.d.ts",
+ "scripts": {
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/wailsapp/wails.git"
+ },
+ "keywords": [
+ "Wails",
+ "Javascript",
+ "Go"
+ ],
+ "author": "Lea Anthony ",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/wailsapp/wails/issues"
+ },
+ "homepage": "https://github.com/wailsapp/wails#readme"
+}
diff --git a/gui/wails-standalone/frontend/wailsjs/runtime/runtime.d.ts b/gui/wails-standalone/frontend/wailsjs/runtime/runtime.d.ts
new file mode 100644
index 0000000..3bbea84
--- /dev/null
+++ b/gui/wails-standalone/frontend/wailsjs/runtime/runtime.d.ts
@@ -0,0 +1,330 @@
+/*
+ _ __ _ __
+| | / /___ _(_) /____
+| | /| / / __ `/ / / ___/
+| |/ |/ / /_/ / / (__ )
+|__/|__/\__,_/_/_/____/
+The electron alternative for Go
+(c) Lea Anthony 2019-present
+*/
+
+export interface Position {
+ x: number;
+ y: number;
+}
+
+export interface Size {
+ w: number;
+ h: number;
+}
+
+export interface Screen {
+ isCurrent: boolean;
+ isPrimary: boolean;
+ width : number
+ height : number
+}
+
+// Environment information such as platform, buildtype, ...
+export interface EnvironmentInfo {
+ buildType: string;
+ platform: string;
+ arch: string;
+}
+
+// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit)
+// emits the given event. Optional data may be passed with the event.
+// This will trigger any event listeners.
+export function EventsEmit(eventName: string, ...data: any): void;
+
+// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name.
+export function EventsOn(eventName: string, callback: (...data: any) => void): () => void;
+
+// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple)
+// sets up a listener for the given event name, but will only trigger a given number times.
+export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void;
+
+// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce)
+// sets up a listener for the given event name, but will only trigger once.
+export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void;
+
+// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff)
+// unregisters the listener for the given event name.
+export function EventsOff(eventName: string, ...additionalEventNames: string[]): void;
+
+// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall)
+// unregisters all listeners.
+export function EventsOffAll(): void;
+
+// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint)
+// logs the given message as a raw message
+export function LogPrint(message: string): void;
+
+// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace)
+// logs the given message at the `trace` log level.
+export function LogTrace(message: string): void;
+
+// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug)
+// logs the given message at the `debug` log level.
+export function LogDebug(message: string): void;
+
+// [LogError](https://wails.io/docs/reference/runtime/log#logerror)
+// logs the given message at the `error` log level.
+export function LogError(message: string): void;
+
+// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal)
+// logs the given message at the `fatal` log level.
+// The application will quit after calling this method.
+export function LogFatal(message: string): void;
+
+// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo)
+// logs the given message at the `info` log level.
+export function LogInfo(message: string): void;
+
+// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning)
+// logs the given message at the `warning` log level.
+export function LogWarning(message: string): void;
+
+// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload)
+// Forces a reload by the main application as well as connected browsers.
+export function WindowReload(): void;
+
+// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp)
+// Reloads the application frontend.
+export function WindowReloadApp(): void;
+
+// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop)
+// Sets the window AlwaysOnTop or not on top.
+export function WindowSetAlwaysOnTop(b: boolean): void;
+
+// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme)
+// *Windows only*
+// Sets window theme to system default (dark/light).
+export function WindowSetSystemDefaultTheme(): void;
+
+// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme)
+// *Windows only*
+// Sets window to light theme.
+export function WindowSetLightTheme(): void;
+
+// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme)
+// *Windows only*
+// Sets window to dark theme.
+export function WindowSetDarkTheme(): void;
+
+// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter)
+// Centers the window on the monitor the window is currently on.
+export function WindowCenter(): void;
+
+// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle)
+// Sets the text in the window title bar.
+export function WindowSetTitle(title: string): void;
+
+// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen)
+// Makes the window full screen.
+export function WindowFullscreen(): void;
+
+// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen)
+// Restores the previous window dimensions and position prior to full screen.
+export function WindowUnfullscreen(): void;
+
+// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen)
+// Returns the state of the window, i.e. whether the window is in full screen mode or not.
+export function WindowIsFullscreen(): Promise;
+
+// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize)
+// Sets the width and height of the window.
+export function WindowSetSize(width: number, height: number): void;
+
+// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize)
+// Gets the width and height of the window.
+export function WindowGetSize(): Promise;
+
+// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize)
+// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions.
+// Setting a size of 0,0 will disable this constraint.
+export function WindowSetMaxSize(width: number, height: number): void;
+
+// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize)
+// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions.
+// Setting a size of 0,0 will disable this constraint.
+export function WindowSetMinSize(width: number, height: number): void;
+
+// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition)
+// Sets the window position relative to the monitor the window is currently on.
+export function WindowSetPosition(x: number, y: number): void;
+
+// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition)
+// Gets the window position relative to the monitor the window is currently on.
+export function WindowGetPosition(): Promise;
+
+// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide)
+// Hides the window.
+export function WindowHide(): void;
+
+// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow)
+// Shows the window, if it is currently hidden.
+export function WindowShow(): void;
+
+// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise)
+// Maximises the window to fill the screen.
+export function WindowMaximise(): void;
+
+// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise)
+// Toggles between Maximised and UnMaximised.
+export function WindowToggleMaximise(): void;
+
+// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise)
+// Restores the window to the dimensions and position prior to maximising.
+export function WindowUnmaximise(): void;
+
+// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised)
+// Returns the state of the window, i.e. whether the window is maximised or not.
+export function WindowIsMaximised(): Promise;
+
+// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise)
+// Minimises the window.
+export function WindowMinimise(): void;
+
+// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise)
+// Restores the window to the dimensions and position prior to minimising.
+export function WindowUnminimise(): void;
+
+// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised)
+// Returns the state of the window, i.e. whether the window is minimised or not.
+export function WindowIsMinimised(): Promise;
+
+// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal)
+// Returns the state of the window, i.e. whether the window is normal or not.
+export function WindowIsNormal(): Promise;
+
+// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour)
+// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels.
+export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void;
+
+// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall)
+// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system.
+export function ScreenGetAll(): Promise;
+
+// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl)
+// Opens the given URL in the system browser.
+export function BrowserOpenURL(url: string): void;
+
+// [Environment](https://wails.io/docs/reference/runtime/intro#environment)
+// Returns information about the environment
+export function Environment(): Promise;
+
+// [Quit](https://wails.io/docs/reference/runtime/intro#quit)
+// Quits the application.
+export function Quit(): void;
+
+// [Hide](https://wails.io/docs/reference/runtime/intro#hide)
+// Hides the application.
+export function Hide(): void;
+
+// [Show](https://wails.io/docs/reference/runtime/intro#show)
+// Shows the application.
+export function Show(): void;
+
+// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext)
+// Returns the current text stored on clipboard
+export function ClipboardGetText(): Promise;
+
+// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext)
+// Sets a text on the clipboard
+export function ClipboardSetText(text: string): Promise;
+
+// [OnFileDrop](https://wails.io/docs/reference/runtime/draganddrop#onfiledrop)
+// OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
+export function OnFileDrop(callback: (x: number, y: number ,paths: string[]) => void, useDropTarget: boolean) :void
+
+// [OnFileDropOff](https://wails.io/docs/reference/runtime/draganddrop#dragandddropoff)
+// OnFileDropOff removes the drag and drop listeners and handlers.
+export function OnFileDropOff() :void
+
+// Check if the file path resolver is available
+export function CanResolveFilePaths(): boolean;
+
+// Resolves file paths for an array of files
+export function ResolveFilePaths(files: File[]): void
+
+// Notification types
+export interface NotificationOptions {
+ id: string;
+ title: string;
+ subtitle?: string; // macOS and Linux only
+ body?: string;
+ categoryId?: string;
+ data?: { [key: string]: any };
+}
+
+export interface NotificationAction {
+ id?: string;
+ title?: string;
+ destructive?: boolean; // macOS-specific
+}
+
+export interface NotificationCategory {
+ id?: string;
+ actions?: NotificationAction[];
+ hasReplyField?: boolean;
+ replyPlaceholder?: string;
+ replyButtonTitle?: string;
+}
+
+// [InitializeNotifications](https://wails.io/docs/reference/runtime/notification#initializenotifications)
+// Initializes the notification service for the application.
+// This must be called before sending any notifications.
+export function InitializeNotifications(): Promise;
+
+// [CleanupNotifications](https://wails.io/docs/reference/runtime/notification#cleanupnotifications)
+// Cleans up notification resources and releases any held connections.
+export function CleanupNotifications(): Promise;
+
+// [IsNotificationAvailable](https://wails.io/docs/reference/runtime/notification#isnotificationavailable)
+// Checks if notifications are available on the current platform.
+export function IsNotificationAvailable(): Promise;
+
+// [RequestNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#requestnotificationauthorization)
+// Requests notification authorization from the user (macOS only).
+export function RequestNotificationAuthorization(): Promise;
+
+// [CheckNotificationAuthorization](https://wails.io/docs/reference/runtime/notification#checknotificationauthorization)
+// Checks the current notification authorization status (macOS only).
+export function CheckNotificationAuthorization(): Promise;
+
+// [SendNotification](https://wails.io/docs/reference/runtime/notification#sendnotification)
+// Sends a basic notification with the given options.
+export function SendNotification(options: NotificationOptions): Promise;
+
+// [SendNotificationWithActions](https://wails.io/docs/reference/runtime/notification#sendnotificationwithactions)
+// Sends a notification with action buttons. Requires a registered category.
+export function SendNotificationWithActions(options: NotificationOptions): Promise;
+
+// [RegisterNotificationCategory](https://wails.io/docs/reference/runtime/notification#registernotificationcategory)
+// Registers a notification category that can be used with SendNotificationWithActions.
+export function RegisterNotificationCategory(category: NotificationCategory): Promise;
+
+// [RemoveNotificationCategory](https://wails.io/docs/reference/runtime/notification#removenotificationcategory)
+// Removes a previously registered notification category.
+export function RemoveNotificationCategory(categoryId: string): Promise;
+
+// [RemoveAllPendingNotifications](https://wails.io/docs/reference/runtime/notification#removeallpendingnotifications)
+// Removes all pending notifications from the notification center.
+export function RemoveAllPendingNotifications(): Promise;
+
+// [RemovePendingNotification](https://wails.io/docs/reference/runtime/notification#removependingnotification)
+// Removes a specific pending notification by its identifier.
+export function RemovePendingNotification(identifier: string): Promise;
+
+// [RemoveAllDeliveredNotifications](https://wails.io/docs/reference/runtime/notification#removealldeliverednotifications)
+// Removes all delivered notifications from the notification center.
+export function RemoveAllDeliveredNotifications(): Promise;
+
+// [RemoveDeliveredNotification](https://wails.io/docs/reference/runtime/notification#removedeliverednotification)
+// Removes a specific delivered notification by its identifier.
+export function RemoveDeliveredNotification(identifier: string): Promise;
+
+// [RemoveNotification](https://wails.io/docs/reference/runtime/notification#removenotification)
+// Removes a notification by its identifier (cross-platform convenience function).
+export function RemoveNotification(identifier: string): Promise;
\ No newline at end of file
diff --git a/gui/wails-standalone/frontend/wailsjs/runtime/runtime.js b/gui/wails-standalone/frontend/wailsjs/runtime/runtime.js
new file mode 100644
index 0000000..556621e
--- /dev/null
+++ b/gui/wails-standalone/frontend/wailsjs/runtime/runtime.js
@@ -0,0 +1,298 @@
+/*
+ _ __ _ __
+| | / /___ _(_) /____
+| | /| / / __ `/ / / ___/
+| |/ |/ / /_/ / / (__ )
+|__/|__/\__,_/_/_/____/
+The electron alternative for Go
+(c) Lea Anthony 2019-present
+*/
+
+export function LogPrint(message) {
+ window.runtime.LogPrint(message);
+}
+
+export function LogTrace(message) {
+ window.runtime.LogTrace(message);
+}
+
+export function LogDebug(message) {
+ window.runtime.LogDebug(message);
+}
+
+export function LogInfo(message) {
+ window.runtime.LogInfo(message);
+}
+
+export function LogWarning(message) {
+ window.runtime.LogWarning(message);
+}
+
+export function LogError(message) {
+ window.runtime.LogError(message);
+}
+
+export function LogFatal(message) {
+ window.runtime.LogFatal(message);
+}
+
+export function EventsOnMultiple(eventName, callback, maxCallbacks) {
+ return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks);
+}
+
+export function EventsOn(eventName, callback) {
+ return EventsOnMultiple(eventName, callback, -1);
+}
+
+export function EventsOff(eventName, ...additionalEventNames) {
+ return window.runtime.EventsOff(eventName, ...additionalEventNames);
+}
+
+export function EventsOffAll() {
+ return window.runtime.EventsOffAll();
+}
+
+export function EventsOnce(eventName, callback) {
+ return EventsOnMultiple(eventName, callback, 1);
+}
+
+export function EventsEmit(eventName) {
+ let args = [eventName].slice.call(arguments);
+ return window.runtime.EventsEmit.apply(null, args);
+}
+
+export function WindowReload() {
+ window.runtime.WindowReload();
+}
+
+export function WindowReloadApp() {
+ window.runtime.WindowReloadApp();
+}
+
+export function WindowSetAlwaysOnTop(b) {
+ window.runtime.WindowSetAlwaysOnTop(b);
+}
+
+export function WindowSetSystemDefaultTheme() {
+ window.runtime.WindowSetSystemDefaultTheme();
+}
+
+export function WindowSetLightTheme() {
+ window.runtime.WindowSetLightTheme();
+}
+
+export function WindowSetDarkTheme() {
+ window.runtime.WindowSetDarkTheme();
+}
+
+export function WindowCenter() {
+ window.runtime.WindowCenter();
+}
+
+export function WindowSetTitle(title) {
+ window.runtime.WindowSetTitle(title);
+}
+
+export function WindowFullscreen() {
+ window.runtime.WindowFullscreen();
+}
+
+export function WindowUnfullscreen() {
+ window.runtime.WindowUnfullscreen();
+}
+
+export function WindowIsFullscreen() {
+ return window.runtime.WindowIsFullscreen();
+}
+
+export function WindowGetSize() {
+ return window.runtime.WindowGetSize();
+}
+
+export function WindowSetSize(width, height) {
+ window.runtime.WindowSetSize(width, height);
+}
+
+export function WindowSetMaxSize(width, height) {
+ window.runtime.WindowSetMaxSize(width, height);
+}
+
+export function WindowSetMinSize(width, height) {
+ window.runtime.WindowSetMinSize(width, height);
+}
+
+export function WindowSetPosition(x, y) {
+ window.runtime.WindowSetPosition(x, y);
+}
+
+export function WindowGetPosition() {
+ return window.runtime.WindowGetPosition();
+}
+
+export function WindowHide() {
+ window.runtime.WindowHide();
+}
+
+export function WindowShow() {
+ window.runtime.WindowShow();
+}
+
+export function WindowMaximise() {
+ window.runtime.WindowMaximise();
+}
+
+export function WindowToggleMaximise() {
+ window.runtime.WindowToggleMaximise();
+}
+
+export function WindowUnmaximise() {
+ window.runtime.WindowUnmaximise();
+}
+
+export function WindowIsMaximised() {
+ return window.runtime.WindowIsMaximised();
+}
+
+export function WindowMinimise() {
+ window.runtime.WindowMinimise();
+}
+
+export function WindowUnminimise() {
+ window.runtime.WindowUnminimise();
+}
+
+export function WindowSetBackgroundColour(R, G, B, A) {
+ window.runtime.WindowSetBackgroundColour(R, G, B, A);
+}
+
+export function ScreenGetAll() {
+ return window.runtime.ScreenGetAll();
+}
+
+export function WindowIsMinimised() {
+ return window.runtime.WindowIsMinimised();
+}
+
+export function WindowIsNormal() {
+ return window.runtime.WindowIsNormal();
+}
+
+export function BrowserOpenURL(url) {
+ window.runtime.BrowserOpenURL(url);
+}
+
+export function Environment() {
+ return window.runtime.Environment();
+}
+
+export function Quit() {
+ window.runtime.Quit();
+}
+
+export function Hide() {
+ window.runtime.Hide();
+}
+
+export function Show() {
+ window.runtime.Show();
+}
+
+export function ClipboardGetText() {
+ return window.runtime.ClipboardGetText();
+}
+
+export function ClipboardSetText(text) {
+ return window.runtime.ClipboardSetText(text);
+}
+
+/**
+ * Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
+ *
+ * @export
+ * @callback OnFileDropCallback
+ * @param {number} x - x coordinate of the drop
+ * @param {number} y - y coordinate of the drop
+ * @param {string[]} paths - A list of file paths.
+ */
+
+/**
+ * OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
+ *
+ * @export
+ * @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
+ * @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target)
+ */
+export function OnFileDrop(callback, useDropTarget) {
+ return window.runtime.OnFileDrop(callback, useDropTarget);
+}
+
+/**
+ * OnFileDropOff removes the drag and drop listeners and handlers.
+ */
+export function OnFileDropOff() {
+ return window.runtime.OnFileDropOff();
+}
+
+export function CanResolveFilePaths() {
+ return window.runtime.CanResolveFilePaths();
+}
+
+export function ResolveFilePaths(files) {
+ return window.runtime.ResolveFilePaths(files);
+}
+
+export function InitializeNotifications() {
+ return window.runtime.InitializeNotifications();
+}
+
+export function CleanupNotifications() {
+ return window.runtime.CleanupNotifications();
+}
+
+export function IsNotificationAvailable() {
+ return window.runtime.IsNotificationAvailable();
+}
+
+export function RequestNotificationAuthorization() {
+ return window.runtime.RequestNotificationAuthorization();
+}
+
+export function CheckNotificationAuthorization() {
+ return window.runtime.CheckNotificationAuthorization();
+}
+
+export function SendNotification(options) {
+ return window.runtime.SendNotification(options);
+}
+
+export function SendNotificationWithActions(options) {
+ return window.runtime.SendNotificationWithActions(options);
+}
+
+export function RegisterNotificationCategory(category) {
+ return window.runtime.RegisterNotificationCategory(category);
+}
+
+export function RemoveNotificationCategory(categoryId) {
+ return window.runtime.RemoveNotificationCategory(categoryId);
+}
+
+export function RemoveAllPendingNotifications() {
+ return window.runtime.RemoveAllPendingNotifications();
+}
+
+export function RemovePendingNotification(identifier) {
+ return window.runtime.RemovePendingNotification(identifier);
+}
+
+export function RemoveAllDeliveredNotifications() {
+ return window.runtime.RemoveAllDeliveredNotifications();
+}
+
+export function RemoveDeliveredNotification(identifier) {
+ return window.runtime.RemoveDeliveredNotification(identifier);
+}
+
+export function RemoveNotification(identifier) {
+ return window.runtime.RemoveNotification(identifier);
+}
\ No newline at end of file
diff --git a/gui/wails/frontend/dist/assets/index.02c817da.js b/gui/wails/frontend/dist/assets/index.02c817da.js
new file mode 100644
index 0000000..dc2b94c
--- /dev/null
+++ b/gui/wails/frontend/dist/assets/index.02c817da.js
@@ -0,0 +1 @@
+(function(){const d=document.createElement("link").relList;if(d&&d.supports&&d.supports("modulepreload"))return;for(const e of document.querySelectorAll('link[rel="modulepreload"]'))f(e);new MutationObserver(e=>{for(const n of e)if(n.type==="childList")for(const s of n.addedNodes)s.tagName==="LINK"&&s.rel==="modulepreload"&&f(s)}).observe(document,{childList:!0,subtree:!0});function m(e){const n={};return e.integrity&&(n.integrity=e.integrity),e.referrerpolicy&&(n.referrerPolicy=e.referrerpolicy),e.crossorigin==="use-credentials"?n.credentials="include":e.crossorigin==="anonymous"?n.credentials="omit":n.credentials="same-origin",n}function f(e){if(e.ep)return;e.ep=!0;const n=m(e);fetch(e.href,n)}})();function N(){return window.go.main.App.CheckConnection()}function F(){return window.go.main.App.Quit()}function $(a){return window.go.main.App.SetFrequency(a)}const D="/assets/logo-dark.ff9a8bc7.jpg";(function(){const a=document.getElementById("theme-toggle");a.addEventListener("click",()=>{const c=document.body.getAttribute("data-theme")==="light"?"dark":"light";document.body.setAttribute("data-theme",c),a.textContent=c==="light"?"\u263E":"\u2600"});const d=640,m=480,f=312,e=D,n=document.getElementById("intro-overlay"),s=document.querySelector("#intro-overlay .door-left"),l=document.querySelector("#intro-overlay .door-right");function L(){const t=window.innerWidth,c=window.innerHeight,o=Math.max(t/d,c/m),h=d*o,p=m*o,v=(t-h)/2,q=(c-p)/2,W=v+f*o,g=Math.max(0,Math.min(t,W)),_=t-g;s.style.width=g+"px",s.style.backgroundImage=`url(${e})`,s.style.backgroundSize=h+"px "+p+"px",s.style.backgroundPosition=v+"px "+q+"px",l.style.left=g+"px",l.style.width=_+"px",l.style.backgroundImage=`url(${e})`,l.style.backgroundSize=h+"px "+p+"px",l.style.backgroundPosition=v-g+"px "+q+"px"}const b=2100,C=100;function A(){n.style.display="",n.classList.remove("appear","open"),n.offsetWidth,L(),setTimeout(()=>n.classList.add("appear"),C),setTimeout(()=>n.classList.add("open"),b),setTimeout(()=>{n.style.display="none"},b+1170)}window.addEventListener("resize",L),A();const E=document.querySelectorAll(".tab-button"),B=document.querySelectorAll(".tab-content");E.forEach(t=>{t.addEventListener("click",()=>{E.forEach(o=>o.classList.remove("active")),B.forEach(o=>o.classList.remove("active")),t.classList.add("active");const c=t.getAttribute("data-tab");document.getElementById(`tab-${c}`).classList.add("active")})});const w=document.getElementById("btn-check"),S=document.getElementById("btn-freq"),T=document.getElementById("btn-quit"),x=document.getElementById("status-indicator"),I=document.getElementById("status-text"),k=document.getElementById("frequency-label"),y=document.getElementById("frequency-value"),u=document.getElementById("freq-modal"),O=document.getElementById("close-modal"),i=document.getElementById("freq-input"),P=document.getElementById("btn-minus"),M=document.getElementById("btn-plus"),R=document.getElementById("btn-set-freq"),r=document.getElementById("modal-error");w.addEventListener("click",()=>{N().then(t=>{x.classList.remove("indicator-red"),x.classList.add("indicator-green"),I.textContent=`\u0421\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E! ${t.status}`,k.textContent="\u0422\u0435\u043A\u0443\u0449\u0430\u044F \u0447\u0430\u0441\u0442\u043E\u0442\u0430:",y.textContent=t.frequency}).catch(t=>{console.error("\u041E\u0448\u0438\u0431\u043A\u0430 CheckConnection:",t),I.textContent="\u041E\u0448\u0438\u0431\u043A\u0430: \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u044C \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443"})}),S.addEventListener("click",()=>{u.classList.remove("hidden");const t=y.textContent!=="---"?y.textContent:"145";i.value=t,r.classList.add("hidden")}),O.addEventListener("click",()=>{u.classList.add("hidden")}),window.addEventListener("click",t=>{t.target===u&&u.classList.add("hidden")}),P.addEventListener("click",()=>{let t=parseInt(i.value,10)||1;t=Math.max(1,t-1),i.value=t.toString()}),M.addEventListener("click",()=>{let t=parseInt(i.value,10)||1;t+=1,i.value=t.toString()}),R.addEventListener("click",()=>{const t=i.value.trim();if(!/^\d+$/.test(t)){r.textContent="\u0412\u0432\u043E\u0434\u0438\u043C\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u0446\u0435\u043B\u044B\u043C \u043F\u043E\u043B\u043E\u0436\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u043C \u0447\u0438\u0441\u043B\u043E\u043C",r.classList.remove("hidden");return}if(parseInt(t,10)<=0){r.textContent="\u0412\u0432\u043E\u0434\u0438\u043C\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u0446\u0435\u043B\u044B\u043C \u043F\u043E\u043B\u043E\u0436\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u043C \u0447\u0438\u0441\u043B\u043E\u043C",r.classList.remove("hidden");return}$(t).then(o=>{k.textContent="\u0423\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u0430\u044F \u0447\u0430\u0441\u0442\u043E\u0442\u0430:",y.textContent=o,u.classList.add("hidden")}).catch(o=>{r.textContent=o,r.classList.remove("hidden")})}),T.addEventListener("click",()=>{F()})})();
diff --git a/gui/wails/frontend/dist/assets/index.41fde905.css b/gui/wails/frontend/dist/assets/index.41fde905.css
new file mode 100644
index 0000000..b457202
--- /dev/null
+++ b/gui/wails/frontend/dist/assets/index.41fde905.css
@@ -0,0 +1 @@
+*{margin:0;padding:0;box-sizing:border-box}html,body{width:100%;height:100%;display:flex;justify-content:center;align-items:center;font-family:Arial,sans-serif;overflow:hidden}body{background:radial-gradient(circle at 50% 18%,#1a1d24 0%,#0d0f13 70%);transition:background .3s ease}body[data-theme=light]{background:radial-gradient(circle at 50% 18%,#ffffff 0%,#dde1e7 70%)}#theme-toggle{position:fixed;top:16px;right:18px;z-index:500;width:34px;height:34px;border-radius:50%;border:1px solid #333a46;background:#222a37;color:#ccc;cursor:pointer;font-size:14px;display:flex;align-items:center;justify-content:center}body[data-theme=light] #theme-toggle{border-color:#c7cbd1;background:#ffffff;color:#555}#app{width:360px;border-radius:14px;box-shadow:0 8px 28px #0000004d;overflow:hidden;background:#1b1e24;transition:background .3s ease}body[data-theme=light] #app{background:#ffffff}#brand{display:flex;flex-direction:column;align-items:center;padding:26px 24px 16px;gap:6px}.logo-icon{position:relative;width:64px;height:48px;margin-bottom:6px}.logo-icon img{position:absolute;inset:0;width:100%;height:100%;object-fit:contain}.logo-icon .logo-dark{mix-blend-mode:screen;display:block}.logo-icon .logo-light{mix-blend-mode:multiply;display:none}body[data-theme=light] .logo-icon .logo-dark{display:none}body[data-theme=light] .logo-icon .logo-light{display:block}#brand h1{color:#eee;font-size:21px;letter-spacing:3.5px;font-weight:700}#brand .tag{color:#7a7f89;font-size:11px;letter-spacing:1.2px}body[data-theme=light] #brand h1{color:#1b1d22}body[data-theme=light] #brand .tag{color:#888d97}.tabs{display:flex;background:transparent}.tabs[hidden]{display:none}.tab-button{flex:1;padding:12px;background:transparent;border:none;cursor:pointer;font-size:14px;color:#9aa0aa;transition:background .2s}.tab-button:hover{background:rgba(255,255,255,.05)}.tab-button.active{color:#eee;border-bottom:2px solid #b8362a;font-weight:700}body[data-theme=light] .tab-button{color:#7a7f89}body[data-theme=light] .tab-button.active{color:#1b1d22}.tab-content{display:none;padding:4px 22px 24px}.tab-content.active{display:block}.buttons-vertical{display:flex;flex-direction:column;gap:10px;margin-bottom:12px}.action-button{padding:11px;font-size:14px;cursor:pointer;border:1px solid #2a2d34;border-radius:8px;background-color:#15171c;color:#e8e8e8;transition:background-color .2s,border-color .2s}.action-button:hover{background-color:#1d2027;border-color:#3a3e46}body[data-theme=light] .action-button{background-color:#fff;border-color:#ccd1d9;color:#22242a}body[data-theme=light] .action-button:hover{background-color:#f0f2f5;border-color:#aeb4bd}.close-button{background-color:#b8362a;color:#fff;border-color:#8f2a20}.close-button:hover{background-color:#c73f31}body[data-theme=light] .close-button{background-color:#e74c3c;border-color:#c0392b;color:#fff}body[data-theme=light] .close-button:hover{background-color:#d8432f}#frequency-panel{display:flex;justify-content:space-between;align-items:center;padding:9px 13px;background-color:#15171c;border:1px solid #2a2d34;border-radius:8px;margin-bottom:9px}#frequency-label{font-weight:700;color:#9aa0aa;font-size:13px}#frequency-value{font-size:17px;color:#f2f2f2}body[data-theme=light] #frequency-panel{background-color:#fff;border-color:#ccd1d9}body[data-theme=light] #frequency-label{color:#7a7f89}body[data-theme=light] #frequency-value{color:#1b1d22}#status-panel{display:flex;align-items:center;gap:9px;padding:9px 13px;background-color:#15171c;border:1px solid #2a2d34;border-radius:8px}body[data-theme=light] #status-panel{background-color:#fff;border-color:#ccd1d9}.indicator{width:14px;height:14px;border-radius:50%;display:inline-block}.indicator-red{background-color:#e74c3c}.indicator-green{background-color:#2ecc71}#status-text{font-size:14px;color:#ddd}body[data-theme=light] #status-text{color:#333}#intro-overlay{position:fixed;inset:0;z-index:1000;background:#000;transform:scale(1.04)}#intro-overlay.appear{animation:introPop .45s cubic-bezier(.2,.9,.3,1) forwards}@keyframes introPop{to{transform:scale(1)}}.door{position:absolute;top:0;bottom:0;overflow:hidden;background-color:#000;background-repeat:no-repeat;transition:transform 1.17s cubic-bezier(.65,0,.35,1)}.door-left,.door-right{left:0}#intro-overlay.open .door-left{transform:translate(-100%)}#intro-overlay.open .door-right{transform:translate(100%)}#intro-overlay.open{transition:opacity .45s ease .65s;opacity:0;pointer-events:none}.modal{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.5);display:flex;justify-content:center;align-items:center;z-index:100}.modal.hidden{display:none}.modal-content{background:#1b1e24;padding:20px;border-radius:8px;width:300px;box-shadow:0 5px 15px #0006;position:relative;color:#eee}body[data-theme=light] .modal-content{background:#ffffff;color:#222}.close-modal{position:absolute;top:10px;right:15px;font-size:22px;cursor:pointer;color:#999}.close-modal:hover{color:#eee}body[data-theme=light] .close-modal{color:#777}body[data-theme=light] .close-modal:hover{color:#333}.modal-content h3{margin-bottom:15px;text-align:center}.freq-input-group{display:flex;align-items:center;justify-content:center;gap:10px;margin-bottom:15px}.freq-btn{width:40px;height:40px;font-size:20px;background:#b8362a;color:#fff;border:none;border-radius:4px;cursor:pointer;transition:background .2s}.freq-btn:hover{background:#c73f31}#freq-input{width:100px;padding:8px;font-size:18px;text-align:center;border:1px solid #444;border-radius:4px;background:#15171c;color:#eee}body[data-theme=light] #freq-input{border-color:#ccc;background:#fff;color:#222}#btn-set-freq{display:block;width:100%;margin-top:10px}.error-msg{color:#e74c3c;margin-top:8px;text-align:center;font-size:14px}.error-msg.hidden{display:none}
diff --git a/gui/wails/frontend/dist/assets/index.448f6f10.css b/gui/wails/frontend/dist/assets/index.448f6f10.css
deleted file mode 100644
index 7d45c33..0000000
--- a/gui/wails/frontend/dist/assets/index.448f6f10.css
+++ /dev/null
@@ -1 +0,0 @@
-*{margin:0;padding:0;box-sizing:border-box}html,body{width:100%;height:100%;display:flex;justify-content:center;align-items:center;background-color:#f0f0f0;font-family:Arial,sans-serif}#app{width:400px;background:white;border-radius:8px;box-shadow:0 2px 10px #0000001a;overflow:hidden}.tabs{display:flex;background:#e9e9e9}.tab-button{flex:1;padding:12px;background:transparent;border:none;cursor:pointer;font-size:16px;transition:background .2s}.tab-button:hover{background:#dcdcdc}.tab-button.active{background:white;border-bottom:3px solid #3498db;font-weight:700}.tab-content{display:none;padding:20px}.tab-content.active{display:block}.buttons-vertical{display:flex;flex-direction:column;gap:12px;margin-bottom:20px}.action-button{padding:12px;font-size:16px;cursor:pointer;border:1px solid #bbb;border-radius:6px;background-color:#f8f8f8;transition:background-color .2s,border-color .2s}.action-button:hover{background-color:#e2e6ea;border-color:#999}.close-button{background-color:#e74c3c;color:#fff;border-color:#c0392b}.close-button:hover{background-color:#c0392b}#frequency-panel{display:flex;justify-content:space-between;align-items:center;padding:10px 14px;background-color:#f9f9f9;border:1px solid #ddd;border-radius:6px;margin-bottom:10px}#frequency-label{font-weight:700;color:#555}#frequency-value{font-size:18px;color:#2c3e50}#status-panel{display:flex;align-items:center;gap:10px;padding:10px 14px;background-color:#f9f9f9;border:1px solid #ddd;border-radius:6px}.indicator{width:16px;height:16px;border-radius:50%;display:inline-block}.indicator-red{background-color:#e74c3c}.indicator-green{background-color:#2ecc71}#status-text{font-size:16px;color:#333}.modal{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.4);display:flex;justify-content:center;align-items:center;z-index:100}.modal.hidden{display:none}.modal-content{background:white;padding:20px;border-radius:8px;width:300px;box-shadow:0 5px 15px #0000004d;position:relative}.close-modal{position:absolute;top:10px;right:15px;font-size:22px;cursor:pointer;color:#777}.close-modal:hover{color:#333}.modal-content h3{margin-bottom:15px;text-align:center}.freq-input-group{display:flex;align-items:center;justify-content:center;gap:10px;margin-bottom:15px}.freq-btn{width:40px;height:40px;font-size:20px;background:#3498db;color:#fff;border:none;border-radius:4px;cursor:pointer;transition:background .2s}.freq-btn:hover{background:#2980b9}#freq-input{width:100px;padding:8px;font-size:18px;text-align:center;border:1px solid #ccc;border-radius:4px}#btn-set-freq{display:block;width:100%;margin-top:10px}.error-msg{color:#e74c3c;margin-top:8px;text-align:center;font-size:14px}.error-msg.hidden{display:none}
diff --git a/gui/wails/frontend/dist/assets/index.cb23cfd6.js b/gui/wails/frontend/dist/assets/index.cb23cfd6.js
deleted file mode 100644
index a1a33c0..0000000
--- a/gui/wails/frontend/dist/assets/index.cb23cfd6.js
+++ /dev/null
@@ -1 +0,0 @@
-(function(){const i=document.createElement("link").relList;if(i&&i.supports&&i.supports("modulepreload"))return;for(const t of document.querySelectorAll('link[rel="modulepreload"]'))a(t);new MutationObserver(t=>{for(const n of t)if(n.type==="childList")for(const s of n.addedNodes)s.tagName==="LINK"&&s.rel==="modulepreload"&&a(s)}).observe(document,{childList:!0,subtree:!0});function m(t){const n={};return t.integrity&&(n.integrity=t.integrity),t.referrerpolicy&&(n.referrerPolicy=t.referrerpolicy),t.crossorigin==="use-credentials"?n.credentials="include":t.crossorigin==="anonymous"?n.credentials="omit":n.credentials="same-origin",n}function a(t){if(t.ep)return;t.ep=!0;const n=m(t);fetch(t.href,n)}})();function h(){return window.go.main.App.CheckConnection()}function b(){return window.go.main.App.Quit()}function p(d){return window.go.main.App.SetFrequency(d)}(function(){const d=document.querySelectorAll(".tab-button"),i=document.querySelectorAll(".tab-content");d.forEach(e=>{e.addEventListener("click",()=>{d.forEach(c=>c.classList.remove("active")),i.forEach(c=>c.classList.remove("active")),e.classList.add("active");const y=e.getAttribute("data-tab");document.getElementById(`tab-${y}`).classList.add("active")})});const m=document.getElementById("btn-check"),a=document.getElementById("btn-freq"),t=document.getElementById("btn-quit"),n=document.getElementById("status-indicator"),s=document.getElementById("status-text"),f=document.getElementById("frequency-label"),u=document.getElementById("frequency-value"),l=document.getElementById("freq-modal"),g=document.getElementById("close-modal"),r=document.getElementById("freq-input"),v=document.getElementById("btn-minus"),E=document.getElementById("btn-plus"),L=document.getElementById("btn-set-freq"),o=document.getElementById("modal-error");m.addEventListener("click",()=>{h().then(e=>{n.classList.remove("indicator-red"),n.classList.add("indicator-green"),s.textContent=`\u0421\u043E\u0435\u0434\u0438\u043D\u0435\u043D\u0438\u0435 \u0443\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043E! ${e.status}`,f.textContent="\u0422\u0435\u043A\u0443\u0449\u0430\u044F \u0447\u0430\u0441\u0442\u043E\u0442\u0430:",u.textContent=e.frequency}).catch(e=>{console.error("\u041E\u0448\u0438\u0431\u043A\u0430 CheckConnection:",e),s.textContent="\u041E\u0448\u0438\u0431\u043A\u0430: \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u0432\u044B\u043F\u043E\u043B\u043D\u0438\u0442\u044C \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443"})}),a.addEventListener("click",()=>{l.classList.remove("hidden");const e=u.textContent!=="---"?u.textContent:"145";r.value=e,o.classList.add("hidden")}),g.addEventListener("click",()=>{l.classList.add("hidden")}),window.addEventListener("click",e=>{e.target===l&&l.classList.add("hidden")}),v.addEventListener("click",()=>{let e=parseInt(r.value,10)||1;e=Math.max(1,e-1),r.value=e.toString()}),E.addEventListener("click",()=>{let e=parseInt(r.value,10)||1;e+=1,r.value=e.toString()}),L.addEventListener("click",()=>{const e=r.value.trim();if(!/^\d+$/.test(e)){o.textContent="\u0412\u0432\u043E\u0434\u0438\u043C\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u0446\u0435\u043B\u044B\u043C \u043F\u043E\u043B\u043E\u0436\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u043C \u0447\u0438\u0441\u043B\u043E\u043C",o.classList.remove("hidden");return}if(parseInt(e,10)<=0){o.textContent="\u0412\u0432\u043E\u0434\u0438\u043C\u043E\u0435 \u0437\u043D\u0430\u0447\u0435\u043D\u0438\u0435 \u0434\u043E\u043B\u0436\u043D\u043E \u0431\u044B\u0442\u044C \u0446\u0435\u043B\u044B\u043C \u043F\u043E\u043B\u043E\u0436\u0438\u0442\u0435\u043B\u044C\u043D\u044B\u043C \u0447\u0438\u0441\u043B\u043E\u043C",o.classList.remove("hidden");return}p(e).then(c=>{f.textContent="\u0423\u0441\u0442\u0430\u043D\u043E\u0432\u043B\u0435\u043D\u043D\u0430\u044F \u0447\u0430\u0441\u0442\u043E\u0442\u0430:",u.textContent=c,l.classList.add("hidden")}).catch(c=>{o.textContent=c,o.classList.remove("hidden")})}),t.addEventListener("click",()=>{b()})})();
diff --git a/gui/wails/frontend/dist/assets/logo-dark.ff9a8bc7.jpg b/gui/wails/frontend/dist/assets/logo-dark.ff9a8bc7.jpg
new file mode 100644
index 0000000..6856a0a
Binary files /dev/null and b/gui/wails/frontend/dist/assets/logo-dark.ff9a8bc7.jpg differ
diff --git a/gui/wails/frontend/dist/assets/logo-light.d5e577a8.png b/gui/wails/frontend/dist/assets/logo-light.d5e577a8.png
new file mode 100644
index 0000000..5c56201
Binary files /dev/null and b/gui/wails/frontend/dist/assets/logo-light.d5e577a8.png differ
diff --git a/gui/wails/frontend/dist/index.html b/gui/wails/frontend/dist/index.html
index b3c31d6..a5f458f 100644
--- a/gui/wails/frontend/dist/index.html
+++ b/gui/wails/frontend/dist/index.html
@@ -4,14 +4,35 @@
- Тест (Спектр)
-
-
+ Спектр
+
+
-
+
+
+
+ ☀
+
+
+
+
-
-
+
+
+
+
+
+
+
+
СПЕКТР
+
видеоприёмник · панель управления
+
+
+
+
Режим приемника
Режим детектора
Информация
@@ -64,4 +85,4 @@
-
\ No newline at end of file
+