mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-22 10:36:14 +00:00
fix(web): replace emoji control icons with flat inline SVGs
The power, mute, shuffle, and repeat buttons used Unicode emoji (⏻ 🔇 🔀 🔁) which Android/mobile browsers render through the OS emoji font with platform-specific colour styling, ignoring CSS color entirely. This caused them to look like colourful emoji badges rather than flat monochrome controls. Replace each with an inline SVG using stroke/fill="currentColor" so they inherit the button's text colour automatically — flat in both light and dark mode, and correctly inverted when a button is in its active (accent-background) state without any extra CSS filter. The .ctrl-btn rule gains display:inline-flex + align-items:center to vertically centre both text-character (⏮ ⏸ ⏭) and SVG content consistently. The .volume-icon label in the volume row switches from an emoji span to the same currentColor SVG at 16 px. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
e5f5e35c01
commit
0b97417eeb
@@ -274,7 +274,10 @@ img { display: block; max-width: 100%; }
|
||||
font-size: 1.1rem;
|
||||
padding: .25rem .4rem;
|
||||
border-radius: 4px;
|
||||
line-height: 1;
|
||||
line-height: 0;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: opacity .15s;
|
||||
}
|
||||
.btn-icon:hover { opacity: .7; }
|
||||
@@ -422,6 +425,10 @@ img { display: block; max-width: 100%; }
|
||||
border: 1px solid var(--border);
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
transition: background .12s;
|
||||
}
|
||||
.ctrl-btn:hover { background: var(--bg); }
|
||||
@@ -429,7 +436,7 @@ img { display: block; max-width: 100%; }
|
||||
.ctrl-btn.active { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }
|
||||
|
||||
.volume-row { display: flex; align-items: center; gap: .75rem; }
|
||||
.volume-icon { font-size: 1rem; }
|
||||
.volume-icon { display: flex; align-items: center; }
|
||||
.volume-slider { flex: 1; accent-color: var(--accent); }
|
||||
.volume-value { font-size: .8rem; color: var(--text-dim); min-width: 2.5ch; text-align: right; }
|
||||
|
||||
|
||||
@@ -31,7 +31,12 @@ function DeviceDetail({ deviceId, devices, onBack }) {
|
||||
<div class="device-detail">
|
||||
<div class="page-header">
|
||||
<button class="back-btn" onClick=${onBack}>← Back</button>
|
||||
<button class="btn-icon" onClick=${() => api.power(deviceId)} title="Power">⏻</button>
|
||||
<button class="btn-icon" onClick=${() => api.power(deviceId)} title="Power">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true">
|
||||
<path d="M12 2v8" />
|
||||
<path d="M18.36 6.64a9 9 0 1 1-12.73 0" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<${NowPlaying} nowPlaying=${device.status?.nowPlaying} deviceId=${deviceId} presets=${device.status?.presets} />
|
||||
<${Controls} deviceId=${deviceId} status=${device.status} />
|
||||
|
||||
@@ -5,6 +5,44 @@ import { api } from '../api.js';
|
||||
|
||||
const html = htm.bind(h);
|
||||
|
||||
// Flat SVG icons using stroke/fill="currentColor" so they automatically
|
||||
// follow the button's text colour in light mode, dark mode, and in the
|
||||
// accent-inverted active state — no CSS filter needed.
|
||||
|
||||
function IconVolume({ muted = false, size = 20 }) {
|
||||
if (muted) {
|
||||
return html`<svg width=${size} height=${size} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/>
|
||||
<line x1="23" y1="9" x2="17" y2="15"/>
|
||||
<line x1="17" y1="9" x2="23" y2="15"/>
|
||||
</svg>`;
|
||||
}
|
||||
return html`<svg width=${size} height=${size} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"/>
|
||||
<path d="M15.54 8.46a5 5 0 0 1 0 7.07"/>
|
||||
</svg>`;
|
||||
}
|
||||
|
||||
function IconShuffle() {
|
||||
return html`<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<polyline points="16 3 21 3 21 8"/>
|
||||
<line x1="4" y1="20" x2="21" y2="3"/>
|
||||
<polyline points="21 16 21 21 16 21"/>
|
||||
<line x1="15" y1="15" x2="21" y2="21"/>
|
||||
<line x1="4" y1="4" x2="9" y2="9"/>
|
||||
</svg>`;
|
||||
}
|
||||
|
||||
function IconRepeat({ one = false }) {
|
||||
return html`<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||
<polyline points="17 1 21 5 17 9"/>
|
||||
<path d="M3 11V9a4 4 0 0 1 4-4h14"/>
|
||||
<polyline points="7 23 3 19 7 15"/>
|
||||
<path d="M21 13v2a4 4 0 0 1-4 4H3"/>
|
||||
${one && html`<text x="12" y="15" text-anchor="middle" font-size="8" font-weight="bold" stroke="none" fill="currentColor" font-family="sans-serif">1</text>`}
|
||||
</svg>`;
|
||||
}
|
||||
|
||||
export function Controls({ deviceId, status }) {
|
||||
const np = status?.nowPlaying;
|
||||
const isPlaying = np?.PlayStatus === 'PLAY_STATE';
|
||||
@@ -45,8 +83,6 @@ export function Controls({ deviceId, status }) {
|
||||
else send('REPEAT_OFF');
|
||||
}
|
||||
|
||||
const repeatIcon = repeat === 'REPEAT_ONE' ? '🔂' : '🔁';
|
||||
|
||||
return html`
|
||||
<div class="controls">
|
||||
<div class="transport">
|
||||
@@ -56,13 +92,17 @@ export function Controls({ deviceId, status }) {
|
||||
</button>
|
||||
<button class="ctrl-btn" onClick=${() => send('NEXT_TRACK')} title="Next">⏭</button>
|
||||
<button class="ctrl-btn ${isMuted ? 'active' : ''}" onClick=${() => send('MUTE')} title="Mute">
|
||||
${isMuted ? '🔇' : '🔊'}
|
||||
${IconVolume({ muted: isMuted })}
|
||||
</button>
|
||||
<button class="ctrl-btn ${shuffle === 'SHUFFLE_ON' ? 'active' : ''}" onClick=${toggleShuffle} title="Shuffle">
|
||||
${IconShuffle()}
|
||||
</button>
|
||||
<button class="ctrl-btn ${repeat !== 'REPEAT_OFF' ? 'active' : ''}" onClick=${cycleRepeat} title=${repeat === 'REPEAT_ONE' ? 'Repeat one' : repeat === 'REPEAT_ALL' ? 'Repeat all' : 'Repeat'}>
|
||||
${IconRepeat({ one: repeat === 'REPEAT_ONE' })}
|
||||
</button>
|
||||
<button class="ctrl-btn ${shuffle === 'SHUFFLE_ON' ? 'active' : ''}" onClick=${toggleShuffle} title="Shuffle">🔀</button>
|
||||
<button class="ctrl-btn ${repeat !== 'REPEAT_OFF' ? 'active' : ''}" onClick=${cycleRepeat} title="Repeat">${repeatIcon}</button>
|
||||
</div>
|
||||
<div class="volume-row">
|
||||
<span class="volume-icon">🔈</span>
|
||||
<span class="volume-icon">${IconVolume({ size: 16 })}</span>
|
||||
<input type="range" class="volume-slider" min="0" max="100"
|
||||
value=${localVolume} onInput=${onVolumeChange} />
|
||||
<span class="volume-value">${localVolume}</span>
|
||||
@@ -77,4 +117,4 @@ export function Controls({ deviceId, status }) {
|
||||
`}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user