diff --git a/CHANGELOG.md b/CHANGELOG.md index ca069d3..20d8ba1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,32 @@ Format: [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/) — versi --- +## [0.3.0] — 2026-05-29 — Dark theme + mute + media keys + current-track display + +A polish pass that closes the gaps surfaced by a UI review. Two categories: **2nd-look fixes** (things the previous release implied but didn't actually deliver) and **nice-to-haves** (small upgrades that lift the admin-player feel). + +### Added — features +- **Mute toggle on the speaker icon.** The icon next to the volume slider is now a button. Click to mute (icon flips to `dashicons-controls-volumeoff`, tinted red); click again to restore the prior volume. Remembers volume across mute/unmute via `data-prev-volume`. +- **MediaSession API integration.** OS media keys (F8/F9 on Mac, Bluetooth headphone buttons, lock-screen widget on supported platforms) now play/pause the radio. The currently-playing station name, "SomaFM" as artist, and the genre as album are exposed as `MediaMetadata` so they show on the OS overlays. +- **Current-track display.** Polls `https://somafm.com/songs/{code}.json` every 30 seconds **while playing only** and shows the track as `♪ Title — Artist` under the station description. Best-effort: silently hidden if the endpoint is unreachable / CORS-blocked, so the plugin keeps working regardless. + +### Fixed — 2nd-look +- **Dark theme is now actually wired through.** v0.2.0 saved the Theme dropdown (auto / light / dark) but had no CSS to render anything other than light. v0.3.0 adds `admin_body_class` filter → `radio-theme-{auto,light,dark}` body class → corresponding dark-palette CSS for the player + about-cards. `auto` follows the OS via `prefers-color-scheme: dark`. +- **Settings-page volume slider** no longer uses an inline `oninput=""` handler — the listener moved into `assets/js/radio.js` (`bindSettingsSlider`). Cleaner under strict-CSP environments. +- **Save errors are surfaced.** AJAX state-save failures were previously swallowed silently — the local UI updated but the user had no signal if the server dropped the request. The plugin now shows a brief notice ("Preferences not saved — check your connection.") in the player's error slot for 3.5 s, then auto-clears. +- **Hardcoded Gitea URL** in the About page replaced with a `RADIO_GITEA_URL` constant defined in `radio.php`. One place to update if the repo ever moves. +- **Genre badge layout fix.** Was using `margin-left: auto` inside a wrap-enabled flex row, which caused it to land on its own line on narrow widget widths. Now styled as a small inline pill (rounded `rgba(0,0,0,0.06)` background) that flows naturally next to the station name. + +### Other +- Plugin version bumped to **0.3.0**. +- New localized strings: `mute`, `unmute`, `saveError` (for the JS-driven UI). +- The mute button has a visible focus ring (`outline: 2px solid var(--wp-admin-theme-color)`) for keyboard navigation. +- Volume slider input now also exits mute state (sets `audio.muted = false` on drag), so dragging the slider always overrides a prior mute click. + +**Files changed:** `radio.php` (version, constant, strings, `admin_body_class` filter), `inc/about.php` (constant for changelog URL), `inc/settings.php` (removed inline `oninput`), `inc/admin-page.php` + `inc/dashboard-widget.php` (speaker icon → mute button, added track slot), `assets/css/radio.css` (genre badge pill, mute button, track display, dark-theme rules incl. `prefers-color-scheme` for `auto`), `assets/js/radio.js` (full rewrite incl. `bindMute`, `bindSettingsSlider`, `startTrackPolling`/`stopTrackPolling`, `updateMediaSession`, save-error surfacing). + +--- + ## [0.2.0] — 2026-05-26 ### Changed — UI rebuilt to WordPress admin standards diff --git a/assets/css/radio.css b/assets/css/radio.css index f790725..70540dc 100644 --- a/assets/css/radio.css +++ b/assets/css/radio.css @@ -44,11 +44,31 @@ } .radio-player__station-genre { - font-size: 11px; - color: #646970; + font-size: 10px; + color: #50575e; text-transform: uppercase; letter-spacing: 0.04em; - margin-left: auto; + font-weight: 600; + margin-left: 4px; + padding: 1px 7px; + background: rgba(0, 0, 0, 0.06); + border-radius: 10px; +} + +/* Current track (poll SomaFM songs endpoint when playing). Hidden when + we have no track info — the slot still exists in the DOM so JS can + show/hide without layout shift on first load. */ +.radio-player__track { + flex: 1 1 100%; + margin: 4px 0 0; + font-size: 12px; + color: #50575e; + font-style: italic; +} +.radio-player__track::before { + content: '♪ '; + opacity: 0.6; + margin-right: 2px; } /* Controls — single row, native button + slider */ @@ -88,6 +108,26 @@ height: 16px; } +/* Mute toggle — the speaker icon is a button. No chrome, just the icon, + like a YouTube/Spotify mute affordance. Red when muted to make the + state obvious. */ +.radio-player__mute { + background: none; + border: 0; + padding: 2px; + margin: 0; + cursor: pointer; + color: #646970; + display: inline-flex; + align-items: center; + line-height: 1; + border-radius: 2px; +} +.radio-player__mute:hover { color: #1d2327; } +.radio-player__mute:focus { outline: 2px solid var(--wp-admin-theme-color, #2271b1); outline-offset: 1px; } +.radio-player__mute--muted { color: #b32d2e; } +.radio-player__mute--muted:hover { color: #8a2424; } + .radio-player__volume input[type="range"] { flex: 1; margin: 0; @@ -274,3 +314,79 @@ .radio-about-changelog-link:hover { text-decoration: underline; } + +/* ────────────────────────────────────────────────────────────────── + * Dark theme — body.radio-theme-dark forces dark; body.radio-theme-auto + * follows the OS via `prefers-color-scheme`. body.radio-theme-light is a + * no-op (the existing rules above are light). + * + * Scope is the player + the about-cards. The WP postbox chrome stays + * under WordPress's own admin colour scheme — we only retint surfaces + * we own. + * ─────────────────────────────────────────────────────────────── */ + +.radio-theme-dark .radio-player__now { + border-bottom-color: #3c434a; +} +.radio-theme-dark .radio-player__station-name { color: #f0f0f1; } +.radio-theme-dark .radio-player__label, +.radio-theme-dark .radio-player__station-genre, +.radio-theme-dark .radio-player__volume-pct, +.radio-theme-dark .radio-player__credit, +.radio-theme-dark .radio-player__mute, +.radio-theme-dark .radio-player__station-select label, +.radio-theme-dark .radio-player__volume .dashicons { + color: #a7aaad; +} +.radio-theme-dark .radio-player__mute:hover { color: #f0f0f1; } +.radio-theme-dark .radio-player__mute--muted { color: #ff8b8b; } +.radio-theme-dark .radio-player__station-desc, +.radio-theme-dark .radio-player__track, +.radio-theme-dark .radio-intro { + color: #c3c4c7; +} +.radio-theme-dark .radio-player__station-genre { + background: rgba(255, 255, 255, 0.08); +} +.radio-theme-dark .radio-player__error { + background: rgba(179, 45, 46, 0.18); + color: #ff9b9b; +} +.radio-theme-dark .radio-about-card { + background: #1d2327; + border-color: #3c434a; + color: #c3c4c7; +} +.radio-theme-dark .radio-about-card h2 { + background: #2c3338; + color: #f0f0f1; + border-bottom-color: #3c434a; +} +.radio-theme-dark #radio_dashboard_widget .radio-player__credit { + border-top-color: #3c434a; +} + +/* Auto — same dark rules behind prefers-color-scheme. Duplicated rather + than nested in @media-inside-selector (CSS doesn't allow that), kept + line-for-line in sync with the .radio-theme-dark block above. */ +@media (prefers-color-scheme: dark) { + .radio-theme-auto .radio-player__now { border-bottom-color: #3c434a; } + .radio-theme-auto .radio-player__station-name { color: #f0f0f1; } + .radio-theme-auto .radio-player__label, + .radio-theme-auto .radio-player__station-genre, + .radio-theme-auto .radio-player__volume-pct, + .radio-theme-auto .radio-player__credit, + .radio-theme-auto .radio-player__mute, + .radio-theme-auto .radio-player__station-select label, + .radio-theme-auto .radio-player__volume .dashicons { color: #a7aaad; } + .radio-theme-auto .radio-player__mute:hover { color: #f0f0f1; } + .radio-theme-auto .radio-player__mute--muted { color: #ff8b8b; } + .radio-theme-auto .radio-player__station-desc, + .radio-theme-auto .radio-player__track, + .radio-theme-auto .radio-intro { color: #c3c4c7; } + .radio-theme-auto .radio-player__station-genre { background: rgba(255, 255, 255, 0.08); } + .radio-theme-auto .radio-player__error { background: rgba(179, 45, 46, 0.18); color: #ff9b9b; } + .radio-theme-auto .radio-about-card { background: #1d2327; border-color: #3c434a; color: #c3c4c7; } + .radio-theme-auto .radio-about-card h2 { background: #2c3338; color: #f0f0f1; border-bottom-color: #3c434a; } + .radio-theme-auto #radio_dashboard_widget .radio-player__credit { border-top-color: #3c434a; } +} diff --git a/assets/js/radio.js b/assets/js/radio.js index 891bf47..d9103c5 100644 --- a/assets/js/radio.js +++ b/assets/js/radio.js @@ -6,15 +6,25 @@ * surface). Persists station + volume changes via AJAX so they survive * page reloads. * - * No build step. No dependencies. Plain ES5 + a few ES6 features that - * every browser supporting