/* Radio — admin styles, WordPress-native */ /* ────────────────────────────────────────────────────────────────── * Player layout (works inside .postbox .inside on main page, * and bare on the dashboard widget where .inside is the parent) * ─────────────────────────────────────────────────────────────── */ .radio-player { display: flex; flex-direction: column; gap: 14px; } /* Now Playing — left-aligned, body text size, no custom typography */ .radio-player__now { display: flex; align-items: baseline; flex-wrap: wrap; gap: 8px; padding-bottom: 10px; border-bottom: 1px solid #dcdcde; } .radio-player__label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: #646970; font-weight: 600; margin-right: 4px; } /* ────────────────────────────────────────────────────────────────── * Now-playing indicator — CSS dancing bars (always present) + * optional Web Audio frequency visualizer (canvas, progressive * upgrade). The JS toggles a `.is-playing` class on the parent * `.radio-player` to drive the dance; when the visualizer is * successfully wired, it hides the bars and reveals the canvas. * ─────────────────────────────────────────────────────────────── */ .radio-player__indicator { display: inline-flex; align-items: flex-end; height: 14px; min-width: 18px; margin-right: 6px; vertical-align: middle; overflow: hidden; } .radio-player__bars { display: inline-flex; align-items: flex-end; height: 100%; width: 18px; gap: 2px; } .radio-player__bars span { display: inline-block; width: 2px; height: 100%; background: var(--wp-admin-theme-color, #2271b1); border-radius: 1px; transform-origin: bottom; transform: scaleY(0.25); transition: transform 0.2s ease; opacity: 0.65; } /* When playing, each bar dances with a staggered delay so the row has a lifelike, slightly out-of-phase pulse rather than uniform thumping. ~0.85s loop keeps it lively without being twitchy. */ .radio-player.is-playing .radio-player__bars span { animation: radio-bars-dance 0.85s ease-in-out infinite; opacity: 1; } .radio-player.is-playing .radio-player__bars span:nth-child(1) { animation-delay: 0s; } .radio-player.is-playing .radio-player__bars span:nth-child(2) { animation-delay: 0.18s; } .radio-player.is-playing .radio-player__bars span:nth-child(3) { animation-delay: 0.36s; } .radio-player.is-playing .radio-player__bars span:nth-child(4) { animation-delay: 0.09s; } @keyframes radio-bars-dance { 0%, 100% { transform: scaleY(0.3); } 20% { transform: scaleY(0.9); } 40% { transform: scaleY(0.5); } 60% { transform: scaleY(1); } 80% { transform: scaleY(0.65); } } /* Web Audio canvas — wider than the CSS bars so the live frequency data has room to breathe. JS swaps display when the visualizer confirms it's receiving real (non-zero) data. */ .radio-player__viz { display: block; height: 14px; width: 60px; } .radio-player__station-name { font-size: 14px; font-weight: 600; color: #1d2327; } .radio-player__station-desc { font-size: 13px; color: #50575e; flex: 1 1 100%; margin: 0; } .radio-player__station-genre { font-size: 10px; color: #50575e; text-transform: uppercase; letter-spacing: 0.04em; 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 */ .radio-player__controls { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; } .radio-player__play { /* native .button .button-primary styling; just ensure glyph aligns */ display: inline-flex !important; align-items: baseline; gap: 6px; } /* Unicode play/pause glyph (not a dashicon — those sit low inside their own font box and look like they're below the text baseline). A plain ▶ / ‖ glyph renders on the text baseline like any other character. */ .radio-player__play-glyph { display: inline-block; font-size: 11px; line-height: 1; /* Coerce to text rendering rather than colour-emoji on systems that might otherwise pick up an emoji variant for ▶ / ‖. */ font-variant-emoji: text; font-family: "Helvetica Neue", Arial, sans-serif; } /* v0.6.0: Pop-out button — small secondary button beside Play. Uses WP native .button styles; the ↗ glyph signals "opens in another window." */ .radio-player__popout { display: inline-flex; align-items: center; gap: 4px; font-size: 12px; } .radio-player__popout span[aria-hidden] { font-size: 13px; line-height: 1; } .radio-player__volume { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; width: 220px; } .radio-player__volume .dashicons { color: #646970; font-size: 16px; width: 16px; 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; /* Use WP's admin theme colour for the slider thumb/track */ accent-color: var(--wp-admin-theme-color, #2271b1); } .radio-player__volume-pct { min-width: 36px; font-size: 12px; color: #646970; text-align: right; font-variant-numeric: tabular-nums; } /* Station selector — label-on-top, full-width select */ .radio-player__station-select { display: flex; flex-direction: column; gap: 4px; } .radio-player__station-select label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: #646970; font-weight: 600; } .radio-player__station-select select { width: 100%; max-width: 360px; } /* Error notice — uses WP notice styling */ .radio-player__error { color: #b32d2e; font-size: 13px; padding: 6px 10px; background: #fcf0f1; border-left: 4px solid #b32d2e; border-radius: 0; margin: 0; } /* Credit footer */ .radio-player__credit { margin: 0; color: #646970; font-size: 12px; } .radio-player__credit--main { margin-top: 12px; } .radio-player__credit a { color: var(--wp-admin-theme-color, #2271b1); text-decoration: none; } .radio-player__credit a:hover { text-decoration: underline; } /* ────────────────────────────────────────────────────────────────── * Main admin page — wrap player in a postbox-like card. * Constrained max-width keeps the player a focused settings-page card * instead of stretching to fill the full admin width. * ─────────────────────────────────────────────────────────────── */ .radio-wrap { max-width: 880px; } .radio-wrap .radio-player { margin-top: 4px; } .radio-intro { margin: 0 0 16px; color: #50575e; font-size: 13px; } /* Small grey pill that follows the Settings page H1 — at-a-glance confirmation of the version you are running (v0.6.2). */ .radio-version-badge { display: inline-block; margin-left: 8px; padding: 2px 8px; background: #e2e4e7; color: #50575e; border-radius: 10px; font-size: 11px; font-weight: 600; vertical-align: middle; letter-spacing: 0.02em; } .radio-theme-dark .radio-version-badge { background: #2c3338; color: #c3c4c7; } /* ────────────────────────────────────────────────────────────────── * Dashboard widget — no nested card; bare content inside .inside * (WP renders the widget as a postbox already; don't double up) * ─────────────────────────────────────────────────────────────── */ #radio_dashboard_widget .radio-player { gap: 10px; } #radio_dashboard_widget .radio-player__now { padding-bottom: 8px; } #radio_dashboard_widget .radio-player__station-name { font-size: 14px; } #radio_dashboard_widget .radio-player__credit { border-top: 1px solid #dcdcde; padding-top: 8px; margin-top: 4px; text-align: center; } /* ────────────────────────────────────────────────────────────────── * About page — postbox-style cards in a metabox column layout * ─────────────────────────────────────────────────────────────── */ .radio-about-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 16px; max-width: 1100px; margin-top: 16px; } .radio-about-card { background: #fff; border: 1px solid #c3c4c7; box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04); } .radio-about-card h2 { margin: 0; padding: 8px 12px; font-size: 14px; font-weight: 600; background: #f6f7f7; border-bottom: 1px solid #c3c4c7; } .radio-about-card > p, .radio-about-card > ul, .radio-about-card > a { padding: 12px; margin: 0; } .radio-about-card > p + p { padding-top: 0; } .radio-about-changelog-link { display: inline-block; margin: 0 12px 12px; font-size: 13px; color: var(--wp-admin-theme-color, #2271b1); text-decoration: none; } .radio-about-changelog-link:hover { text-decoration: underline; } /* ────────────────────────────────────────────────────────────────── * Version history — full-width card BELOW the top row of cards * (v0.6.1). Latest release shown in full; earlier releases collapse * to one line each so the card stays compact however many versions * ship. Full prose for older versions lives in the CHANGELOG on Gitea. * ─────────────────────────────────────────────────────────────── */ .radio-about-versions { max-width: 1100px; margin-top: 16px; background: #fff; border: 1px solid #c3c4c7; box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04); } .radio-about-versions h2 { margin: 0; padding: 8px 12px; font-size: 14px; font-weight: 600; background: #f6f7f7; border-bottom: 1px solid #c3c4c7; } .radio-about-versions__latest { padding: 12px 12px 8px; } .radio-about-versions__latest .ver { font-weight: 600; color: var(--wp-admin-theme-color, #2271b1); font-size: 13px; } .radio-about-versions__latest .latest { display: inline-block; margin-left: 6px; padding: 1px 7px; background: #00a32a; color: #fff; border-radius: 9px; font-size: 11px; font-weight: 600; vertical-align: middle; } .radio-about-versions__latest p { margin: 6px 0 0; font-size: 13px; color: #1d2327; } .radio-about-versions h3 { margin: 4px 0 0; padding: 8px 12px 4px; font-size: 11px; font-weight: 600; color: #646970; text-transform: uppercase; letter-spacing: 0.06em; border-top: 1px solid #f0f0f1; } .radio-about-versions__earlier { list-style: none; margin: 0; padding: 4px 12px 8px; } .radio-about-versions__earlier li { padding: 4px 0; font-size: 13px; color: #50575e; } .radio-about-versions__earlier .ver { display: inline-block; min-width: 48px; font-weight: 600; color: var(--wp-admin-theme-color, #2271b1); } .radio-about-versions__earlier .ver-date { display: inline-block; min-width: 110px; font-size: 11px; color: #646970; } /* ────────────────────────────────────────────────────────────────── * 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-about-versions { background: #1d2327; border-color: #3c434a; color: #c3c4c7; } .radio-theme-dark .radio-about-versions h2 { background: #2c3338; color: #f0f0f1; border-bottom-color: #3c434a; } .radio-theme-dark .radio-about-versions h3 { color: #a7aaad; border-top-color: #3c434a; } .radio-theme-dark .radio-about-versions__latest p { color: #c3c4c7; } .radio-theme-dark .radio-about-versions__earlier li { color: #c3c4c7; } .radio-theme-dark .radio-about-versions__earlier .ver-date { color: #a7aaad; } .radio-theme-dark #radio_dashboard_widget .radio-player__credit { border-top-color: #3c434a; } /* Give the player its own dark surface when theme=dark so the dark text has something to read against — WP postboxes don't follow our dark choice, so without this the light text would sit on white. */ .radio-theme-dark .radio-player { background: #1d2327; padding: 14px 16px; border-radius: 3px; border: 1px solid #3c434a; } /* ────────────────────────────────────────────────────────────────── * History + Favourites page (v0.5.0) * ─────────────────────────────────────────────────────────────── */ .radio-history-wrap { max-width: 1100px; } .radio-tab-count { color: #646970; font-weight: 400; margin-left: 2px; } .radio-history-toolbar { display: flex; gap: 10px; align-items: center; margin: 12px 0 16px; flex-wrap: wrap; } .radio-history-toolbar input[type="search"] { flex: 1 1 220px; max-width: 360px; } .radio-history-toolbar select { max-width: 220px; } .radio-history-clear { margin-left: auto !important; } .radio-history-empty { margin-top: 24px; padding: 24px; background: #fff; border: 1px solid #c3c4c7; color: #50575e; font-style: italic; text-align: center; } .radio-history-table { background: #fff; } .radio-history-table th, .radio-history-table td { padding: 10px 12px; vertical-align: middle; } .radio-history-table th.when, .radio-history-table th.station, .radio-history-table th.fav { white-space: nowrap; } .radio-history-table .when, .radio-history-table .station { color: #646970; font-size: 12px; white-space: nowrap; } .radio-history-table .track { font-size: 14px; } .radio-history-table .track strong { color: #1d2327; } .radio-history-table .search { white-space: nowrap; } .radio-history-table .fav { text-align: center; width: 42px; } /* Search-link pills, brand-tinted on hover */ .radio-search-link { display: inline-block; padding: 2px 8px; margin-right: 4px; font-size: 11px; font-weight: 600; border-radius: 10px; text-decoration: none; background: #e2e4e7; color: #1d2327; transition: background 0.15s ease, color 0.15s ease; } .radio-search-link:hover { background: #d0d3d6; } .radio-search-link--spotify:hover { background: #1ed760; color: #000; } .radio-search-link--youtube:hover { background: #ff0000; color: #fff; } .radio-search-link--apple:hover { background: #fa57c1; color: #fff; } .radio-search-link--bandcamp:hover { background: #629aa9; color: #fff; } /* Favourite star — grey by default, golden when starred */ .radio-fav-btn { background: none; border: 0; padding: 4px 6px; cursor: pointer; font-size: 18px; line-height: 1; color: #c3c4c7; transition: transform 0.12s ease, color 0.12s ease; } .radio-fav-btn:hover { transform: scale(1.2); color: #f0b849; } .radio-fav-btn:focus { outline: 2px solid var(--wp-admin-theme-color, #2271b1); outline-offset: 1px; } .radio-fav-btn.is-fav { color: #f0b849; } .radio-fav-btn[disabled] { opacity: 0.5; cursor: progress; } /* Filtered-out rows hidden via JS */ .radio-history-row.is-filtered { display: none; } /* Dark theme — history surfaces */ .radio-theme-dark .radio-history-table { background: #1d2327; color: #c3c4c7; border-color: #3c434a; } .radio-theme-dark .radio-history-table th { background: #2c3338; color: #f0f0f1; border-bottom-color: #3c434a; } .radio-theme-dark .radio-history-table .track strong { color: #f0f0f1; } .radio-theme-dark .radio-history-table .when, .radio-theme-dark .radio-history-table .station { color: #a7aaad; } .radio-theme-dark .radio-search-link { background: #2c3338; color: #c3c4c7; } .radio-theme-dark .radio-history-empty { background: #1d2327; border-color: #3c434a; color: #c3c4c7; } .radio-theme-dark .radio-fav-btn { color: #50575e; } .radio-theme-dark .radio-fav-btn.is-fav, .radio-theme-dark .radio-fav-btn:hover { color: #f0b849; }