update ui
This commit is contained in:
+5
-5
File diff suppressed because one or more lines are too long
-1
File diff suppressed because one or more lines are too long
+1
File diff suppressed because one or more lines are too long
Vendored
+2
-2
@@ -7,8 +7,8 @@
|
|||||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
|
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
|
||||||
<script type="module" crossorigin src="/assets/index-CanNU2CV.js"></script>
|
<script type="module" crossorigin src="/assets/index-D7ESsBgL.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-DqwSTyl8.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-SAHpcFVS.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
+46
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"hash": "da0aea58",
|
||||||
|
"configHash": "f821e5b8",
|
||||||
|
"lockfileHash": "5d6ef89d",
|
||||||
|
"browserHash": "9295d454",
|
||||||
|
"optimized": {
|
||||||
|
"react": {
|
||||||
|
"src": "../../react/index.js",
|
||||||
|
"file": "react.js",
|
||||||
|
"fileHash": "ef6552ca",
|
||||||
|
"needsInterop": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"src": "../../react-dom/index.js",
|
||||||
|
"file": "react-dom.js",
|
||||||
|
"fileHash": "d1244567",
|
||||||
|
"needsInterop": true
|
||||||
|
},
|
||||||
|
"react/jsx-dev-runtime": {
|
||||||
|
"src": "../../react/jsx-dev-runtime.js",
|
||||||
|
"file": "react_jsx-dev-runtime.js",
|
||||||
|
"fileHash": "a6e059f6",
|
||||||
|
"needsInterop": true
|
||||||
|
},
|
||||||
|
"react/jsx-runtime": {
|
||||||
|
"src": "../../react/jsx-runtime.js",
|
||||||
|
"file": "react_jsx-runtime.js",
|
||||||
|
"fileHash": "eb926eaa",
|
||||||
|
"needsInterop": true
|
||||||
|
},
|
||||||
|
"react-dom/client": {
|
||||||
|
"src": "../../react-dom/client.js",
|
||||||
|
"file": "react-dom_client.js",
|
||||||
|
"fileHash": "f7b551c0",
|
||||||
|
"needsInterop": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"chunks": {
|
||||||
|
"chunk-373CG7ZK": {
|
||||||
|
"file": "chunk-373CG7ZK.js"
|
||||||
|
},
|
||||||
|
"chunk-REFQX4J5": {
|
||||||
|
"file": "chunk-REFQX4J5.js"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
Vendored
Generated
Vendored
Generated
Vendored
Generated
Vendored
Generated
Vendored
Generated
Vendored
Generated
Vendored
Generated
Vendored
Generated
Vendored
Generated
Vendored
Generated
Vendored
Generated
Vendored
Generated
Vendored
Generated
Vendored
Generated
Vendored
+534
-19
@@ -3,27 +3,32 @@
|
|||||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--bg: #1e1e2e;
|
--bg: #111418;
|
||||||
--surface: #313244;
|
--surface: #1b2026;
|
||||||
--surface-hi: #45475a;
|
--surface-hi: #252c34;
|
||||||
--border: #45475a;
|
--surface-soft: color-mix(in srgb, var(--surface) 58%, var(--bg));
|
||||||
--border-hi: #585b70;
|
--border: #2d3540;
|
||||||
--text: #cdd6f4;
|
--border-hi: #46515f;
|
||||||
--text-muted: #a6adc8;
|
--text: #e7edf3;
|
||||||
--text-dim: #6c7086;
|
--text-muted: #9ba7b4;
|
||||||
--accent: #cba6f7;
|
--text-dim: #677482;
|
||||||
--accent-lo: rgba(203,166,247,0.16);
|
--accent: #44c2b4;
|
||||||
--accent-hi: #f5c2e7;
|
--accent-lo: rgba(68,194,180,0.15);
|
||||||
--green: #a6e3a1;
|
--accent-hi: #8de3da;
|
||||||
--green-lo: rgba(166,227,161,0.16);
|
--accent-text: #071513;
|
||||||
--red: #f38ba8;
|
--green: #79d28f;
|
||||||
--yellow: #f9e2af;
|
--green-lo: rgba(121,210,143,0.16);
|
||||||
--blue: #89b4fa;
|
--red: #f07178;
|
||||||
--mauve: #cba6f7;
|
--yellow: #e9c46a;
|
||||||
--overlay: #11111b;
|
--blue: #7aa2f7;
|
||||||
--radius: 3px;
|
--mauve: #b48ead;
|
||||||
|
--peach: #f4a261;
|
||||||
|
--base: #111418;
|
||||||
|
--overlay: #0b0e11;
|
||||||
|
--radius: 6px;
|
||||||
--ff: 'Syne', sans-serif;
|
--ff: 'Syne', sans-serif;
|
||||||
--fm: 'JetBrains Mono', monospace;
|
--fm: 'JetBrains Mono', monospace;
|
||||||
|
--shadow: 0 18px 50px rgba(0, 0, 0, 0.28);
|
||||||
}
|
}
|
||||||
|
|
||||||
html, body, #root { height: 100%; }
|
html, body, #root { height: 100%; }
|
||||||
@@ -1597,3 +1602,513 @@ body {
|
|||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
.settings-input--sm { width: 70px; }
|
.settings-input--sm { width: 70px; }
|
||||||
|
|
||||||
|
/* ── Design refresh ───────────────────────────────────────────────────────── */
|
||||||
|
body {
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 18% 0%, color-mix(in srgb, var(--accent) 14%, transparent), transparent 34%),
|
||||||
|
linear-gradient(180deg, color-mix(in srgb, var(--surface-hi) 32%, var(--bg)) 0%, var(--bg) 48%, color-mix(in srgb, var(--overlay) 68%, var(--bg)) 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app {
|
||||||
|
width: min(100%, 980px);
|
||||||
|
max-width: 980px;
|
||||||
|
min-height: 100%;
|
||||||
|
height: auto;
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-header {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 20;
|
||||||
|
gap: 22px;
|
||||||
|
padding: 18px 0 12px;
|
||||||
|
border-bottom: 1px solid color-mix(in srgb, var(--border-hi) 70%, transparent);
|
||||||
|
background: linear-gradient(180deg, color-mix(in srgb, var(--bg) 98%, transparent), color-mix(in srgb, var(--bg) 88%, transparent));
|
||||||
|
backdrop-filter: blur(14px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-title {
|
||||||
|
min-width: 132px;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-title-main {
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-title-sub {
|
||||||
|
color: var(--accent);
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-nav {
|
||||||
|
flex: 1;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
gap: 6px;
|
||||||
|
max-width: 560px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-btn {
|
||||||
|
border: 1px solid transparent;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 12px;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
padding: 9px 10px;
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
border-color: var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-btn--active {
|
||||||
|
background: var(--accent-lo);
|
||||||
|
border-color: color-mix(in srgb, var(--accent) 55%, transparent);
|
||||||
|
color: var(--accent-hi);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-main {
|
||||||
|
padding: 16px 0 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-section {
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: linear-gradient(180deg, color-mix(in srgb, var(--surface-hi) 72%, transparent), color-mix(in srgb, var(--surface) 72%, transparent));
|
||||||
|
padding: 18px;
|
||||||
|
box-shadow: 0 1px 0 rgba(255, 255, 255, 0.03) inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-section--meta {
|
||||||
|
padding: 12px 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-error,
|
||||||
|
.panel-empty {
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: var(--surface-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-header {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-label {
|
||||||
|
color: var(--text-dim);
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 0.14em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-value {
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn,
|
||||||
|
.browser-icon-btn,
|
||||||
|
.browser-back-btn,
|
||||||
|
.settings-action-btn,
|
||||||
|
.recognize-btn,
|
||||||
|
.danger-btn,
|
||||||
|
.mute-btn,
|
||||||
|
.toggle-btn,
|
||||||
|
.transport-btn {
|
||||||
|
min-height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn,
|
||||||
|
.browser-icon-btn,
|
||||||
|
.browser-back-btn,
|
||||||
|
.settings-action-btn,
|
||||||
|
.recognize-btn,
|
||||||
|
.danger-btn,
|
||||||
|
.mute-btn,
|
||||||
|
.delete-btn,
|
||||||
|
.toggle-btn,
|
||||||
|
.transport-btn,
|
||||||
|
.playlist-item,
|
||||||
|
.cover-card,
|
||||||
|
.browser-mode-btn,
|
||||||
|
.album-back-btn,
|
||||||
|
.album-play-btn,
|
||||||
|
.album-play-dropdown button {
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-btn:focus-visible,
|
||||||
|
.tab-btn:focus-visible,
|
||||||
|
.select:focus-visible,
|
||||||
|
.settings-input:focus-visible,
|
||||||
|
.browser-input:focus-visible,
|
||||||
|
.transport-btn:focus-visible,
|
||||||
|
.toggle-btn:focus-visible,
|
||||||
|
.browser-mode-btn:focus-visible,
|
||||||
|
.settings-action-btn:focus-visible {
|
||||||
|
outline: 2px solid color-mix(in srgb, var(--accent) 75%, transparent);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vol-slider,
|
||||||
|
.slider-track,
|
||||||
|
.browser-input,
|
||||||
|
.settings-input,
|
||||||
|
.select {
|
||||||
|
background: var(--surface-soft);
|
||||||
|
border-color: var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vol-slider {
|
||||||
|
height: 38px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vol-fill,
|
||||||
|
.slider-fill,
|
||||||
|
.progress-fill {
|
||||||
|
background: linear-gradient(90deg, color-mix(in srgb, var(--accent) 34%, transparent), var(--accent));
|
||||||
|
}
|
||||||
|
|
||||||
|
.now-playing {
|
||||||
|
gap: 16px;
|
||||||
|
padding: 2px 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.now-cover {
|
||||||
|
width: 92px;
|
||||||
|
height: 92px;
|
||||||
|
flex-basis: 92px;
|
||||||
|
border-color: var(--border-hi);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.now-cover--empty {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-title {
|
||||||
|
color: var(--text);
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 1.15;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-sub {
|
||||||
|
color: var(--text-muted);
|
||||||
|
line-height: 1.45;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
height: 8px;
|
||||||
|
background: color-mix(in srgb, var(--overlay) 78%, var(--bg));
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 999px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-fill {
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transport-panel {
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transport {
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transport-btn {
|
||||||
|
background: var(--surface-soft);
|
||||||
|
border-color: var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
font-size: 17px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transport-btn:hover:not(:disabled) {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.transport-btn--play {
|
||||||
|
width: 76px;
|
||||||
|
border-color: color-mix(in srgb, var(--accent) 55%, transparent);
|
||||||
|
color: var(--accent-hi);
|
||||||
|
}
|
||||||
|
|
||||||
|
.transport-btn--play.transport-btn--active {
|
||||||
|
background: linear-gradient(180deg, color-mix(in srgb, var(--accent) 28%, transparent), color-mix(in srgb, var(--accent) 12%, transparent));
|
||||||
|
}
|
||||||
|
|
||||||
|
.transport-volume {
|
||||||
|
flex-basis: 190px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-btn {
|
||||||
|
width: 38px;
|
||||||
|
background: var(--surface-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-btn--on,
|
||||||
|
.transport-btn--active,
|
||||||
|
.browser-mode-btn--active {
|
||||||
|
box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 12%, transparent) inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-toggle {
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: var(--surface-soft);
|
||||||
|
color: var(--text-muted);
|
||||||
|
padding: 10px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playlist-list,
|
||||||
|
.browser {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.browser-mode-row {
|
||||||
|
gap: 8px;
|
||||||
|
padding: 0 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.browser-mode-btn {
|
||||||
|
background: var(--surface-soft);
|
||||||
|
padding: 9px 10px;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.browser-search-row,
|
||||||
|
.browser-nav {
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.browser-input {
|
||||||
|
min-height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.browser-list {
|
||||||
|
min-height: min(66vh, 720px);
|
||||||
|
max-height: min(66vh, 720px);
|
||||||
|
padding-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.browser-row {
|
||||||
|
min-height: 42px;
|
||||||
|
padding: 7px 8px;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
.browser-row:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.035);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover-grid {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(142px, 1fr));
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover-card {
|
||||||
|
background: var(--surface-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover-title {
|
||||||
|
min-height: 48px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.queue-backdrop {
|
||||||
|
background: rgba(5, 7, 10, 0.68);
|
||||||
|
backdrop-filter: blur(3px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.queue-drawer {
|
||||||
|
width: min(820px, calc(100% - 24px));
|
||||||
|
background: color-mix(in srgb, var(--surface-soft) 80%, var(--overlay));
|
||||||
|
border-color: var(--border-hi);
|
||||||
|
border-radius: 10px 10px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.queue-item {
|
||||||
|
min-height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dsp-response {
|
||||||
|
background: color-mix(in srgb, var(--overlay) 78%, var(--bg));
|
||||||
|
}
|
||||||
|
|
||||||
|
.dsp-response svg {
|
||||||
|
height: 190px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dsp-response-fill {
|
||||||
|
fill: color-mix(in srgb, var(--accent) 12%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-section {
|
||||||
|
background: var(--surface-soft);
|
||||||
|
border-color: var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.group-header,
|
||||||
|
.group-vol {
|
||||||
|
background: color-mix(in srgb, var(--surface-hi) 78%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.client-row:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.025);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stream-row,
|
||||||
|
.settings-service-row,
|
||||||
|
.settings-row,
|
||||||
|
.meta-row {
|
||||||
|
min-height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stream-row,
|
||||||
|
.settings-service-row {
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
background: var(--surface-soft);
|
||||||
|
padding: 9px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.snap-art {
|
||||||
|
border-color: var(--border-hi);
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.snap-meta {
|
||||||
|
background: linear-gradient(to bottom, rgba(5, 7, 10, 0.78), rgba(5, 7, 10, 0.34), transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.snap-title {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.album-header {
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.album-play-btn {
|
||||||
|
color: var(--accent-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.album-play-btn:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-list {
|
||||||
|
max-height: min(54vh, 520px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.disc-heading {
|
||||||
|
background: color-mix(in srgb, var(--surface-soft) 80%, var(--overlay));
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-row {
|
||||||
|
min-height: 38px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-input {
|
||||||
|
min-height: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 760px) {
|
||||||
|
.app-main > .panel {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-section:first-child .now-playing {
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-section:first-child .now-cover {
|
||||||
|
width: 124px;
|
||||||
|
height: 124px;
|
||||||
|
flex-basis: 124px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.app {
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-header {
|
||||||
|
align-items: stretch;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
padding-top: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-title {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-nav {
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-btn {
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 9px 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-main {
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-section {
|
||||||
|
padding: 14px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.now-cover {
|
||||||
|
width: 76px;
|
||||||
|
height: 76px;
|
||||||
|
flex-basis: 76px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.track-title {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transport-volume {
|
||||||
|
flex-basis: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.browser-list {
|
||||||
|
min-height: 560px;
|
||||||
|
max-height: 560px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover-grid {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(118px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-row {
|
||||||
|
align-items: stretch;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-input,
|
||||||
|
.settings-input--sm {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ import './App.css';
|
|||||||
|
|
||||||
const TABS = [
|
const TABS = [
|
||||||
{ id: 'dsp', label: 'DSP' },
|
{ id: 'dsp', label: 'DSP' },
|
||||||
{ id: 'player', label: 'PLAYER' },
|
{ id: 'player', label: 'Player' },
|
||||||
{ id: 'network', label: 'NETWORK' },
|
{ id: 'network', label: 'Network' },
|
||||||
{ id: 'settings', label: 'SETTINGS' },
|
{ id: 'settings', label: 'Settings' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
@@ -30,6 +30,7 @@ export default function App() {
|
|||||||
key={t.id}
|
key={t.id}
|
||||||
className={`tab-btn ${tab === t.id ? 'tab-btn--active' : ''}`}
|
className={`tab-btn ${tab === t.id ? 'tab-btn--active' : ''}`}
|
||||||
onClick={() => setTab(t.id)}
|
onClick={() => setTab(t.id)}
|
||||||
|
aria-pressed={tab === t.id}
|
||||||
>
|
>
|
||||||
{t.label}
|
{t.label}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -5,13 +5,17 @@ const DEFAULT_THEME = {
|
|||||||
'--bg': '#1e1e2e',
|
'--bg': '#1e1e2e',
|
||||||
'--surface': '#313244',
|
'--surface': '#313244',
|
||||||
'--surface-hi': '#45475a',
|
'--surface-hi': '#45475a',
|
||||||
|
'--surface-soft': '#272838',
|
||||||
'--border': '#45475a',
|
'--border': '#45475a',
|
||||||
'--border-hi': '#585b70',
|
'--border-hi': '#585b70',
|
||||||
'--accent': '#cba6f7',
|
'--accent': '#cba6f7',
|
||||||
'--accent-lo': 'rgba(203,166,247,0.16)',
|
'--accent-lo': 'rgba(203,166,247,0.16)',
|
||||||
'--accent-hi': '#f5c2e7',
|
'--accent-hi': '#f5c2e7',
|
||||||
|
'--accent-text': '#11111b',
|
||||||
'--blue': '#89b4fa',
|
'--blue': '#89b4fa',
|
||||||
'--mauve': '#cba6f7',
|
'--mauve': '#cba6f7',
|
||||||
|
'--base': '#1e1e2e',
|
||||||
|
'--overlay': '#11111b',
|
||||||
};
|
};
|
||||||
|
|
||||||
function clamp(value, min, max) {
|
function clamp(value, min, max) {
|
||||||
@@ -74,21 +78,27 @@ function deriveTheme([r, g, b]) {
|
|||||||
const accentHi = hslToRgb((hue + 28) % 360, clamp(sat * 0.86, 0.32, 0.72), 0.82);
|
const accentHi = hslToRgb((hue + 28) % 360, clamp(sat * 0.86, 0.32, 0.72), 0.82);
|
||||||
const bg = hslToRgb(hue, clamp(sat * 0.22, 0.12, 0.28), 0.13);
|
const bg = hslToRgb(hue, clamp(sat * 0.22, 0.12, 0.28), 0.13);
|
||||||
const surface = hslToRgb(hue, clamp(sat * 0.24, 0.14, 0.32), 0.19);
|
const surface = hslToRgb(hue, clamp(sat * 0.24, 0.14, 0.32), 0.19);
|
||||||
|
const surfaceSoft = hslToRgb(hue, clamp(sat * 0.20, 0.12, 0.28), 0.16);
|
||||||
const surfaceHi = hslToRgb(hue, clamp(sat * 0.22, 0.12, 0.3), 0.27);
|
const surfaceHi = hslToRgb(hue, clamp(sat * 0.22, 0.12, 0.3), 0.27);
|
||||||
const border = hslToRgb(hue, clamp(sat * 0.20, 0.10, 0.28), 0.29);
|
const border = hslToRgb(hue, clamp(sat * 0.20, 0.10, 0.28), 0.29);
|
||||||
const borderHi = hslToRgb(hue, clamp(sat * 0.22, 0.12, 0.32), 0.36);
|
const borderHi = hslToRgb(hue, clamp(sat * 0.22, 0.12, 0.32), 0.36);
|
||||||
|
const overlay = hslToRgb(hue, clamp(sat * 0.18, 0.10, 0.24), 0.08);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'--bg': hex(bg),
|
'--bg': hex(bg),
|
||||||
'--surface': hex(surface),
|
'--surface': hex(surface),
|
||||||
|
'--surface-soft': hex(surfaceSoft),
|
||||||
'--surface-hi': hex(surfaceHi),
|
'--surface-hi': hex(surfaceHi),
|
||||||
'--border': hex(border),
|
'--border': hex(border),
|
||||||
'--border-hi': hex(borderHi),
|
'--border-hi': hex(borderHi),
|
||||||
'--accent': hex(accent),
|
'--accent': hex(accent),
|
||||||
'--accent-lo': rgba(accent, 0.16),
|
'--accent-lo': rgba(accent, 0.16),
|
||||||
'--accent-hi': hex(accentHi),
|
'--accent-hi': hex(accentHi),
|
||||||
|
'--accent-text': '#0b0e11',
|
||||||
'--blue': hex(accent),
|
'--blue': hex(accent),
|
||||||
'--mauve': hex(accentHi),
|
'--mauve': hex(accentHi),
|
||||||
|
'--base': hex(bg),
|
||||||
|
'--overlay': hex(overlay),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ config_file =
|
|||||||
[audio]
|
[audio]
|
||||||
mixer = software
|
mixer = software
|
||||||
mixer_volume =
|
mixer_volume =
|
||||||
output = audioresample ! audioconvert ! audio/x-raw,rate=48000,channels=2,format=S16LE ! tee name=t ! queue leaky=2 ! filesink location=/audio/mopidy.fifo t. ! queue leaky=2 ! filesink location=/audio/cava.fifo
|
output = audioresample ! audioconvert ! audio/x-raw,rate=48000,channels=2,format=S16LE ! tee name=t ! queue max-size-buffers=0 max-size-bytes=0 max-size-time=2000000000 ! filesink location=/audio/mopidy.fifo sync=true async=false t. ! queue leaky=2 max-size-buffers=0 max-size-bytes=0 max-size-time=200000000 ! filesink location=/audio/cava.fifo sync=true async=false
|
||||||
buffer_time =
|
buffer_time =
|
||||||
|
|
||||||
[proxy]
|
[proxy]
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ def _update_env_file(updates):
|
|||||||
# ── Internal state ────────────────────────────────────────────────────────────
|
# ── Internal state ────────────────────────────────────────────────────────────
|
||||||
_rpc_id = itertools.count(1)
|
_rpc_id = itertools.count(1)
|
||||||
_cache = {}
|
_cache = {}
|
||||||
|
_cache_lock = threading.Lock()
|
||||||
_cava_lock = threading.Lock()
|
_cava_lock = threading.Lock()
|
||||||
_cava_process = None
|
_cava_process = None
|
||||||
_cava_thread = None
|
_cava_thread = None
|
||||||
@@ -151,7 +152,14 @@ def rpc(url, method, params=None, timeout=8):
|
|||||||
|
|
||||||
|
|
||||||
# ── Snapcast / Mopidy metadata ────────────────────────────────────────────────
|
# ── Snapcast / Mopidy metadata ────────────────────────────────────────────────
|
||||||
def snapcast_status():
|
def snapcast_status(max_age=0.75):
|
||||||
|
cache_key = ("snapcast_status", effective_snapcast_url())
|
||||||
|
now = time.time()
|
||||||
|
with _cache_lock:
|
||||||
|
cached = _cache.get(cache_key)
|
||||||
|
if cached and now - cached["at"] <= max_age:
|
||||||
|
return cached["data"]
|
||||||
|
|
||||||
result = rpc(effective_snapcast_url(), "Server.GetStatus")
|
result = rpc(effective_snapcast_url(), "Server.GetStatus")
|
||||||
server = result.get("server", result)
|
server = result.get("server", result)
|
||||||
groups = server.get("groups", [])
|
groups = server.get("groups", [])
|
||||||
@@ -162,12 +170,15 @@ def snapcast_status():
|
|||||||
)
|
)
|
||||||
active_stream_id = active_group.get("stream_id") if active_group else None
|
active_stream_id = active_group.get("stream_id") if active_group else None
|
||||||
active_stream = next((s for s in streams if s.get("id") == active_stream_id), None)
|
active_stream = next((s for s in streams if s.get("id") == active_stream_id), None)
|
||||||
return {
|
data = {
|
||||||
"group": active_group,
|
"group": active_group,
|
||||||
"stream": active_stream,
|
"stream": active_stream,
|
||||||
"stream_id": active_stream_id,
|
"stream_id": active_stream_id,
|
||||||
"streams": streams,
|
"streams": streams,
|
||||||
}
|
}
|
||||||
|
with _cache_lock:
|
||||||
|
_cache[cache_key] = {"at": now, "data": data}
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
def mopidy_now_playing():
|
def mopidy_now_playing():
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ ENTRYPOINT ["/entrypoint.sh"]
|
|||||||
EXPOSE 1704 1705 1780
|
EXPOSE 1704 1705 1780
|
||||||
|
|
||||||
HEALTHCHECK --interval=15s --timeout=5s --start-period=10s \
|
HEALTHCHECK --interval=15s --timeout=5s --start-period=10s \
|
||||||
CMD wget -qO- http://localhost:1780/jsonrpc \
|
CMD curl -fsS http://localhost:1780/jsonrpc \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
-d '{"jsonrpc":"2.0","method":"Server.GetStatus","id":1}' \
|
-d '{"jsonrpc":"2.0","method":"Server.GetStatus","id":1}' \
|
||||||
| grep -q '"id":1' || exit 1
|
| grep -q '"id":1' || exit 1
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user