diff --git a/README.md b/README.md
index a86d8c9..c4b3a6f 100644
--- a/README.md
+++ b/README.md
@@ -108,13 +108,14 @@ To change the default window size, edit `src-tauri/tauri.conf.json`:
[Add License Information Here]
-## Release v0.1
+## Release v0.2
-Initial public preview (v0.1) — a minimal, working RadioPlayer experience:
+Public beta (v0.2) — updates since v0.1:
-- Custom CAF Receiver UI (HTML/CSS/JS) in `receiver/` with branded artwork and playback status.
-- Plays LIVE stream: `https://live.radio1.si/Radio1MB` (contentType: `audio/mpeg`, streamType: `LIVE`).
-- Desktop sidecar (`sidecar/index.js`) launches the Default Media Receiver and sends LOAD commands; launch flow now retries if the device reports `NOT_ALLOWED` by stopping existing sessions first.
+- **Android build support:** Project includes Android build scripts and Gradle wrappers. See [scripts/build-android.sh](scripts/build-android.sh) and [build-android.ps1](build-android.ps1). Prebuilt native helper binaries are available in `src-tauri/binaries/` for convenience.
+- **Web receiver & webapp:** The `receiver/` folder contains a Custom CAF Receiver UI (HTML/CSS/JS) and the `webapp/` folder provides a standalone web distribution for hosting the app in browsers or PWAs.
+- **Sidecar improvements:** `sidecar/index.js` now retries launches when devices return `NOT_ALLOWED` by attempting to stop existing sessions before retrying. Check sidecar logs for `Launch NOT_ALLOWED` messages and retry attempts.
+- **LIVE stream:** The app continues to support the LIVE stream `https://live.radio1.si/Radio1MB` (contentType: `audio/mpeg`, streamType: `LIVE`).
Included receiver files:
@@ -140,6 +141,6 @@ npx http-server receiver -p 8443 -S -C localhost.pem -K localhost-key.pem
Sidecar / troubleshoot
-- If a Cast launch fails with `NOT_ALLOWED`, the sidecar will now attempt to stop any existing sessions on the device and retry the launch (best-effort). Check sidecar logs for `Launch NOT_ALLOWED` and subsequent retry attempts.
+- If a Cast launch fails with `NOT_ALLOWED`, the sidecar will attempt to stop any existing sessions on the device and retry the launch (best-effort). Check sidecar logs for `Launch NOT_ALLOWED` and subsequent retry attempts.
- Note: the sidecar uses `castv2-client` (not the official Google sender SDK). Group/stereo behavior may vary across device types — for full sender capabilities consider adding an official sender implementation.
diff --git a/android/app/src/main/assets/styles.css b/android/app/src/main/assets/styles.css
index c01becc..6ee55ec 100644
--- a/android/app/src/main/assets/styles.css
+++ b/android/app/src/main/assets/styles.css
@@ -18,13 +18,6 @@
cursor: default;
}
-/* Show pointer cursor for interactive / clickable elements (override global default) */
-a, a[href], button, input[type="button"], input[type="submit"],
-[role="button"], [onclick], .clickable, .icon-btn, .control-btn, label[for],
-.station-item, [tabindex]:not([tabindex="-1"]) {
- cursor: pointer !important;
-}
-
/* Hide Scrollbars */
::-webkit-scrollbar {
display: none;
diff --git a/package-lock.json b/package-lock.json
index 0d043f4..c53984f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,7 @@
"version": "0.1.0",
"devDependencies": {
"@tauri-apps/cli": "^2",
+ "npx": "^3.0.0",
"rcedit": "^1.1.2"
}
},
@@ -229,12 +230,1464 @@
"node": ">= 10"
}
},
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/npx": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/npx/-/npx-3.0.0.tgz",
+ "integrity": "sha512-4KNE+u9lp5+iDDRkv3GuxHJjQGV+AZs6vqB9HNbxmPRmJLAZRxdLVeka8zp4pQh6FEvp8B0JYrKIx+Ibd5krog==",
+ "bundleDependencies": [
+ "bluebird",
+ "npm-package-arg",
+ "update-notifier",
+ "which",
+ "yargs"
+ ],
+ "deprecated": "This package is now part of the npm CLI.",
+ "dev": true,
+ "license": "CC0-1.0",
+ "dependencies": {
+ "bluebird": "^3.5.0",
+ "npm-package-arg": "^5.0.1",
+ "rimraf": "^2.6.1",
+ "update-notifier": "^2.1.0",
+ "which": "^1.2.14",
+ "yargs": "^8.0.1"
+ },
+ "bin": {
+ "npx": "index.js"
+ }
+ },
+ "node_modules/npx/node_modules/ansi-align": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^2.0.0"
+ }
+ },
+ "node_modules/npx/node_modules/ansi-regex": {
+ "version": "2.1.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/ansi-styles": {
+ "version": "2.2.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/bluebird": {
+ "version": "3.5.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npx/node_modules/boxen": {
+ "version": "1.1.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-align": "^2.0.0",
+ "camelcase": "^4.0.0",
+ "chalk": "^1.1.1",
+ "cli-boxes": "^1.0.0",
+ "string-width": "^2.0.0",
+ "term-size": "^0.1.0",
+ "widest-line": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/builtin-modules": {
+ "version": "1.1.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/builtins": {
+ "version": "1.0.3",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npx/node_modules/camelcase": {
+ "version": "4.1.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/capture-stack-trace": {
+ "version": "1.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/chalk": {
+ "version": "1.1.3",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^2.2.1",
+ "escape-string-regexp": "^1.0.2",
+ "has-ansi": "^2.0.0",
+ "strip-ansi": "^3.0.0",
+ "supports-color": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/cli-boxes": {
+ "version": "1.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/cliui": {
+ "version": "3.2.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1",
+ "wrap-ansi": "^2.0.0"
+ }
+ },
+ "node_modules/npx/node_modules/cliui/node_modules/string-width": {
+ "version": "1.0.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/code-point-at": {
+ "version": "1.1.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/configstore": {
+ "version": "3.1.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "dot-prop": "^4.1.0",
+ "graceful-fs": "^4.1.2",
+ "make-dir": "^1.0.0",
+ "unique-string": "^1.0.0",
+ "write-file-atomic": "^2.0.0",
+ "xdg-basedir": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/configstore/node_modules/dot-prop": {
+ "version": "4.1.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-obj": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/create-error-class": {
+ "version": "3.0.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "capture-stack-trace": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/cross-spawn": {
+ "version": "4.0.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "lru-cache": "^4.0.1",
+ "which": "^1.2.9"
+ }
+ },
+ "node_modules/npx/node_modules/cross-spawn-async": {
+ "version": "2.2.5",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "lru-cache": "^4.0.0",
+ "which": "^1.2.8"
+ }
+ },
+ "node_modules/npx/node_modules/crypto-random-string": {
+ "version": "1.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/decamelize": {
+ "version": "1.2.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/deep-extend": {
+ "version": "0.4.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/npx/node_modules/duplexer3": {
+ "version": "0.1.4",
+ "dev": true,
+ "inBundle": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/npx/node_modules/error-ex": {
+ "version": "1.3.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/npx/node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/npx/node_modules/execa": {
+ "version": "0.5.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn": "^4.0.0",
+ "get-stream": "^2.2.0",
+ "is-stream": "^1.1.0",
+ "npm-run-path": "^2.0.0",
+ "p-finally": "^1.0.0",
+ "signal-exit": "^3.0.0",
+ "strip-eof": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/find-up": {
+ "version": "2.1.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/get-caller-file": {
+ "version": "1.0.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npx/node_modules/get-stream": {
+ "version": "2.3.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "object-assign": "^4.0.1",
+ "pinkie-promise": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/got": {
+ "version": "6.7.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "create-error-class": "^3.0.0",
+ "duplexer3": "^0.1.4",
+ "get-stream": "^3.0.0",
+ "is-redirect": "^1.0.0",
+ "is-retry-allowed": "^1.0.0",
+ "is-stream": "^1.0.0",
+ "lowercase-keys": "^1.0.0",
+ "safe-buffer": "^5.0.1",
+ "timed-out": "^4.0.0",
+ "unzip-response": "^2.0.1",
+ "url-parse-lax": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/got/node_modules/get-stream": {
+ "version": "3.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/graceful-fs": {
+ "version": "4.1.11",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/npx/node_modules/has-ansi": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/hosted-git-info": {
+ "version": "2.4.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npx/node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/npx/node_modules/ini": {
+ "version": "1.3.4",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/npx/node_modules/invert-kv": {
+ "version": "1.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npx/node_modules/is-builtin-module": {
+ "version": "1.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "builtin-modules": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "number-is-nan": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/is-npm": {
+ "version": "1.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/is-obj": {
+ "version": "1.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/is-redirect": {
+ "version": "1.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/is-retry-allowed": {
+ "version": "1.1.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/is-stream": {
+ "version": "1.1.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/isexe": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npx/node_modules/latest-version": {
+ "version": "3.1.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "package-json": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/lazy-req": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/lcid": {
+ "version": "1.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "invert-kv": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/load-json-file": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.1.2",
+ "parse-json": "^2.2.0",
+ "pify": "^2.0.0",
+ "strip-bom": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/locate-path": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^2.0.0",
+ "path-exists": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/lowercase-keys": {
+ "version": "1.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/lru-cache": {
+ "version": "4.0.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "pseudomap": "^1.0.1",
+ "yallist": "^2.0.0"
+ }
+ },
+ "node_modules/npx/node_modules/make-dir": {
+ "version": "1.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "pify": "^2.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/mem": {
+ "version": "1.1.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "mimic-fn": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/mimic-fn": {
+ "version": "1.1.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/normalize-package-data": {
+ "version": "2.3.8",
+ "dev": true,
+ "inBundle": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "hosted-git-info": "^2.1.4",
+ "is-builtin-module": "^1.0.0",
+ "semver": "2 || 3 || 4 || 5",
+ "validate-npm-package-license": "^3.0.1"
+ }
+ },
+ "node_modules/npx/node_modules/npm-package-arg": {
+ "version": "5.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "hosted-git-info": "^2.4.2",
+ "osenv": "^0.1.4",
+ "semver": "^5.1.0",
+ "validate-npm-package-name": "^3.0.0"
+ }
+ },
+ "node_modules/npx/node_modules/npm-run-path": {
+ "version": "2.0.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/number-is-nan": {
+ "version": "1.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/object-assign": {
+ "version": "4.1.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/os-homedir": {
+ "version": "1.0.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/os-locale": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "execa": "^0.5.0",
+ "lcid": "^1.0.0",
+ "mem": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/os-tmpdir": {
+ "version": "1.0.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/osenv": {
+ "version": "0.1.4",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "os-homedir": "^1.0.0",
+ "os-tmpdir": "^1.0.0"
+ }
+ },
+ "node_modules/npx/node_modules/p-finally": {
+ "version": "1.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/p-limit": {
+ "version": "1.1.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/p-locate": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/package-json": {
+ "version": "4.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "got": "^6.7.1",
+ "registry-auth-token": "^3.0.1",
+ "registry-url": "^3.0.3",
+ "semver": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/parse-json": {
+ "version": "2.2.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "error-ex": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/path-exists": {
+ "version": "3.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/path-key": {
+ "version": "2.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/path-type": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "pify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/pify": {
+ "version": "2.3.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/pinkie": {
+ "version": "2.0.4",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/pinkie-promise": {
+ "version": "2.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "pinkie": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/prepend-http": {
+ "version": "1.0.4",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/pseudomap": {
+ "version": "1.0.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npx/node_modules/rc": {
+ "version": "1.2.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
+ "dependencies": {
+ "deep-extend": "~0.4.0",
+ "ini": "~1.3.0",
+ "minimist": "^1.2.0",
+ "strip-json-comments": "~2.0.1"
+ },
+ "bin": {
+ "rc": "index.js"
+ }
+ },
+ "node_modules/npx/node_modules/rc/node_modules/minimist": {
+ "version": "1.2.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npx/node_modules/read-pkg": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "load-json-file": "^2.0.0",
+ "normalize-package-data": "^2.3.2",
+ "path-type": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/read-pkg-up": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "find-up": "^2.0.0",
+ "read-pkg": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/registry-auth-token": {
+ "version": "3.3.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "rc": "^1.1.6",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/npx/node_modules/registry-url": {
+ "version": "3.1.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "rc": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/require-directory": {
+ "version": "2.1.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/require-main-filename": {
+ "version": "1.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npx/node_modules/safe-buffer": {
+ "version": "5.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT"
+ },
+ "node_modules/npx/node_modules/semver": {
+ "version": "5.3.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver"
+ }
+ },
+ "node_modules/npx/node_modules/semver-diff": {
+ "version": "2.1.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^5.0.3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/set-blocking": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npx/node_modules/signal-exit": {
+ "version": "3.0.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npx/node_modules/slide": {
+ "version": "1.1.6",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/npx/node_modules/spdx-correct": {
+ "version": "1.0.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "spdx-license-ids": "^1.0.2"
+ }
+ },
+ "node_modules/npx/node_modules/spdx-expression-parse": {
+ "version": "1.0.4",
+ "dev": true,
+ "inBundle": true,
+ "license": "(MIT AND CC-BY-3.0)"
+ },
+ "node_modules/npx/node_modules/spdx-license-ids": {
+ "version": "1.2.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "Unlicense"
+ },
+ "node_modules/npx/node_modules/string-width": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/string-width/node_modules/is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/strip-ansi": {
+ "version": "3.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/strip-bom": {
+ "version": "3.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/strip-eof": {
+ "version": "1.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/strip-json-comments": {
+ "version": "2.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/supports-color": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/npx/node_modules/term-size": {
+ "version": "0.1.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "execa": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/term-size/node_modules/execa": {
+ "version": "0.4.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "cross-spawn-async": "^2.1.1",
+ "is-stream": "^1.1.0",
+ "npm-run-path": "^1.0.0",
+ "object-assign": "^4.0.1",
+ "path-key": "^1.0.0",
+ "strip-eof": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.12"
+ }
+ },
+ "node_modules/npx/node_modules/term-size/node_modules/npm-run-path": {
+ "version": "1.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/term-size/node_modules/path-key": {
+ "version": "1.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/timed-out": {
+ "version": "4.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/unique-string": {
+ "version": "1.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "crypto-random-string": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/unzip-response": {
+ "version": "2.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/update-notifier": {
+ "version": "2.1.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "boxen": "^1.0.0",
+ "chalk": "^1.0.0",
+ "configstore": "^3.0.0",
+ "is-npm": "^1.0.0",
+ "latest-version": "^3.0.0",
+ "lazy-req": "^2.0.0",
+ "semver-diff": "^2.0.0",
+ "xdg-basedir": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/url-parse-lax": {
+ "version": "1.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "prepend-http": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/validate-npm-package-license": {
+ "version": "3.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "spdx-correct": "~1.0.0",
+ "spdx-expression-parse": "~1.0.0"
+ }
+ },
+ "node_modules/npx/node_modules/validate-npm-package-name": {
+ "version": "3.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "builtins": "^1.0.3"
+ }
+ },
+ "node_modules/npx/node_modules/which": {
+ "version": "1.2.14",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "which": "bin/which"
+ }
+ },
+ "node_modules/npx/node_modules/which-module": {
+ "version": "2.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npx/node_modules/widest-line": {
+ "version": "1.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "string-width": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/widest-line/node_modules/string-width": {
+ "version": "1.0.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/wrap-ansi": {
+ "version": "2.1.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "string-width": "^1.0.1",
+ "strip-ansi": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/wrap-ansi/node_modules/string-width": {
+ "version": "1.0.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npx/node_modules/write-file-atomic": {
+ "version": "2.1.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "graceful-fs": "^4.1.11",
+ "imurmurhash": "^0.1.4",
+ "slide": "^1.1.5"
+ }
+ },
+ "node_modules/npx/node_modules/xdg-basedir": {
+ "version": "3.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/npx/node_modules/y18n": {
+ "version": "3.2.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npx/node_modules/yallist": {
+ "version": "2.1.2",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC"
+ },
+ "node_modules/npx/node_modules/yargs": {
+ "version": "8.0.1",
+ "dev": true,
+ "inBundle": true,
+ "license": "MIT",
+ "dependencies": {
+ "camelcase": "^4.1.0",
+ "cliui": "^3.2.0",
+ "decamelize": "^1.1.1",
+ "get-caller-file": "^1.0.1",
+ "os-locale": "^2.0.0",
+ "read-pkg-up": "^2.0.0",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^1.0.1",
+ "set-blocking": "^2.0.0",
+ "string-width": "^2.0.0",
+ "which-module": "^2.0.0",
+ "y18n": "^3.2.1",
+ "yargs-parser": "^7.0.0"
+ }
+ },
+ "node_modules/npx/node_modules/yargs-parser": {
+ "version": "7.0.0",
+ "dev": true,
+ "inBundle": true,
+ "license": "ISC",
+ "dependencies": {
+ "camelcase": "^4.1.0"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/rcedit": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/rcedit/-/rcedit-1.1.2.tgz",
"integrity": "sha512-z2ypB4gbINhI6wVe0JJMmdpmOpmNc4g90sE6/6JSuch5kYnjfz9CxvVPqqhShgR6GIkmtW3W2UlfiXhWljA0Fw==",
"dev": true,
"license": "MIT"
+ },
+ "node_modules/rimraf": {
+ "version": "2.7.1",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
+ "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true,
+ "license": "ISC"
}
}
}
diff --git a/package.json b/package.json
index 7b3d63d..95db625 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
},
"devDependencies": {
"@tauri-apps/cli": "^2",
+ "npx": "^3.0.0",
"rcedit": "^1.1.2"
}
}
diff --git a/sidecar/package-lock.json b/sidecar/package-lock.json
index 5a08fc4..6b77db3 100644
--- a/sidecar/package-lock.json
+++ b/sidecar/package-lock.json
@@ -9,6 +9,9 @@
"version": "1.0.0",
"dependencies": {
"castv2-client": "^1.2.0"
+ },
+ "bin": {
+ "radiocast-sidecar": "index.js"
}
},
"node_modules/@protobufjs/aspromise": {
diff --git a/src-tauri/binaries/radiocast-sidecar-x86_64-pc-windows-msvc.exe b/src-tauri/binaries/radiocast-sidecar-x86_64-pc-windows-msvc.exe
index 52ad4c3..1b651fb 100644
Binary files a/src-tauri/binaries/radiocast-sidecar-x86_64-pc-windows-msvc.exe and b/src-tauri/binaries/radiocast-sidecar-x86_64-pc-windows-msvc.exe differ
diff --git a/src/index.html b/src/index.html
index 8a82012..9b67b04 100644
--- a/src/index.html
+++ b/src/index.html
@@ -6,6 +6,9 @@
RadioPlayer
+
+
+
diff --git a/src/main.js b/src/main.js
index f15ea03..a53d9a5 100644
--- a/src/main.js
+++ b/src/main.js
@@ -106,7 +106,7 @@ async function loadStations() {
try {
// stop any existing pollers before reloading stations
stopCurrentSongPollers();
- const resp = await fetch('stations.json');
+ const resp = await fetch('/stations.json');
const raw = await resp.json();
// Normalize station objects so the rest of the app can rely on `name` and `url`.
@@ -152,6 +152,11 @@ async function loadStations() {
// Append user stations after file stations
stations = stations.concat(userNormalized);
+ // Debug: report how many stations we have after loading
+ try {
+ console.debug('loadStations: loaded stations count:', stations.length);
+ } catch (e) {}
+
if (stations.length > 0) {
// Try to restore last selected station by id
const lastId = getLastStationId();
@@ -163,6 +168,7 @@ async function loadStations() {
currentIndex = 0;
}
+ console.debug('loadStations: loading station index', currentIndex);
loadStation(currentIndex);
// start polling for currentSong endpoints (if any)
startCurrentSongPollers();
@@ -414,10 +420,9 @@ function updateNowPlayingUI() {
if (!station) return;
if (nowPlayingEl && nowArtistEl && nowTitleEl) {
- // Show now-playing if we have either an artist or a title (some stations only provide title)
- if (station.currentSongInfo && (station.currentSongInfo.artist || station.currentSongInfo.title)) {
- nowArtistEl.textContent = station.currentSongInfo.artist || '';
- nowTitleEl.textContent = station.currentSongInfo.title || '';
+ if (station.currentSongInfo && station.currentSongInfo.artist && station.currentSongInfo.title) {
+ nowArtistEl.textContent = station.currentSongInfo.artist;
+ nowTitleEl.textContent = station.currentSongInfo.title;
nowPlayingEl.classList.remove('hidden');
} else {
nowArtistEl.textContent = '';
@@ -680,13 +685,14 @@ function loadStation(index) {
}
});
} else {
- // Fallback: show the full station name when no logo is provided
+ // Fallback to single-letter/logo text
logoImgEl.src = '';
logoImgEl.classList.add('hidden');
- try {
- logoTextEl.textContent = (station.name || '').trim();
- } catch (e) {
- logoTextEl.textContent = '';
+ const numberMatch = station.name.match(/\d+/);
+ if (numberMatch) {
+ logoTextEl.textContent = numberMatch[0];
+ } else {
+ logoTextEl.textContent = station.name.charAt(0).toUpperCase();
}
logoTextEl.classList.remove('hidden');
}
@@ -922,6 +928,15 @@ async function selectCastDevice(deviceName) {
window.addEventListener('DOMContentLoaded', init);
+// Register Service Worker for PWA installation (non-disruptive)
+if ('serviceWorker' in navigator) {
+ window.addEventListener('load', () => {
+ navigator.serviceWorker.register('sw.js')
+ .then((reg) => console.log('ServiceWorker registered:', reg.scope))
+ .catch((err) => console.debug('ServiceWorker registration failed:', err));
+ });
+}
+
// Open overlay and show list of stations (used by menu/hamburger)
async function openStationsOverlay() {
castOverlay.classList.remove('hidden');
diff --git a/src/manifest.json b/src/manifest.json
new file mode 100644
index 0000000..a27e292
--- /dev/null
+++ b/src/manifest.json
@@ -0,0 +1,22 @@
+{
+ "name": "RadioPlayer",
+ "short_name": "Radio",
+ "description": "RadioPlayer — stream radio stations from the web",
+ "start_url": ".",
+ "scope": ".",
+ "display": "standalone",
+ "background_color": "#1f1f2e",
+ "theme_color": "#1f1f2e",
+ "icons": [
+ {
+ "src": "assets/favicon_io/android-chrome-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "assets/favicon_io/android-chrome-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ }
+ ]
+}
diff --git a/src/sw.js b/src/sw.js
new file mode 100644
index 0000000..fa201a5
--- /dev/null
+++ b/src/sw.js
@@ -0,0 +1,48 @@
+const CACHE_NAME = 'radiocast-core-v1';
+const CORE_ASSETS = [
+ '.',
+ 'index.html',
+ 'main.js',
+ 'styles.css',
+ 'stations.json',
+ 'assets/favicon_io/android-chrome-192x192.png',
+ 'assets/favicon_io/android-chrome-512x512.png',
+ 'assets/favicon_io/apple-touch-icon.png'
+];
+
+self.addEventListener('install', (event) => {
+ event.waitUntil(
+ caches.open(CACHE_NAME).then((cache) => cache.addAll(CORE_ASSETS))
+ );
+});
+
+self.addEventListener('activate', (event) => {
+ event.waitUntil(
+ caches.keys().then((keys) => Promise.all(
+ keys.map((k) => { if (k !== CACHE_NAME) return caches.delete(k); return null; })
+ ))
+ );
+});
+
+self.addEventListener('fetch', (event) => {
+ // Only handle GET requests
+ if (event.request.method !== 'GET') return;
+
+ event.respondWith(
+ caches.match(event.request).then((cached) => {
+ if (cached) return cached;
+ return fetch(event.request).then((networkResp) => {
+ // Optionally cache new resources (best-effort)
+ try {
+ const respClone = networkResp.clone();
+ caches.open(CACHE_NAME).then((cache) => cache.put(event.request, respClone)).catch(()=>{});
+ } catch (e) {}
+ return networkResp;
+ }).catch(() => {
+ // If offline and HTML navigation, return cached index.html
+ if (event.request.mode === 'navigate') return caches.match('index.html');
+ return new Response('', { status: 503, statusText: 'Service Unavailable' });
+ });
+ })
+ );
+});
diff --git a/tools/post-build-rcedit.js b/tools/post-build-rcedit.js
index 992ab39..d3a291f 100644
--- a/tools/post-build-rcedit.js
+++ b/tools/post-build-rcedit.js
@@ -37,9 +37,9 @@ if (localBin) {
cmd = localBin;
args = [exePath, '--set-icon', iconPath];
} else {
- // Fallback to npx. Note: Node can't execute PowerShell shims (npx.ps1), so this may fail
- // in environments that only provide .ps1 launchers.
- cmd = 'npx';
+ // Fallback to npx. Use the platform-specific shim on Windows so Node spawn finds it.
+ // PowerShell-only shims like npx.ps1 won't work with Node spawn; prefer npx.cmd.
+ cmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
args = ['rcedit', exePath, '--set-icon', iconPath];
}
diff --git a/webapp/README.md b/webapp/README.md
new file mode 100644
index 0000000..a025f30
--- /dev/null
+++ b/webapp/README.md
@@ -0,0 +1,19 @@
+# RadioCast Webapp (Vite)
+
+This folder contains a minimal Vite scaffold that loads the existing app code
+from the workspace `src` folder. It is intentionally lightweight and keeps the
+original project files unchanged.
+
+Quick start:
+
+```powershell
+cd webapp
+npm install
+npm run dev
+# open http://localhost:5173
+```
+
+Notes:
+- The Vite config allows reading files from the parent workspace so the
+ existing `src/main.js` is reused.
+- You can `npm run build` here to produce a static build in `webapp/dist`.
diff --git a/webapp/assets/favicon_io/site.webmanifest b/webapp/assets/favicon_io/site.webmanifest
new file mode 100644
index 0000000..1dd9112
--- /dev/null
+++ b/webapp/assets/favicon_io/site.webmanifest
@@ -0,0 +1 @@
+{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
diff --git a/webapp/index.html b/webapp/index.html
new file mode 100644
index 0000000..065e57d
--- /dev/null
+++ b/webapp/index.html
@@ -0,0 +1,218 @@
+
+
+
+
+
+
+ RadioPlayer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
![station logo]()
+
1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Choose
+
+
+
+ -
+
Scanning...
+ Searching for speakers
+
+
+
+
+
+
+
+
+
+
+
Edit Stations
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/webapp/manifest.json b/webapp/manifest.json
new file mode 100644
index 0000000..a27e292
--- /dev/null
+++ b/webapp/manifest.json
@@ -0,0 +1,22 @@
+{
+ "name": "RadioPlayer",
+ "short_name": "Radio",
+ "description": "RadioPlayer — stream radio stations from the web",
+ "start_url": ".",
+ "scope": ".",
+ "display": "standalone",
+ "background_color": "#1f1f2e",
+ "theme_color": "#1f1f2e",
+ "icons": [
+ {
+ "src": "assets/favicon_io/android-chrome-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "assets/favicon_io/android-chrome-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ }
+ ]
+}
diff --git a/webapp/package-lock.json b/webapp/package-lock.json
new file mode 100644
index 0000000..f43a09e
--- /dev/null
+++ b/webapp/package-lock.json
@@ -0,0 +1,942 @@
+{
+ "name": "radiocast-webapp",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "radiocast-webapp",
+ "version": "0.1.0",
+ "devDependencies": {
+ "vite": "^5.0.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.54.0.tgz",
+ "integrity": "sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.54.0.tgz",
+ "integrity": "sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.54.0.tgz",
+ "integrity": "sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.54.0.tgz",
+ "integrity": "sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.54.0.tgz",
+ "integrity": "sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.54.0.tgz",
+ "integrity": "sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.54.0.tgz",
+ "integrity": "sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.54.0.tgz",
+ "integrity": "sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.54.0.tgz",
+ "integrity": "sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.54.0.tgz",
+ "integrity": "sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.54.0.tgz",
+ "integrity": "sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.54.0.tgz",
+ "integrity": "sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.54.0.tgz",
+ "integrity": "sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.54.0.tgz",
+ "integrity": "sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.54.0.tgz",
+ "integrity": "sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.54.0.tgz",
+ "integrity": "sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.54.0.tgz",
+ "integrity": "sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.54.0.tgz",
+ "integrity": "sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.54.0.tgz",
+ "integrity": "sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.54.0.tgz",
+ "integrity": "sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.54.0.tgz",
+ "integrity": "sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.54.0.tgz",
+ "integrity": "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "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/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "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/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.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "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.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.54.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.54.0.tgz",
+ "integrity": "sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.54.0",
+ "@rollup/rollup-android-arm64": "4.54.0",
+ "@rollup/rollup-darwin-arm64": "4.54.0",
+ "@rollup/rollup-darwin-x64": "4.54.0",
+ "@rollup/rollup-freebsd-arm64": "4.54.0",
+ "@rollup/rollup-freebsd-x64": "4.54.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.54.0",
+ "@rollup/rollup-linux-arm-musleabihf": "4.54.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.54.0",
+ "@rollup/rollup-linux-arm64-musl": "4.54.0",
+ "@rollup/rollup-linux-loong64-gnu": "4.54.0",
+ "@rollup/rollup-linux-ppc64-gnu": "4.54.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.54.0",
+ "@rollup/rollup-linux-riscv64-musl": "4.54.0",
+ "@rollup/rollup-linux-s390x-gnu": "4.54.0",
+ "@rollup/rollup-linux-x64-gnu": "4.54.0",
+ "@rollup/rollup-linux-x64-musl": "4.54.0",
+ "@rollup/rollup-openharmony-arm64": "4.54.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.54.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.54.0",
+ "@rollup/rollup-win32-x64-gnu": "4.54.0",
+ "@rollup/rollup-win32-x64-msvc": "4.54.0",
+ "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/vite": {
+ "version": "5.4.21",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/webapp/package.json b/webapp/package.json
new file mode 100644
index 0000000..148ec13
--- /dev/null
+++ b/webapp/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "radiocast-webapp",
+ "version": "0.1.0",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview --port 5174"
+ },
+ "devDependencies": {
+ "vite": "^5.0.0"
+ }
+}
diff --git a/webapp/src/main.js b/webapp/src/main.js
new file mode 100644
index 0000000..08d2863
--- /dev/null
+++ b/webapp/src/main.js
@@ -0,0 +1,20 @@
+// RadioCast webapp entry (web-only)
+// Removed Tauri-specific shims so this file runs in a plain browser.
+
+document.addEventListener('DOMContentLoaded', () => {
+ const app = document.getElementById('app');
+ if (!app) {
+ console.warn('No #app element found');
+ return;
+ }
+
+ app.innerHTML = `
+
+ RadioCast (Web)
+ Running as a plain web application (no Tauri).
+ Status: Idle
+
+ `;
+
+ console.log('RadioCast webapp started (web mode)');
+});
diff --git a/webapp/stations.json b/webapp/stations.json
new file mode 100644
index 0000000..05b58d3
--- /dev/null
+++ b/webapp/stations.json
@@ -0,0 +1,800 @@
+[
+ {
+ "id": "Radio1",
+ "title": "Radio 1",
+ "slogan": "Več dobre glasbe",
+ "logo": "http://datacache.radio.si/api/radiostations/logo/radio1.svg",
+ "liveAudio": "http://live.radio1.si/Radio1",
+ "liveVideo": null,
+ "poster": "",
+ "lastSongs": "http://data.radio.si/api/lastsongsxml/radio1/json",
+ "epg": "http://spored.radio.si/api/now/radio1",
+ "defaultText": "www.radio1.si",
+ "www": "https://www.radio1.si",
+ "mountPoints": [
+ "Radio1",
+ "Radio1BK",
+ "Radio1CE",
+ "Radio1GOR",
+ "Radio1KOR",
+ "Radio1LI",
+ "Radio1MB",
+ "Radio1NM",
+ "Radio1OB",
+ "Radio1PO",
+ "Radio1PR",
+ "Radio1PRI",
+ "Radio1PT",
+ "Radio1RIB",
+ "Radio1VE",
+ "Radio1VR",
+ "Radio1SAV"
+ ],
+ "social": [
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg",
+ "link": "tel:+38651300300"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg",
+ "link": "http://m.radio1.si"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg",
+ "link": "http://www.youtube.com/user/radio1slovenia"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg",
+ "link": "http://facebook.com/RadioEna"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg",
+ "link": "http://www.instagram.com/radio1slo"
+ }
+ ],
+ "enabled": true,
+ "radioApiIO": "https://onair.radioapi.io/ingest/infonet/radio1?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=50668",
+ "rpUid": "705167",
+ "dabUser": "radio1",
+ "dabPass": "sUbSGhmzdwKQT",
+ "dabDefaultImg": "http://media.radio.si/logo/dns/radio1/320x240.png",
+ "small": false
+ },
+ {
+ "id": "Aktual",
+ "title": "Radio Aktual",
+ "slogan": "Narejen za vaša ušesa",
+ "logo": "http://datacache.radio.si/api/radiostations/logo/aktual.svg",
+ "liveAudio": "http://live.radio.si/Aktual",
+ "liveVideo": "https://radio.serv.si/AktualTV/video.m3u8",
+ "poster": "https://cdn1.radio.si/900/screenaktual_90c0280a8.jpg",
+ "lastSongs": "http://data.radio.si/api/lastsongsxml/aktual/json",
+ "epg": null,
+ "defaultText": "",
+ "www": "https://radioaktual.si",
+ "mountPoints": [
+ "Aktual"
+ ],
+ "social": [
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg",
+ "link": "tel:+386158801430"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg",
+ "link": "https://radioaktual.si/"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg",
+ "link": "http://www.youtube.com/user/raktual?sub_confirmation=1"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg",
+ "link": "https://www.facebook.com/raktual"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg",
+ "link": "https://www.instagram.com/radioaktual/"
+ }
+ ],
+ "enabled": true,
+ "radioApiIO": "",
+ "rpUid": "705160",
+ "dabUser": "aktual",
+ "dabPass": "GB31GZd5st0M",
+ "dabDefaultImg": "http://media.radio.si/logo/dns/aktual/RadioAktual_DAB.jpg",
+ "small": false
+ },
+ {
+ "id": "Veseljak",
+ "title": "Radio Veseljak",
+ "slogan": "Najboljša domača glasba",
+ "logo": "http://datacache.radio.si/api/radiostations/logo/veseljak.svg",
+ "liveAudio": "http://live.radio.si/Veseljak",
+ "liveVideo": "https://radio.serv.si/VeseljakGolicaTV/video.m3u8",
+ "poster": "https://cdn1.radio.si/900/screenveseljak_166218c26.jpg",
+ "lastSongs": "http://data.radio.si/api/lastsongsxml/veseljak/json",
+ "epg": null,
+ "defaultText": "www.veseljak.si",
+ "www": "https://veseljak.si/",
+ "mountPoints": [
+ "Veseljak",
+ "VeseljakPO"
+ ],
+ "social": [
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg",
+ "link": "tel:+38615880110"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg",
+ "link": "https://veseljak.si/"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg",
+ "link": "https://www.youtube.com/c/VESELJAKNAJBOLJSADOMACAGLASBA"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg",
+ "link": "https://www.facebook.com/RadioVeseljak"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg",
+ "link": "https://www.instagram.com/veseljak.si/"
+ }
+ ],
+ "enabled": true,
+ "radioApiIO": "",
+ "rpUid": "705166",
+ "dabUser": "veseljak",
+ "dabPass": "sLRDCAX9j3k2",
+ "dabDefaultImg": "http://media.radio.si/logo/dns/veseljak/RadioVeseljak_DAB.jpg",
+ "small": false
+ },
+ {
+ "id": "Radio1Rock",
+ "title": "Radio 1 ROCK",
+ "slogan": "100% Rock",
+ "logo": "http://datacache.radio.si/api/radiostations/logo/radio1rock.svg",
+ "liveAudio": "http://live.radio.si/Radio1Rock",
+ "liveVideo": null,
+ "poster": null,
+ "lastSongs": "http://data.radio.si/api/lastsongsxml/radio1rock/json",
+ "epg": "http://spored.radio.si/api/now/radio1rock",
+ "defaultText": "www.radio1rock.si",
+ "www": "https://radio1rock.si/",
+ "mountPoints": [
+ "Radio1Rock"
+ ],
+ "social": [
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg",
+ "link": "tel:+38683879300"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg",
+ "link": "https://www.radio1rock.si/"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg",
+ "link": "https://www.facebook.com/R1Rock"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg",
+ "link": "https://www.instagram.com/R1rock.si/"
+ }
+ ],
+ "enabled": true,
+ "radioApiIO": "https://onair.radioapi.io/ingest/infonet/radiobob?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=61109",
+ "rpUid": "705162",
+ "dabUser": "radiobob",
+ "dabPass": "cjT24PpyVxit6",
+ "dabDefaultImg": "http://media.radio.si/logo/dns/radio1rock/320x240.png",
+ "small": false
+ },
+ {
+ "id": "Radio80",
+ "title": "Radio 1 80-a",
+ "slogan": "Samo hiti 80-ih",
+ "logo": "http://datacache.radio.si/api/radiostations/logo/radio80.svg",
+ "liveAudio": "http://live.radio.si/Radio80",
+ "liveVideo": null,
+ "poster": null,
+ "lastSongs": "http://data.radio.si/api/lastsongsxml/radio80/json",
+ "epg": "http://spored.radio.si/api/now/radio80",
+ "defaultText": "www.radio80.si",
+ "www": "https://radio80.si/",
+ "mountPoints": [
+ "Radio80"
+ ],
+ "social": [
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg",
+ "link": "tel:+38615008875"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg",
+ "link": "https://radio80.si/"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg",
+ "link": "https://www.youtube.com/radio1slovenia"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg",
+ "link": "https://www.facebook.com/radioena"
+ }
+ ],
+ "enabled": true,
+ "radioApiIO": "https://onair.radioapi.io/ingest/infonet/radio180-a?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=89760",
+ "rpUid": "705102",
+ "dabUser": "radio80",
+ "dabPass": "nc6da2LolcBXC",
+ "dabDefaultImg": "http://media.radio.si/logo/dns/radio80/320x240.png",
+ "small": false
+ },
+ {
+ "id": "Radio90",
+ "title": "Radio 1 90-a",
+ "slogan": "Samo hiti 90-ih",
+ "logo": "http://datacache.radio.si/api/radiostations/logo/radio90.svg",
+ "liveAudio": "http://live.radio.si/Radio90",
+ "liveVideo": null,
+ "poster": null,
+ "lastSongs": "http://data.radio.si/api/lastsongsxml/radio90/json",
+ "epg": null,
+ "defaultText": "www.radio1.si",
+ "www": "https://radio1.si/",
+ "mountPoints": [
+ "Radio90"
+ ],
+ "social": [
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg",
+ "link": "tel:+38615008875"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg",
+ "link": "https://www.radio1.si/"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg",
+ "link": "https://www.youtube.com/radio1slovenia"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg",
+ "link": "https://www.facebook.com/radioena"
+ }
+ ],
+ "enabled": true,
+ "radioApiIO": "",
+ "rpUid": "705172",
+ "dabUser": "radio90",
+ "dabPass": "P2RyUrHcyq7M",
+ "dabDefaultImg": "http://media.radio.si/logo/dns/radio90/320x240.png",
+ "small": false
+ },
+ {
+ "id": "Toti",
+ "title": "Toti radio",
+ "slogan": "Toti hudi hiti",
+ "logo": "http://datacache.radio.si/api/radiostations/logo/toti.svg",
+ "liveAudio": "http://live.radio.si/Toti",
+ "liveVideo": null,
+ "poster": null,
+ "lastSongs": "http://data.radio.si/api/lastsongsxml/toti/json",
+ "epg": "http://spored.radio.si/api/now/toti",
+ "defaultText": "www.totiradio.si",
+ "www": "https://totiradio.si/",
+ "mountPoints": [
+ "Maxi",
+ "Toti"
+ ],
+ "social": [
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg",
+ "link": "tel:+38651220220"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg",
+ "link": "https://totiradio.si/"
+ }
+ ],
+ "enabled": true,
+ "radioApiIO": "https://onair.radioapi.io/ingest/infonet/totiradio?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=91414",
+ "rpUid": "705108",
+ "dabUser": "toti",
+ "dabPass": "wmAos05tECsmf",
+ "dabDefaultImg": "http://media.radio.si/logo/dns/toti/320x240.png",
+ "small": false
+ },
+ {
+ "id": "Antena",
+ "title": "Radio Antena",
+ "slogan": "Največ hitov, najmanj govora",
+ "logo": "http://datacache.radio.si/api/radiostations/logo/antena.svg",
+ "liveAudio": "http://live.radio.si/Antena",
+ "liveVideo": null,
+ "poster": null,
+ "lastSongs": "http://data.radio.si/api/lastsongsxml/antena/json",
+ "epg": "http://spored.radio.si/api/now/antena",
+ "defaultText": "www.radioantena.si",
+ "www": "https://radioantena.si/",
+ "mountPoints": [
+ "Antena"
+ ],
+ "social": [
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg",
+ "link": "tel:+38612425630 "
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg",
+ "link": "https://radioantena.si/"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg",
+ "link": "https://www.youtube.com/user/radioantenaslo"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg",
+ "link": "https://www.facebook.com/HitradioAntena"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg",
+ "link": "https://www.instagram.com/radioantena.si/"
+ }
+ ],
+ "enabled": true,
+ "radioApiIO": "https://onair.radioapi.io/ingest/infonet/radioantena?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=37864",
+ "rpUid": "705161",
+ "dabUser": "radioantena",
+ "dabPass": "nGkMhFk77jnBQ",
+ "dabDefaultImg": "http://media.radio.si/logo/dns/antena/320x240.png",
+ "small": false
+ },
+ {
+ "id": "BestFM",
+ "title": "BestFM",
+ "slogan": "Muska, muska, muska",
+ "logo": "http://datacache.radio.si/api/radiostations/logo/bestfm.svg",
+ "liveAudio": "http://live.radio.si/BestFM",
+ "liveVideo": "https://radio.serv.si/BestTV/video.m3u8",
+ "poster": "https://cdn1.radio.si/900/screenbest_6559e3ac8.jpg",
+ "lastSongs": "http://data.radio.si/api/lastsongsxml/bestfm/json",
+ "epg": null,
+ "defaultText": "www.bestfm.si",
+ "www": "https://bestfm.si/",
+ "mountPoints": [
+ "BestFM"
+ ],
+ "social": [
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg",
+ "link": "tel:+38673372030"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg",
+ "link": "https://bestfm.si/"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg",
+ "link": "https://www.facebook.com/profile.php?id=100086776586975"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg",
+ "link": "https://www.instagram.com/bestfm.si/"
+ }
+ ],
+ "enabled": true,
+ "radioApiIO": "",
+ "rpUid": "705115",
+ "dabUser": "bestfm",
+ "dabPass": "momo911x",
+ "dabDefaultImg": "http://media.radio.si/logo/dns/bestfm/BestFM_DAB.jpg",
+ "small": false
+ },
+ {
+ "id": "Krka",
+ "title": "Radio Krka",
+ "slogan": "Dolenjska v srcu",
+ "logo": "http://datacache.radio.si/api/radiostations/logo/krka.svg",
+ "liveAudio": "http://live.radio.si/Krka",
+ "liveVideo": null,
+ "poster": null,
+ "lastSongs": "http://data.radio.si/api/lastsongsxml/krka/json",
+ "epg": "",
+ "defaultText": "www.radiokrka.si",
+ "www": "https://radiokrka.si/",
+ "mountPoints": [
+ "Krka"
+ ],
+ "social": [
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg",
+ "link": "tel:+38673372030"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg",
+ "link": "https://radiokrka.si/"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg",
+ "link": "https://www.youtube.com/user/radiokrka"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg",
+ "link": "https://www.facebook.com/radiokrka"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg",
+ "link": "https://www.instagram.com/radiokrka/"
+ }
+ ],
+ "enabled": true,
+ "radioApiIO": "",
+ "rpUid": "705120",
+ "dabUser": "krka",
+ "dabPass": "qBi6z!um2Gm",
+ "dabDefaultImg": "http://media.radio.si/logo/dns/krka/RadioKrka_DAB.jpg",
+ "small": false
+ },
+ {
+ "id": "Klasik",
+ "title": "Klasik radio",
+ "slogan": "Glasba, ki vas sprosti",
+ "logo": "https://data.radio.si/api/radiostations/logo/klasik.svg",
+ "liveAudio": "http://live.radio.si/Klasik",
+ "liveVideo": null,
+ "poster": null,
+ "lastSongs": "https://data.radio.si/api/lastsongsxml/klasik/json",
+ "epg": "",
+ "defaultText": "www.klasikradio.si",
+ "www": "https://www.klasikradio.si/",
+ "mountPoints": [
+ "Klasik"
+ ],
+ "social": [
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg",
+ "link": "tel:+38612425630"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg",
+ "link": "https://www.klasikradio.si/"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg",
+ "link": "https://www.facebook.com/profile.php?id=100064736766638"
+ }
+ ],
+ "enabled": true,
+ "radioApiIO": "",
+ "rpUid": "705176",
+ "dabUser": "klasik",
+ "dabPass": "mQTpTR9XEbiF",
+ "dabDefaultImg": "http://media.radio.si/logo/dns/klasik/320x240.png",
+ "small": false
+ },
+ {
+ "id": "Maxi",
+ "title": "Toti Maxi",
+ "slogan": "Sama dobra glasba",
+ "logo": "https://data.radio.si/api/radiostations/logo/maxi.svg",
+ "liveAudio": "http://live.radio.si/Maxi",
+ "liveVideo": null,
+ "poster": null,
+ "lastSongs": "https://data.radio.si/api/lastsongsxml/toti/json",
+ "epg": "",
+ "defaultText": "www.totimaxi.si",
+ "www": "https://www.radiomaxi.si/",
+ "mountPoints": [
+ "Maxi"
+ ],
+ "social": [
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg",
+ "link": "tel:+38631628444"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg",
+ "link": "https://www.radiomaxi.si/"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg",
+ "link": "https://www.facebook.com/profile.php?id=100064736766638"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg",
+ "link": "https://www.facebook.com/radiosalomon"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg",
+ "link": "https://www.instagram.com/radiosalomon/"
+ }
+ ],
+ "enabled": true,
+ "radioApiIO": "https://onair.radioapi.io/ingest/infonet/totiradio?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=37998",
+ "rpUid": "705109",
+ "dabUser": "salomon",
+ "dabPass": "a1bfadd8b8ut",
+ "dabDefaultImg": "http://media.radio.si/logo/dns/salomon/RadioSalomon_DAB.jpg",
+ "small": false
+ },
+ {
+ "id": "Salomon",
+ "title": "Radio Salomon",
+ "slogan": "Izbrana urbana glasba",
+ "logo": "http://datacache.radio.si/api/radiostations/logo/salomon.svg",
+ "liveAudio": "http://live.radio.si/Salomon",
+ "liveVideo": null,
+ "poster": null,
+ "lastSongs": "http://data.radio.si/api/lastsongsxml/salomon/json",
+ "epg": "",
+ "defaultText": "www.radiosalomon.si",
+ "www": "https://radiosalomon.si/",
+ "mountPoints": [
+ "Salomon"
+ ],
+ "social": [
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg",
+ "link": "tel:+386015880111"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg",
+ "link": "https://radiosalomon.si/"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg",
+ "link": "https://www.youtube.com/channel/UCd7OpUbSIoZarJgwFf4aIxw"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg",
+ "link": "https://www.facebook.com/RadioSalomon"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg",
+ "link": "https://www.instagram.com/radiosalomon/"
+ }
+ ],
+ "enabled": true,
+ "radioApiIO": "",
+ "rpUid": "705116",
+ "dabUser": "salomon",
+ "dabPass": "a1bfadd8b8ut",
+ "dabDefaultImg": "http://media.radio.si/logo/dns/salomon/RadioSalomon_DAB.jpg",
+ "small": false
+ },
+ {
+ "id": "Ptuj",
+ "title": "Radio Ptuj",
+ "slogan": "Največje uspešnice vseh časov",
+ "logo": "https://data.radio.si/api/radiostations/logo/ptuj.svg",
+ "liveAudio": "http://live.radio.si/Ptuj",
+ "liveVideo": null,
+ "poster": null,
+ "lastSongs": "https://data.radio.si/api/lastsongsxml/ptuj/json",
+ "epg": "",
+ "defaultText": "www.radio-ptuj.si",
+ "www": "https://www.radio-ptuj.si/",
+ "mountPoints": [
+ "Ptuj"
+ ],
+ "social": [
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg",
+ "link": "tel:+38627493420"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg",
+ "link": "https://www.radio-ptuj.si/"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg",
+ "link": "https://www.youtube.com/@RadioPtuj"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg",
+ "link": "https://www.facebook.com/RadioPtuj"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg",
+ "link": "https://www.instagram.com/radio_ptuj/"
+ }
+ ],
+ "enabled": true,
+ "radioApiIO": "",
+ "rpUid": "705119",
+ "dabUser": "ptuj",
+ "dabPass": "cwv4jXVKMYT",
+ "dabDefaultImg": "http://media.radio.si/logo/dns/ptuj/RadioPtuj_DAB.jpg",
+ "small": false
+ },
+ {
+ "id": "Fantasy",
+ "title": "Radio Fantasy",
+ "slogan": "Same dobre vibracije",
+ "logo": "https://data.radio.si/api/radiostations/logo/fantasy.svg",
+ "liveAudio": "http://live.radio.si/Fantasy",
+ "liveVideo": null,
+ "poster": null,
+ "lastSongs": "https://data.radio.si/api/lastsongsxml/fantasy/json",
+ "epg": "http://spored.radio.si/api/now/robin",
+ "defaultText": "",
+ "www": "https://rfantasy.si/",
+ "mountPoints": [
+ "Fantasy"
+ ],
+ "social": [
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg",
+ "link": "tel:+38634903921"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg",
+ "link": "https://www.rfantasy.si/"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg",
+ "link": "https://www.youtube.com/c/RadioFantasyTv"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg",
+ "link": "https://www.facebook.com/RadioFantasySlo"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg",
+ "link": "https://www.instagram.com/radiofantasyslo/"
+ }
+ ],
+ "enabled": true,
+ "radioApiIO": "https://onair.radioapi.io/ingest/infonet/radiofantasy?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=61118",
+ "rpUid": "",
+ "dabUser": "radiorobin",
+ "dabPass": "rt5mo9b9",
+ "dabDefaultImg": "http://media.radio.si/logo/dns/robin/320x240.png",
+ "small": false
+ },
+ {
+ "id": "Robin",
+ "title": "Radio Robin",
+ "slogan": "Brez tebe ni mene",
+ "logo": "https://data.radio.si/api/radiostations/logo/robin.svg",
+ "liveAudio": "http://live.radio.si/Robin",
+ "liveVideo": null,
+ "poster": null,
+ "lastSongs": "https://data.radio.si/api/lastsongsxml/robin/json",
+ "epg": "http://spored.radio.si/api/now/robin",
+ "defaultText": "www.robin.si",
+ "www": "https://www.robin.si/",
+ "mountPoints": [
+ "Robin"
+ ],
+ "social": [
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg",
+ "link": "tel:+38653302822"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg",
+ "link": "https://www.robin.si/"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg",
+ "link": "https://www.youtube.com/channel/UCACfPObotnJAnVXfCZNMlUg"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg",
+ "link": "https://www.facebook.com/Radio.Robin.goriski"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg",
+ "link": "https://www.instagram.com/radio_robin/"
+ }
+ ],
+ "enabled": true,
+ "radioApiIO": "https://onair.radioapi.io/ingest/infonet/radiorobin?format=aim&key=83B0Cq0wZH5DmLyZ13qr&cid=37984",
+ "rpUid": "705103",
+ "dabUser": "radiorobin",
+ "dabPass": "rt5mo9b9",
+ "dabDefaultImg": "http://media.radio.si/logo/dns/robin/320x240.png",
+ "small": false
+ },
+ {
+ "id": "Koroski",
+ "title": "Koroški radio",
+ "slogan": "Ritem Koroške",
+ "logo": "https://data.radio.si/api/radiostations/logo/koroski.svg",
+ "liveAudio": "http://live.radio.si/Koroski",
+ "liveVideo": null,
+ "poster": null,
+ "lastSongs": "https://data.radio.si/api/lastsongsxml/koroski/json",
+ "epg": "http://spored.radio.si/api/now/koroski",
+ "defaultText": "www.koroski-radio.si",
+ "www": "https://www.koroski-radio.si/",
+ "mountPoints": [
+ "Koroski"
+ ],
+ "social": [
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg",
+ "link": "tel:+38628841245"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg",
+ "link": "https://www.koroski-radio.si/"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg",
+ "link": "https://www.youtube.com/channel/UCLwH6lX4glK4o1N77JkeaJw"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg",
+ "link": "https://www.facebook.com/KoroskiRadio"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg",
+ "link": "https://www.instagram.com/koroski_r/"
+ }
+ ],
+ "enabled": true,
+ "radioApiIO": "",
+ "rpUid": "705105",
+ "dabUser": "koroski",
+ "dabPass": "num87dhket",
+ "dabDefaultImg": "http://media.radio.si/logo/dns/koroski/320x240.png",
+ "small": true
+ },
+ {
+ "id": "VeseljakZlatiZvoki",
+ "title": "Veseljak Zlati zvoki",
+ "slogan": "Najvecja zakladnica slovenske domace glasbe",
+ "logo": "https://data.radio.si/api/radiostations/logo/veseljakzlatizvoki.svg",
+ "liveAudio": "http://live.radio.si/VeseljakZlatiZvoki",
+ "liveVideo": null,
+ "poster": null,
+ "lastSongs": "https://data.radio.si/api/lastsongsxml/veseljakzlatizvoki/json",
+ "epg": "",
+ "defaultText": "www.veseljak.si",
+ "www": "https://www.veseljak.si/",
+ "mountPoints": [
+ "VeseljakZlatiZvoki"
+ ],
+ "social": [
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-telefon-01.svg",
+ "link": "tel:+38615880110"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-www-01.svg",
+ "link": "https://veseljak.si/"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-youtube-01.svg",
+ "link": "https://www.youtube.com/c/VESELJAKNAJBOLJSADOMACAGLASBA"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-facebook-01.svg",
+ "link": "https://www.facebook.com/RadioVeseljak"
+ },
+ {
+ "icon": "http://datacache.radio.si/api/radiostations/logo/ikona-instagram-01.svg",
+ "link": "https://www.instagram.com/veseljak.si/"
+ }
+ ],
+ "enabled": true,
+ "radioApiIO": "",
+ "rpUid": "705175",
+ "dabUser": "zlatizvoki",
+ "dabPass": "4jeeUnjA4qYV",
+ "dabDefaultImg": "http://media.radio.si/logo/dns/veseljakzlatizvoki/RadioVeseljakZlatiZvoki_DAB.jpg",
+ "small": false
+ },
+ {
+ "id": "RockMB",
+ "title": "Rock Maribor",
+ "slogan": "100% Rock",
+ "logo": "https://data.radio.si/api/radiostations/logo/rockmb.svg",
+ "liveAudio": "http://live.radio.si/RockMB",
+ "liveVideo": null,
diff --git a/webapp/styles.css b/webapp/styles.css
new file mode 100644
index 0000000..1748b27
--- /dev/null
+++ b/webapp/styles.css
@@ -0,0 +1,886 @@
+/* Copied from src/styles.css */
+:root {
+ --bg-gradient: linear-gradient(135deg, #7b7fd8, #b57cf2);
+ --glass-bg: rgba(255, 255, 255, 0.1);
+ --glass-border: rgba(255, 255, 255, 0.2);
+ --accent: #dfa6ff;
+ --accent-glow: rgba(223, 166, 255, 0.5);
+ --text-main: #ffffff;
+ --text-muted: rgba(255, 255, 255, 0.7);
+ --danger: #cf6679;
+ --success: #7dffb3;
+ --card-radius: 10px;
+}
+
+* {
+ box-sizing: border-box;
+ user-select: none;
+ -webkit-user-drag: none;
+ cursor: default;
+}
+
+/* Hide Scrollbars */
+::-webkit-scrollbar {
+ display: none;
+}
+
+body {
+ margin: 0;
+ padding: 0;
+ height: 100vh;
+ width: 100vw;
+ background: linear-gradient(-45deg, #7b7fd8, #b57cf2, #8b5cf6, #6930c3, #7b7fd8);
+.status-indicator-wrap {
+ display:flex;
+ align-items:center;
+ gap:10px;
+ justify-content:center;
+ margin-top:8px;
+ color:var(--text-main);
+}
+ background-size: 400% 400%;
+ animation: gradientShift 12s ease-in-out infinite;
+ font-family: 'Segoe UI', system-ui, sans-serif;
+ color: var(--text-main);
+ overflow: hidden;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+@keyframes gradientShift {
+ 0% {
+ background-position: 0% 50%;
+ }
+ 25% {
+ background-position: 100% 50%;
+ }
+ 50% {
+ background-position: 50% 100%;
+ }
+ 75% {
+ background-position: 100% 50%;
+ }
+ 100% {
+ background-position: 0% 50%;
+ }
+}
+
+/* Background Blobs */
+.bg-shape {
+ position: absolute;
+ border-radius: 50%;
+ filter: blur(60px);
+ z-index: 0;
+ opacity: 0.6;
+ animation: float 10s infinite alternate;
+}
+
+.shape-1 {
+ width: 300px;
+ height: 300px;
+ background: #5e60ce;
+ top: -50px;
+ left: -50px;
+}
+
+.shape-2 {
+ width: 250px;
+ height: 250px;
+ background: #ff6bf0;
+ bottom: -50px;
+ right: -50px;
+ animation-delay: -5s;
+}
+
+@keyframes float {
+ 0% { transform: translate(0, 0); }
+ 100% { transform: translate(30px, 30px); }
+}
+
+.app-container {
+ width: 100%;
+ height: 100%;
+ position: relative;
+ padding: 10px; /* Slight padding from window edges if desired, or 0 */
+}
+
+.glass-card {
+ position: relative;
+ z-index: 1;
+ width: 100%;
+ height: 100%;
+ background: var(--glass-bg);
+ border: 1px solid var(--glass-border);
+ backdrop-filter: blur(24px);
+ border-radius: var(--card-radius);
+ display: flex;
+ flex-direction: column;
+ padding: 24px;
+ box-shadow: 0 16px 40px rgba(0, 0, 0, 0.2);
+}
+
+/* Make whole card draggable for window movement; interactive children override with no-drag */
+.glass-card {
+ -webkit-app-region: drag;
+}
+
+/* Header */
+.header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 20px;
+ -webkit-app-region: drag; /* Draggable area */
+ padding: 10px 14px 8px 14px;
+ border-radius: 14px;
+ background: linear-gradient(135deg, rgba(60,84,255,0.14), rgba(123,127,216,0.10));
+ border: 1px solid rgba(120,130,255,0.12);
+ box-shadow: 0 10px 30px rgba(28,25,60,0.35), inset 0 1px 0 rgba(255,255,255,0.03);
+ backdrop-filter: blur(8px) saturate(120%);
+ position: relative;
+ z-index: 3;
+}
+
+.header-top {
+ display:flex;
+ justify-content:space-between;
+ align-items:center;
+ width:100%;
+}
+
+
+.header-top-row {
+ display:flex;
+ justify-content:space-between;
+ align-items:center;
+ width:100%;
+}
+
+
+.header-icons-left { flex: 0 0 auto; display:flex; align-items:center; gap:8px; padding-left:8px; }
+
+.header-center-status { flex:1; display:flex; justify-content:center; align-items:center; }
+
+.header-close { flex:0 0 auto; }
+
+.header-second-row {
+ display:flex;
+ justify-content:center;
+ align-items:center;
+ width:100%;
+ margin-top:6px;
+}
+
+.status-indicator-wrap { display:flex; gap:8px; align-items:center; color:var(--text-main); }
+
+.header-third-row { display:none; }
+.header-left {
+ justify-content: flex-start;
+ flex: 0 0 auto;
+}
+
+.header-right {
+ justify-content: flex-end;
+ flex: 0 0 auto;
+}
+
+.app-title { text-align: center; }
+
+.header-info {
+ text-align: center;
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 4px;
+}
+
+.app-title {
+ font-weight: 700;
+ font-size: 1.05rem;
+ color: var(--text-main);
+ letter-spacing: 0.4px;
+}
+
+.status-indicator {
+ font-size: 0.85rem;
+ color: var(--success);
+ margin-top: 0;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.status-dot {
+ width: 6px;
+ height: 6px;
+ background-color: var(--success);
+ border-radius: 50%;
+ box-shadow: 0 0 8px var(--success);
+}
+
+.icon-btn {
+ background: rgba(255,255,255,0.02);
+ border: 1px solid rgba(255,255,255,0.03);
+ color: var(--text-main);
+ padding: 8px;
+ cursor: pointer;
+ border-radius: 10px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: transform 0.12s ease, background 0.12s ease, box-shadow 0.12s ease;
+ -webkit-app-region: no-drag; /* Buttons clickable */
+}
+
+.icon-btn:hover {
+ background: linear-gradient(135deg, rgba(255,255,255,0.06), rgba(255,255,255,0.02));
+ transform: translateY(-3px);
+ box-shadow: 0 10px 24px rgba(0,0,0,0.2);
+}
+
+.header-buttons {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ -webkit-app-region: no-drag;
+}
+
+.close-btn:hover {
+ background: rgba(207, 102, 121, 0.3) !important;
+ color: var(--danger);
+}
+
+/* Artwork */
+.artwork-section {
+ flex: 1;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-bottom: 20px;
+}
+
+.artwork-container {
+ width: 220px;
+ height: 220px;
+ border-radius: 24px;
+ padding: 6px; /* spacing for ring */
+ background: linear-gradient(135deg, rgba(255,255,255,0.03), rgba(255,255,255,0.00));
+ box-shadow: 0 12px 40px rgba(0,0,0,0.32), inset 0 1px 0 rgba(255,255,255,0.03);
+ border: 1px solid rgba(255,255,255,0.08);
+ backdrop-filter: blur(8px) saturate(120%);
+ position: relative;
+}
+
+.artwork-placeholder {
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(135deg, #4ea8de, #6930c3);
+ border-radius: 20px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ position: relative;
+ overflow: hidden;
+ box-shadow: inset 0 0 30px rgba(0,0,0,0.22);
+ border: 1px solid rgba(255,255,255,0.04);
+}
+
+/* glossy inner rim for artwork */
+.artwork-container::after {
+ content: '';
+ position: absolute;
+ inset: 6px; /* follows padding to create rim */
+ border-radius: 20px;
+ pointer-events: none;
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.05), inset 0 -20px 40px rgba(255,255,255,0.02);
+ mix-blend-mode: overlay;
+}
+
+/* Make artwork clickable and give subtle hover feedback */
+.artwork-placeholder {
+ cursor: pointer;
+ transition: transform 0.12s ease, box-shadow 0.12s ease;
+}
+.artwork-placeholder:hover {
+ box-shadow: 0 18px 40px rgba(255, 255, 0, 0.45), inset 0 0 28px rgba(255,255,255,0.02);
+}
+
+.artwork-placeholder {
+ width: 100%;
+ height: 100%;
+ background: linear-gradient(135deg, #4ea8de, #6930c3);
+ border-radius: 20px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ position: relative;
+ overflow: hidden;
+ box-shadow: inset 0 0 20px rgba(0,0,0,0.2);
+}
+
+.station-logo-text {
+ font-size: 5rem;
+ font-weight: 800;
+ font-style: italic;
+ color: rgba(255,255,255,0.9);
+ text-shadow: 0 4px 10px rgba(0,0,0,0.3);
+ position: relative;
+ z-index: 3;
+}
+
+.station-logo-img {
+ /* Fill the artwork placeholder while keeping aspect ratio and inner padding */
+ width: 100%;
+ height: 100%;
+ object-fit: contain;
+ display: block;
+ padding: 12px; /* inner spacing from rounded edges */
+ box-sizing: border-box;
+ border-radius: 12px;
+ box-shadow: 0 8px 20px rgba(0,0,0,0.35);
+ position: relative;
+ z-index: 3;
+}
+
+/* Logo blobs container sits behind logo but inside artwork placeholder */
+.logo-blobs {
+ position: absolute;
+ inset: 0;
+ filter: url(#goo);
+ z-index: 1;
+ pointer-events: none;
+}
+
+.blob {
+ position: absolute;
+ border-radius: 50%;
+ /* more transparent overall */
+ opacity: 0.18;
+ /* slightly smaller blur for subtle definition */
+ filter: blur(6px);
+}
+
+.b1 { width: 110px; height: 110px; left: 8%; top: 20%; background: radial-gradient(circle at 30% 30%, #c77dff, #8b5cf6); animation: float1 6s ease-in-out infinite; }
+.b2 { width: 85px; height: 85px; right: 6%; top: 10%; background: radial-gradient(circle at 30% 30%, #7bffd1, #7dffb3); animation: float2 5.5s ease-in-out infinite; }
+.b3 { width: 95px; height: 95px; left: 20%; bottom: 12%; background: radial-gradient(circle at 20% 20%, #ffd07a, #ff6bf0); animation: float3 7s ease-in-out infinite; }
+.b4 { width: 70px; height: 70px; right: 24%; bottom: 18%; background: radial-gradient(circle at 30% 30%, #6bd3ff, #4ea8de); animation: float4 6.5s ease-in-out infinite; }
+.b5 { width: 50px; height: 50px; left: 46%; top: 36%; background: radial-gradient(circle at 40% 40%, #ffa6d6, #c77dff); animation: float5 8s ease-in-out infinite; }
+
+/* Additional blobs */
+.b6 { width: 75px; height: 75px; left: 12%; top: 48%; background: radial-gradient(circle at 30% 30%, #bde7ff, #6bd3ff); animation: float6 6.8s ease-in-out infinite; }
+.b7 { width: 42px; height: 42px; right: 10%; top: 42%; background: radial-gradient(circle at 40% 40%, #ffd9b3, #ffd07a); animation: float7 7.2s ease-in-out infinite; }
+.b8 { width: 70px; height: 70px; left: 34%; bottom: 8%; background: radial-gradient(circle at 30% 30%, #e3b6ff, #c77dff); animation: float8 6.4s ease-in-out infinite; }
+.b9 { width: 36px; height: 36px; right: 34%; bottom: 6%; background: radial-gradient(circle at 30% 30%, #9ef7d3, #7bffd1); animation: float9 8.4s ease-in-out infinite; }
+.b10 { width: 30px; height: 30px; left: 52%; bottom: 28%; background: radial-gradient(circle at 30% 30%, #ffd0f0, #ffa6d6); animation: float10 5.8s ease-in-out infinite; }
+
+@keyframes float1 { 0% { transform: translateY(0) translateX(0) scale(1); } 50% { transform: translateY(12px) translateX(8px) scale(1.06); } 100% { transform: translateY(0) translateX(0) scale(1); } }
+@keyframes float2 { 0% { transform: translateY(0) translateX(0) scale(1); } 50% { transform: translateY(-10px) translateX(-6px) scale(1.04); } 100% { transform: translateY(0) translateX(0) scale(1); } }
+@keyframes float3 { 0% { transform: translateY(0) translateX(0) scale(1); } 50% { transform: translateY(8px) translateX(-10px) scale(1.05); } 100% { transform: translateY(0) translateX(0) scale(1); } }
+@keyframes float4 { 0% { transform: translateY(0) translateX(0) scale(1); } 50% { transform: translateY(-6px) translateX(10px) scale(1.03); } 100% { transform: translateY(0) translateX(0) scale(1); } }
+@keyframes float5 { 0% { transform: translateY(0) translateX(0) scale(1); } 50% { transform: translateY(-12px) translateX(4px) scale(1.07); } 100% { transform: translateY(0) translateX(0) scale(1); } }
+@keyframes float6 { 0% { transform: translateY(0) translateX(0) scale(1); } 50% { transform: translateY(-8px) translateX(6px) scale(1.05); } 100% { transform: translateY(0) translateX(0) scale(1); } }
+@keyframes float7 { 0% { transform: translateY(0) translateX(0) scale(1); } 50% { transform: translateY(10px) translateX(-6px) scale(1.04); } 100% { transform: translateY(0) translateX(0) scale(1); } }
+@keyframes float8 { 0% { transform: translateY(0) translateX(0) scale(1); } 50% { transform: translateY(-6px) translateX(10px) scale(1.03); } 100% { transform: translateY(0) translateX(0) scale(1); } }
+@keyframes float9 { 0% { transform: translateY(0) translateX(0) scale(1); } 50% { transform: translateY(12px) translateX(-4px) scale(1.06); } 100% { transform: translateY(0) translateX(0) scale(1); } }
+@keyframes float10 { 0% { transform: translateY(0) translateX(0) scale(1); } 50% { transform: translateY(-10px) translateX(2px) scale(1.04); } 100% { transform: translateY(0) translateX(0) scale(1); } }
+
+/* Slightly darken backdrop gradient so blobs read better */
+.artwork-placeholder::before {
+ content: '';
+ position: absolute;
+ inset: 0;
+ background: linear-gradient(180deg, rgba(0,0,0,0.06), rgba(0,0,0,0.12));
+ z-index: 0;
+}
+
+/* Make artwork/logo clickable: show pointer cursor */
+.artwork-placeholder,
+.artwork-placeholder:hover,
+.station-logo-img,
+.station-logo-text {
+ cursor: pointer !important;
+ pointer-events: auto;
+}
+
+/* Subtle hover affordance to make clickability clearer */
+.artwork-placeholder:hover .station-logo-img,
+.artwork-placeholder:hover .station-logo-text {
+ transform: scale(1.03);
+ transition: transform 160ms ease;
+}
+
+/* Track Info */
+.track-info {
+ text-align: center;
+ margin-bottom: 20px;
+ /* Reserve fixed space for station name, artist and title to avoid layout jumps */
+ min-height: 5.2rem;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+.track-info h2 {
+ margin: 0;
+ font-size: 1.5rem;
+ font-weight: 600;
+ text-shadow: 0 2px 4px rgba(0,0,0,0.2);
+}
+
+/* Now playing container: artist and title on separate lines */
+#now-playing {
+ margin: 6px 0 0;
+ width: 100%;
+ /* Reserve two lines so content changes don't shift layout */
+ height: 2.6rem;
+ display: block;
+}
+
+#now-playing .now-artist,
+#now-playing .now-title {
+ color: var(--text-main);
+ font-size: 0.95rem;
+ font-weight: 600;
+ line-height: 1.2rem;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+/* Hide visually but keep layout space */
+#now-playing.hidden {
+ visibility: hidden;
+}
+
+.track-info p {
+ margin: 6px 0 0;
+ color: var(--text-muted);
+ font-size: 0.95rem;
+}
+
+/* Progress Bar (Visual) */
+.progress-container {
+ width: 100%;
+ height: 4px;
+ background: rgba(255,255,255,0.1);
+ border-radius: 2px;
+ margin-bottom: 30px;
+ position: relative;
+}
+
+.progress-fill {
+ width: 100%; /* Live always full or pulsing */
+ height: 100%;
+ background: linear-gradient(90deg, var(--accent), #fff);
+ border-radius: 2px;
+ opacity: 0.8;
+ box-shadow: 0 0 10px var(--accent-glow);
+}
+
+.progress-handle {
+ position: absolute;
+ right: 0;
+ top: 50%;
+ transform: translate(50%, -50%);
+ width: 12px;
+ height: 12px;
+ background: #fff;
+ border-radius: 50%;
+ box-shadow: 0 0 10px rgba(255,255,255,0.8);
+}
+
+/* Controls */
+.controls-section {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 30px;
+ margin-bottom: 30px;
+}
+
+.control-btn {
+ background: none;
+ border: none;
+ color: var(--text-main);
+ cursor: pointer;
+ transition: transform 0.1s, opacity 0.2s;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.control-btn:active {
+ transform: scale(0.9);
+}
+
+.control-btn.secondary {
+ width: 48px;
+ height: 48px;
+ border-radius: 50%;
+ background: rgba(255,255,255,0.05);
+ border: 1px solid rgba(255,255,255,0.1);
+ box-shadow: 0 4px 10px rgba(0,0,0,0.1);
+}
+
+.control-btn.primary {
+ width: 72px;
+ height: 72px;
+ border-radius: 50%;
+ background: linear-gradient(135deg, rgba(255,255,255,0.2), rgba(255,255,255,0.05));
+ border: 1px solid rgba(255,255,255,0.3);
+ box-shadow: 0 8px 20px rgba(0,0,0,0.2), inset 0 0 10px rgba(255,255,255,0.1);
+ color: #fff;
+}
+
+.control-btn.primary svg {
+ filter: drop-shadow(0 0 5px var(--accent-glow));
+}
+
+/* Playing state - pulsing glow ring */
+.control-btn.primary.playing {
+ animation: pulse-ring 2s ease-in-out infinite;
+}
+
+@keyframes pulse-ring {
+ 0%, 100% {
+ box-shadow: 0 8px 20px rgba(0,0,0,0.2),
+ inset 0 0 10px rgba(255,255,255,0.1),
+ 0 0 0 0 rgba(223, 166, 255, 0.7);
+ }
+ 50% {
+ box-shadow: 0 8px 20px rgba(0,0,0,0.2),
+ inset 0 0 10px rgba(255,255,255,0.1),
+ 0 0 0 8px rgba(223, 166, 255, 0);
+ }
+}
+
+/* Icon container prevents layout jump */
+.icon-container {
+ position: relative;
+ width: 32px;
+ height: 32px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.icon-container svg {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+}
+
+.hidden {
+ display: none !important;
+}
+
+/* Volume */
+.volume-section {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-top: auto;
+ padding: 0 10px;
+}
+
+.slider-container {
+ flex: 1;
+}
+
+/* Make slider interactive when the parent card is draggable */
+.slider-container,
+input[type=range] {
+ -webkit-app-region: no-drag;
+}
+
+input[type=range] {
+ width: 100%;
+ background: transparent;
+ -webkit-appearance: none;
+ appearance: none;
+}
+
+input[type=range]::-webkit-slider-runnable-track {
+ width: 100%;
+ height: 4px;
+ cursor: pointer;
+ background: rgba(255,255,255,0.2);
+ border-radius: 2px;
+}
+
+input[type=range]::-webkit-slider-thumb {
+ height: 16px;
+ width: 16px;
+ border-radius: 50%;
+ background: #ffffff;
+ cursor: pointer;
+ -webkit-appearance: none;
+ margin-top: -6px; /* align with track */
+ box-shadow: 0 0 10px rgba(0,0,0,0.2);
+}
+
+#volume-value {
+ font-size: 0.8rem;
+ font-weight: 500;
+ width: 30px;
+ text-align: right;
+}
+
+.icon-btn.small {
+ padding: 0;
+ width: 24px;
+ height: 24px;
+}
+
+/* Cast Overlay (Beautified as per layout2_plan.md) */
+.overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(20, 10, 35, 0.45);
+ backdrop-filter: blur(14px);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity 0.3s;
+}
+
+.overlay:not(.hidden) {
+ opacity: 1;
+ pointer-events: auto;
+}
+
+/* Modal */
+.modal {
+ width: min(420px, calc(100vw - 48px));
+ padding: 22px;
+ border-radius: 22px;
+ background: rgba(30, 30, 40, 0.82);
+ border: 1px solid rgba(255,255,255,0.12);
+ box-shadow: 0 30px 80px rgba(0,0,0,0.6);
+ color: #fff;
+ animation: pop 0.22s ease;
+ -webkit-app-region: no-drag;
+}
+
+@keyframes pop {
+ from { transform: scale(0.94); opacity: 0; }
+ to { transform: scale(1); opacity: 1; }
+}
+
+.modal h2 {
+ margin: 0 0 14px;
+ text-align: center;
+ font-size: 20px;
+}
+
+/* Device list */
+.device-list {
+ list-style: none;
+ padding: 10px 5px;
+ margin: 0 0 18px;
+ max-height: 360px;
+ overflow-y: auto;
+}
+
+/* Stations grid to show cards (used for stations overlay) */
+.stations-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
+ gap: 12px;
+ padding: 8px;
+}
+
+.station-card {
+ list-style: none;
+ padding: 12px;
+ border-radius: 14px;
+ cursor: pointer;
+ background: linear-gradient(180deg, rgba(255,255,255,0.03), rgba(255,255,255,0.01));
+ border: 1px solid rgba(255,255,255,0.06);
+ display: flex;
+ gap: 12px;
+ align-items: center;
+ transition: transform 0.12s ease, box-shadow 0.12s ease, background 0.12s;
+}
+
+.station-card:hover {
+ transform: translateY(-6px);
+ box-shadow: 0 18px 40px rgba(0,0,0,0.45);
+}
+
+.station-card.selected {
+ background: linear-gradient(135deg, #c77dff, #8b5cf6);
+ color: #111;
+ box-shadow: 0 10px 30px rgba(199,125,255,0.22);
+}
+
+.station-card-left {
+ width: 56px;
+ height: 56px;
+ flex: 0 0 56px;
+ display:flex;
+ align-items:center;
+ justify-content:center;
+}
+
+.station-card-logo {
+ width: 56px;
+ height: 56px;
+ object-fit:contain;
+ border-radius: 10px;
+ box-shadow: 0 6px 18px rgba(0,0,0,0.35);
+ background: rgba(255,255,255,0.02);
+}
+
+.station-card-fallback {
+ width: 56px;
+ height: 56px;
+ border-radius: 10px;
+ display:flex;
+ align-items:center;
+ justify-content:center;
+ font-weight:800;
+ font-size:1.2rem;
+ background: linear-gradient(135deg, rgba(255,255,255,0.04), rgba(255,255,255,0.02));
+ color: var(--text-main);
+}
+
+.station-card-body {
+ display:flex;
+ flex-direction:column;
+ gap:3px;
+ overflow:hidden;
+}
+
+.station-card-title {
+ font-weight:700;
+ font-size:0.95rem;
+ line-height:1.1;
+}
+
+.station-card-sub {
+ font-size:0.8rem;
+ color: rgba(255,255,255,0.7);
+ overflow:hidden;
+ text-overflow:ellipsis;
+ white-space:nowrap;
+}
+
+/* Device row */
+.device {
+ padding: 12px 14px;
+ border-radius: 14px;
+ margin-bottom: 8px;
+ cursor: pointer;
+ background: rgba(255,255,255,0.05);
+ transition: transform 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
+ text-align: left;
+}
+
+.device:hover {
+ background: rgba(255,255,255,0.10);
+ transform: translateY(-1px);
+}
+
+.device .device-main {
+ font-size: 15px;
+ font-weight: 600;
+ color: var(--text-main);
+}
+
+.device .device-sub {
+ margin-top: 3px;
+ font-size: 12px;
+ opacity: 0.7;
+ color: var(--text-muted);
+}
+
+/* Selected device */
+.device.selected {
+ background: linear-gradient(135deg, #c77dff, #8b5cf6);
+ box-shadow: 0 0 18px rgba(199,125,255,0.65);
+ color: #111;
+}
+
+.device.selected .device-main,
+.device.selected .device-sub {
+ color: #111;
+}
+
+.device.selected .device-sub {
+ opacity: 0.85;
+}
+
+/* Cancel button */
+.btn.cancel {
+ width: 100%;
+ padding: 12px;
+ border-radius: 999px;
+ border: none;
+ background: #d16b7d;
+ color: #fff;
+ font-size: 15px;
+ cursor: pointer;
+ transition: transform 0.15s ease, background 0.2s;
+ font-weight: 600;
+}
+
+.btn.cancel:hover {
+ transform: scale(1.02);
+ background: #e17c8d;
+}
+
+/* Editor specific tweaks */
+.modal form input {
+ outline: none;
+}
+
+/* Ensure editor overlay input fields look consistent */
+#editor-list .device {
+ display: block;
+}
+
+.btn.edit-btn, .btn.delete-btn {
+ padding: 8px 10px;
+ border-radius: 10px;
+ border: none;
+ color: #fff;
+ font-weight: 700;
+ cursor: pointer;
+}
+
+#add-station-form button.btn {
+ border-radius: 10px;
+}
+
+/* Make modal form inputs visible on dark translucent background */
+.modal input,
+.modal textarea,
+.modal select {
+ background: rgba(255,255,255,0.04);
+ border: 1px solid rgba(255,255,255,0.12);
+ color: var(--text-main);
+ padding: 10px 12px;
+ border-radius: 8px;
+ box-shadow: inset 0 1px 0 rgba(255,255,255,0.02);
+}
+
+.modal input::placeholder,
+.modal textarea::placeholder {
+ color: rgba(255,255,255,0.55);
+}
+
+.btn {
+ padding: 10px 14px;
+ border-radius: 10px;
+ border: none;
+ cursor: pointer;
+ font-weight: 700;
+}
diff --git a/webapp/sw.js b/webapp/sw.js
new file mode 100644
index 0000000..fa201a5
--- /dev/null
+++ b/webapp/sw.js
@@ -0,0 +1,48 @@
+const CACHE_NAME = 'radiocast-core-v1';
+const CORE_ASSETS = [
+ '.',
+ 'index.html',
+ 'main.js',
+ 'styles.css',
+ 'stations.json',
+ 'assets/favicon_io/android-chrome-192x192.png',
+ 'assets/favicon_io/android-chrome-512x512.png',
+ 'assets/favicon_io/apple-touch-icon.png'
+];
+
+self.addEventListener('install', (event) => {
+ event.waitUntil(
+ caches.open(CACHE_NAME).then((cache) => cache.addAll(CORE_ASSETS))
+ );
+});
+
+self.addEventListener('activate', (event) => {
+ event.waitUntil(
+ caches.keys().then((keys) => Promise.all(
+ keys.map((k) => { if (k !== CACHE_NAME) return caches.delete(k); return null; })
+ ))
+ );
+});
+
+self.addEventListener('fetch', (event) => {
+ // Only handle GET requests
+ if (event.request.method !== 'GET') return;
+
+ event.respondWith(
+ caches.match(event.request).then((cached) => {
+ if (cached) return cached;
+ return fetch(event.request).then((networkResp) => {
+ // Optionally cache new resources (best-effort)
+ try {
+ const respClone = networkResp.clone();
+ caches.open(CACHE_NAME).then((cache) => cache.put(event.request, respClone)).catch(()=>{});
+ } catch (e) {}
+ return networkResp;
+ }).catch(() => {
+ // If offline and HTML navigation, return cached index.html
+ if (event.request.mode === 'navigate') return caches.match('index.html');
+ return new Response('', { status: 503, statusText: 'Service Unavailable' });
+ });
+ })
+ );
+});
diff --git a/webapp/vite.config.js b/webapp/vite.config.js
new file mode 100644
index 0000000..4d94e34
--- /dev/null
+++ b/webapp/vite.config.js
@@ -0,0 +1,13 @@
+import { defineConfig } from 'vite';
+import path from 'path';
+
+// Allow Vite dev server to read files from parent folder so we can import
+// the existing `src` code without copying it.
+export default defineConfig({
+ server: {
+ fs: {
+ // allow access to parent workspace root
+ allow: [path.resolve(__dirname, '..')]
+ }
+ }
+});