SatsAmp/design/glass-radio-theme.css
avi 1b7c6febbb feat: rename app to SatsAmp + appearance themes (violet/blue/green/orange accents, System/Dark/Light modes)
- project dir ~/Projects/RadAmp -> ~/Projects/SatsAmp; classes, strings, launcher label, UA strings renamed; applicationId + radamp.db kept so existing installs/data survive
- UiPrefs (SharedPreferences) + UiTokens: 4 accents x dark/light, every text/accent pair AA-verified (accentOnFill picks ink vs white per accent)
- legacy Premium* colors rewritten to LocalTokens.current reads; theme switches live, persists across restart
- Appearance dialog in Library top bar (swatches + System/Dark/Light), verified on-device: orange live-switch, light mode + persistence, folder-tab regression pass
2026-09-07 18:45:29 -05:00

465 lines
18 KiB
CSS
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* ============================================================
SatsAmp Glass — glassmorphism theme for audio/radio streaming
Dark default + light variant. WCAG 2.2 AA verified (see demo).
============================================================ */
/* ---------- 1. TOKENS ---------- */
:root {
color-scheme: dark;
/* Base background: flat dark charcoal, very subtle vertical gradient */
--bg-top: #23232B;
--bg-bottom: #1B1B22;
--bg-gradient: linear-gradient(180deg, var(--bg-top) 0%, var(--bg-bottom) 100%);
/* Glass recipe */
--glass-bg: rgba(255, 255, 255, 0.06);
--glass-bg-strong: rgba(255, 255, 255, 0.09); /* hover */
--glass-border: rgba(255, 255, 255, 0.10); /* decorative only, NOT a 3:1 boundary */
--border-interactive: rgba(255, 255, 255, 0.36); /* 3.0:1 on glass — WCAG 1.4.11 */
--glass-blur: 18px; /* hard cap: 18–20px, never higher */
--glass-saturate: 160%;
--glass-highlight: inset 0 1px 0 rgba(255, 255, 255, 0.14);
--shadow-glass: 0 10px 30px rgba(0, 0, 0, 0.35);
--shadow-fab: 0 12px 32px rgba(0, 0, 0, 0.5), 0 4px 10px rgba(124, 77, 255, 0.35);
/* Accent — violet */
--accent: #7C4DFF;
--accent-hover: #8E66FF;
--accent-pressed: #6A3DE0;
--accent-soft: rgba(124, 77, 255, 0.16); /* icon chips, active fills */
/* #7C4DFF is only 2.71:1 on glass → text/active-label accent on glass uses this
(4.83:1 on glass, 5.78:1 on base). #7C4DFF itself is reserved for fills
and for text on the SOLID base (3.24:1, large text only). */
--accent-text: #A78BFF;
/* Text (all pairs ≥4.5:1 on glass; do not darken) */
--text-title: #FFFFFF; /* 13.0:1 on glass */
--text-body: rgba(255, 255, 255, 0.88); /* 10.5:1 on glass */
--text-caption: rgba(255, 255, 255, 0.55); /* 5.1:1 on glass — floor */
/* Status */
--live: #34C759; /* 5.9:1 on glass */
--danger: #FF6B6B;
/* Focus */
--focus-ring: #A78BFF; /* 4.8:1 vs glass */
--focus-width: 2px;
--focus-offset: 3px;
/* Solid panels (long lists / body copy — glass is sparing) */
--panel-solid: #202029; /* ≈90% opaque base */
--panel-border: rgba(255, 255, 255, 0.08);
/* Radii */
--r-card: 20px; /* now-playing bar, cards */
--r-input: 14px; /* inputs, chips */
--r-pill: 999px; /* pills, circular buttons */
/* Spacing scale */
--sp-1: 4px; --sp-2: 8px; --sp-3: 12px; --sp-4: 16px;
--sp-5: 20px; --sp-6: 24px; --sp-8: 32px; --sp-10: 40px;
/* Type */
--font: "Inter", "SF Pro Text", "Geist", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
--fs-body: 16px; --lh-body: 1.6;
--fs-title: 22px; --fs-hero: 28px;
--fs-caption: 12px; --ls-caption: 0.4px;
/* Safe areas */
--safe-bottom: env(safe-area-inset-bottom, 0px);
}
/* ---------- Light variant ---------- */
[data-theme="light"] {
color-scheme: light;
--bg-top: #F2F1F7;
--bg-bottom: #E7E5F0;
--glass-bg: rgba(255, 255, 255, 0.62);
--glass-bg-strong: rgba(255, 255, 255, 0.78);
--glass-border: rgba(35, 35, 43, 0.10);
--border-interactive: rgba(35, 35, 43, 0.45); /* 3:1 on light glass */
--glass-highlight: inset 0 1px 0 rgba(255, 255, 255, 0.65);
--shadow-glass: 0 10px 30px rgba(35, 35, 43, 0.14);
--accent-soft: rgba(124, 77, 255, 0.12);
/* On light glass: dark text carries contrast, violet text uses deeper mix */
--text-title: #1B1B22;
--text-body: rgba(27, 27, 34, 0.88);
--text-caption: rgba(27, 27, 34, 0.62); /* ≥4.5:1 on light glass */
--accent-text: #5B2EDB; /* ≥4.5:1 on light glass */
--panel-solid: #FBFAFE;
--panel-border: rgba(35, 35, 43, 0.10);
--focus-ring: #5B2EDB;
--live: #0F7A36; /* #34C759 is only 2.13:1 on light glass — darken per AA rule */
--danger: #C03434;
}
/* ---------- 2. BASE ---------- */
.glass-app {
font-family: var(--font);
font-size: var(--fs-body);
line-height: var(--lh-body);
color: var(--text-body);
background: var(--bg-gradient);
min-height: 100%;
-webkit-font-smoothing: antialiased;
/* Violet glows live UNDER glass, never behind text: painted on the root only */
background-image:
radial-gradient(60% 32% at 78% 12%, rgba(124, 77, 255, 0.14), transparent 70%),
radial-gradient(50% 28% at 18% 88%, rgba(124, 77, 255, 0.08), transparent 70%),
var(--bg-gradient);
}
.glass-app *, *::before, *::after { box-sizing: border-box; }
h1, h2, h3 { color: var(--text-title); font-weight: 700; margin: 0; }
.caption {
font-size: var(--fs-caption);
letter-spacing: var(--ls-caption);
color: var(--text-caption);
font-weight: 500; /* no weight below 400 on glass; 500 keeps captions crisp */
}
.no-italic, .glass-app em { font-style: normal; }
/* Focus ring on every interactive element, visible over blur */
.glass-app :focus-visible {
outline: var(--focus-width) solid var(--focus-ring);
outline-offset: var(--focus-offset);
border-radius: inherit;
}
/* Skip link */
.skip-link {
position: absolute; left: var(--sp-2); top: -48px; z-index: 100;
background: var(--accent); color: #fff; padding: var(--sp-2) var(--sp-4);
border-radius: var(--r-input); text-decoration: none; font-weight: 600;
transition: top 0.15s ease;
}
.skip-link:focus { top: var(--sp-2); }
/* ---------- 3. GLASS PRIMITIVE ---------- */
.glass {
background: var(--glass-bg);
border: 1px solid var(--glass-border);
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
box-shadow: var(--glass-highlight), var(--shadow-glass);
}
/* Solid fallback when backdrop-filter is unsupported: opaque surface, same border */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
.glass { background: var(--panel-solid); }
}
/* Text-on-glass safety: 90%-opaque overlay behind text blocks that sit over a
glow or artwork. The blur never carries contrast — this does. */
.glass--text-safe::after {
content: ""; position: absolute; inset: 0; z-index: -1;
background: rgba(27, 27, 34, 0.9);
border-radius: inherit;
}
[data-theme="light"] .glass--text-safe::after { background: rgba(251, 250, 254, 0.92); }
/* ---------- 4. STATUS BAR ---------- */
.status-bar {
display: flex; justify-content: space-between; align-items: center;
padding: var(--sp-2) var(--sp-5);
color: var(--text-title);
font-size: 14px; font-weight: 600;
font-variant-numeric: tabular-nums;
}
.status-bar .sys-icons { display: flex; gap: var(--sp-2); align-items: center; }
.status-bar svg { display: block; }
/* ---------- 5. NOW PLAYING BAR (hero glass) ---------- */
.np-bar {
position: sticky; top: 0; z-index: 20;
display: grid;
grid-template-columns: 48px 1fr;
gap: var(--sp-3);
align-items: center;
padding: var(--sp-3) var(--sp-4);
border-radius: var(--r-card);
margin: 0 var(--sp-4);
}
.np-chip {
width: 48px; height: 48px;
border-radius: 14px;
display: grid; place-items: center;
background: var(--accent);
color: #fff;
box-shadow: 0 4px 14px rgba(124, 77, 255, 0.45);
}
.np-title {
color: var(--text-title); font-weight: 700; font-size: 15px;
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.np-meta {
display: flex; justify-content: space-between; align-items: center;
gap: var(--sp-2);
}
.np-meta .caption { font-variant-numeric: tabular-nums; }
.signal-bars { display: inline-flex; gap: 2px; align-items: flex-end; height: 10px; }
.signal-bars i { width: 3px; background: var(--live); border-radius: 1px; }
.signal-bars i:nth-child(1) { height: 4px; } .signal-bars i:nth-child(2) { height: 6px; }
.signal-bars i:nth-child(3) { height: 8px; } .signal-bars i:nth-child(4) { height: 10px; opacity: 0.35; }
.live-dot {
display: inline-block; width: 8px; height: 8px; border-radius: 50%;
background: var(--live); margin-right: var(--sp-1);
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse { 50% { opacity: 0.45; } }
/* ---------- 6. NOW PLAYING DETAIL ---------- */
.np-hero-title { font-size: var(--fs-hero); line-height: 1.2; }
.np-detail { padding: var(--sp-6) var(--sp-5) var(--sp-4); }
.np-artwork-row { display: flex; gap: var(--sp-4); align-items: flex-end; margin-top: var(--sp-5); }
.np-artwork {
width: 132px; height: 132px; border-radius: var(--r-card);
background:
radial-gradient(120% 120% at 20% 10%, rgba(124,77,255,0.55), transparent 60%),
linear-gradient(160deg, #2C2C3A, #1E1E28);
border: 1px solid var(--glass-border);
box-shadow: var(--shadow-glass);
display: grid; place-items: center; color: var(--accent-text);
}
.station-chip {
display: inline-flex; align-items: center; gap: var(--sp-2);
padding: var(--sp-2) var(--sp-4);
border-radius: var(--r-pill);
font-size: 13px; font-weight: 600;
color: var(--text-body);
}
.station-chip .np-chip { width: 24px; height: 24px; border-radius: 8px; }
.station-chip .np-chip svg { width: 14px; height: 14px; }
/* Volume slider — glass track, violet fill + thumb */
.volume {
--fill: 72%;
margin-top: var(--sp-6);
}
.volume .caption { display: flex; justify-content: space-between; margin-bottom: var(--sp-2); }
input[type="range"].vol {
-webkit-appearance: none; appearance: none;
width: 100%; height: 10px; border-radius: var(--r-pill);
background:
linear-gradient(90deg, var(--accent) var(--fill), transparent var(--fill)),
var(--glass-bg);
border: 1px solid var(--border-interactive);
box-shadow: var(--glass-highlight);
}
input[type="range"].vol::-webkit-slider-thumb {
-webkit-appearance: none; appearance: none;
width: 24px; height: 24px; border-radius: 50%;
background: var(--accent);
border: 2px solid #fff;
box-shadow: 0 4px 12px rgba(124, 77, 255, 0.5);
cursor: grab;
}
input[type="range"].vol::-moz-range-thumb {
width: 24px; height: 24px; border-radius: 50%;
background: var(--accent); border: 2px solid #fff;
box-shadow: 0 4px 12px rgba(124, 77, 255, 0.5);
}
input[type="range"].vol::-moz-range-progress { background: var(--accent); height: 10px; border-radius: var(--r-pill); }
/* Transport row + FAB */
.transport {
display: flex; align-items: center; justify-content: center;
gap: var(--sp-6); margin-top: var(--sp-8);
}
.icon-btn {
width: 48px; height: 48px; border-radius: var(--r-pill);
display: grid; place-items: center;
border: 1px solid var(--glass-border);
background: var(--glass-bg); color: var(--text-body);
cursor: pointer;
box-shadow: var(--glass-highlight);
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
transition: background 0.15s ease, transform 0.1s ease;
}
.icon-btn:hover { background: var(--glass-bg-strong); color: var(--text-title); }
.icon-btn:active { transform: scale(0.94); }
.icon-btn.is-on { background: var(--accent-soft); color: var(--accent-text); border-color: var(--accent); }
.fab {
width: 64px; height: 64px; border-radius: var(--r-pill);
border: none; cursor: pointer;
background: var(--accent); color: #fff; /* #FFF on #7C4DFF = 4.81:1 */
display: grid; place-items: center;
box-shadow: var(--shadow-fab);
transition: background 0.15s ease, transform 0.1s ease;
}
.fab:hover { background: var(--accent-hover); }
.fab:active { background: var(--accent-pressed); transform: scale(0.95); }
.fab--sm { width: 52px; height: 52px; }
/* ---------- 7. SEGMENTED TAB STRIP ---------- */
.seg-tabs {
display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
gap: var(--sp-1);
margin: var(--sp-5) var(--sp-4) 0;
padding: var(--sp-1);
border-radius: var(--r-input);
}
.seg-tab {
display: flex; flex-direction: column; align-items: center; gap: 2px;
padding: var(--sp-2) var(--sp-1);
border: none; background: transparent; cursor: pointer;
border-radius: 10px;
color: var(--text-caption);
font: 600 12px/1.35 var(--font);
letter-spacing: var(--ls-caption);
position: relative;
}
.seg-tab svg { width: 20px; height: 20px; }
.seg-tab:hover { color: var(--text-body); }
.seg-tab[aria-selected="true"] {
color: var(--accent-text); /* AA on glass */
}
.seg-tab[aria-selected="true"]::after {
content: ""; position: absolute; bottom: 2px; left: 25%; right: 25%;
height: 2px; border-radius: 2px; background: var(--accent);
}
/* ---------- 8. CONTENT PANEL (solid — glass is sparing) ---------- */
.panel {
margin: var(--sp-4);
background: var(--panel-solid);
border: 1px solid var(--panel-border);
border-radius: var(--r-card);
overflow: clip;
}
.panel-head {
display: flex; justify-content: space-between; align-items: baseline;
padding: var(--sp-4) var(--sp-4) var(--sp-2);
}
.row-item {
display: grid; grid-template-columns: 40px 1fr auto; gap: var(--sp-3);
align-items: center;
padding: var(--sp-3) var(--sp-4);
border: none; background: transparent; width: 100%; text-align: left;
color: var(--text-body); cursor: pointer;
font: inherit;
}
.row-item + .row-item { border-top: 1px solid var(--panel-border); }
.row-item:hover { background: rgba(255, 255, 255, 0.04); }
.row-item .t { color: var(--text-title); font-weight: 600; font-size: 15px; }
.row-thumb {
width: 40px; height: 40px; border-radius: 10px;
background: var(--accent-soft); color: var(--accent-text);
display: grid; place-items: center;
}
/* ---------- 9. BOTTOM NAV ---------- */
.bottom-nav {
position: sticky; bottom: 0; z-index: 20;
display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
padding: var(--sp-2) var(--sp-2) calc(var(--sp-2) + var(--safe-bottom));
border-radius: var(--r-card) var(--r-card) 0 0;
margin: 0 var(--sp-2);
}
.nav-item {
display: flex; flex-direction: column; align-items: center; gap: 2px;
padding: var(--sp-2) var(--sp-1);
min-height: 48px; /* hit target ≥44px */
border: none; background: transparent; cursor: pointer;
border-radius: var(--r-input);
color: var(--text-caption);
font: 600 11px/1.3 var(--font); letter-spacing: var(--ls-caption);
}
.nav-item svg { width: 22px; height: 22px; }
.nav-item:hover { color: var(--text-body); }
/* Active = unmistakable: violet fill behind the icon, not a subtle tint */
.nav-item[aria-current="page"] { color: var(--accent-text); }
.nav-item[aria-current="page"] .nav-ico {
background: var(--accent-soft);
box-shadow: inset 0 0 0 1px var(--accent);
border-radius: var(--r-pill);
}
.nav-ico { display: grid; place-items: center; width: 44px; height: 26px; }
/* ---------- 10. ACTION ROW ---------- */
.action-row {
display: grid; grid-template-columns: 1fr 1fr; gap: var(--sp-3);
padding: var(--sp-3) var(--sp-4);
}
.btn-glass {
display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-2);
min-height: 52px; padding: 0 var(--sp-5);
border-radius: var(--r-input);
font: 700 15px var(--font);
cursor: pointer;
color: var(--text-title);
border: 1px solid var(--border-interactive);
background: var(--glass-bg);
box-shadow: var(--glass-highlight), var(--shadow-glass);
-webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-saturate));
transition: background 0.15s ease, transform 0.1s ease;
}
.btn-glass:hover { background: var(--glass-bg-strong); }
.btn-glass:active { transform: scale(0.98); }
.btn-glass--primary {
background: var(--accent); color: #fff; border-color: transparent;
}
.btn-glass--primary:hover { background: var(--accent-hover); }
.btn-glass--primary:active { background: var(--accent-pressed); }
/* ---------- 11. TOAST + MODAL ---------- */
.toast {
position: fixed; left: 50%; bottom: calc(96px + var(--safe-bottom));
transform: translateX(-50%);
padding: var(--sp-3) var(--sp-5);
border-radius: var(--r-pill);
color: var(--text-title); font-size: 14px; font-weight: 600;
z-index: 60; max-width: min(92vw, 420px); text-align: center;
}
.modal-backdrop {
position: fixed; inset: 0; z-index: 50;
background: rgba(10, 10, 14, 0.6);
display: grid; place-items: center; padding: var(--sp-5);
}
.modal {
width: min(92vw, 380px);
border-radius: var(--r-card);
padding: var(--sp-6);
background: var(--panel-solid); /* solid: text never relies on blur */
border: 1px solid var(--glass-border);
box-shadow: var(--shadow-glass);
}
.modal h3 { font-size: var(--fs-title); margin-bottom: var(--sp-2); }
.modal p { margin: 0 0 var(--sp-5); }
.modal-actions { display: flex; justify-content: flex-end; gap: var(--sp-3); }
.link-btn {
border: none; background: none; cursor: pointer;
font: 700 15px var(--font); color: var(--accent-text);
padding: var(--sp-2) var(--sp-3); border-radius: 10px;
}
.link-btn--danger { color: var(--danger); }
/* ---------- 12. ACCESSIBILITY FALLBACKS ---------- */
@media (prefers-reduced-transparency: reduce) {
:root { --glass-bg: #262631; --glass-bg-strong: #2C2C38; }
[data-theme="light"] { --glass-bg: #FFFFFF; --glass-bg-strong: #F4F3FA; }
.glass, .icon-btn, .nav-item, .btn-glass, .toast, .seg-tabs {
-webkit-backdrop-filter: none; backdrop-filter: none;
background: var(--glass-bg);
}
.modal-backdrop { background: rgba(10, 10, 14, 0.92); }
}
@media (prefers-reduced-motion: reduce) {
.live-dot { animation: none; }
*, *::before, *::after { transition: none !important; }
.icon-btn:active, .fab:active, .btn-glass:active { transform: none; }
}
@media (forced-colors: active) {
.glass, .icon-btn, .btn-glass, .seg-tabs, .bottom-nav, .toast, .modal, .panel {
background: Canvas; border: 1px solid CanvasText;
backdrop-filter: none; -webkit-backdrop-filter: none;
box-shadow: none; forced-color-adjust: none;
}
.np-chip, .fab { background: Highlight; color: HighlightText; border: 1px solid CanvasText; }
.seg-tab[aria-selected="true"], .nav-item[aria-current="page"] { color: Highlight; }
.seg-tab[aria-selected="true"]::after { background: Highlight; }
.glass-app :focus-visible { outline: 3px solid Highlight; }
}