- radio-browser.info directory search (debounced, ~58k stations) with
codec/bitrate/country metadata and save-to-stations action
- station favorites (star) persisted in StationRepository; Starred
section pinned in Stations; active-station row highlight
- motion system (Motion.kt): shared-axis nav transitions, hub hop,
metadata rise, play/pause pop; LocalReduceMotion honors system
'remove animations'
- AppHomeButton (launcher icon) top-left on every surface + SatsAmp
wordmark on the hub; home pops the stack back to the player
- transport row: add missing Next button, conventional order
stop · prev · play · next
- WaveGraphic center glow: wider, fainter, no perceptible disc edge
- StreamMetadata: strip unsubstituted {{template}} residue
- PRODUCT.md + DESIGN.md (impeccable design context); .gitignore for
re-downloadable skill engine binaries
120 lines
7.2 KiB
Markdown
120 lines
7.2 KiB
Markdown
---
|
||
name: SatsAmp
|
||
description: Android radio player with a glass-on-charcoal surface system and four user-selectable AA-verified accents.
|
||
colors:
|
||
base-top: "#23232B"
|
||
base-bottom: "#1B1B22"
|
||
panel-dark: "#202029"
|
||
paper-top: "#F2F1F7"
|
||
paper-bottom: "#E7E5F0"
|
||
panel-light: "#FBFAFE"
|
||
text-primary-dark: "#FFFFFF"
|
||
text-secondary-dark: "#D9D9DE"
|
||
text-muted-dark: "#8C8C96"
|
||
text-primary-light: "#1B1B22"
|
||
violet-fill: "#7C4DFF"
|
||
violet-text: "#A78BFF"
|
||
blue-fill: "#4C7DFF"
|
||
blue-text: "#93B4FF"
|
||
green-fill: "#1FA855"
|
||
green-text: "#5BD98A"
|
||
orange-fill: "#F97316"
|
||
orange-text: "#FFB37E"
|
||
violet-fill-light: "#6A3DE0"
|
||
blue-fill-light: "#2563EB"
|
||
green-fill-light: "#0C7A38"
|
||
orange-fill-light: "#C2570A"
|
||
ink-on-fill: "#141419"
|
||
success: "#34C759"
|
||
error: "#EF4444"
|
||
warning: "#F59E0B"
|
||
typography:
|
||
display:
|
||
fontFamily: "System default (Roboto)"
|
||
fontWeight: 700
|
||
title:
|
||
fontFamily: "System default (Roboto)"
|
||
fontWeight: 600
|
||
body:
|
||
fontFamily: "System default (Roboto)"
|
||
fontWeight: 400
|
||
caption:
|
||
fontFamily: "System default (Roboto)"
|
||
fontSize: "12sp"
|
||
fontWeight: 400
|
||
rounded:
|
||
card: "20dp"
|
||
input: "14dp"
|
||
surface: "12dp"
|
||
full: "circle"
|
||
components:
|
||
button-accent:
|
||
backgroundColor: "{colors.violet-fill}"
|
||
textColor: "#FFFFFF"
|
||
rounded: "{rounded.input}"
|
||
save-button:
|
||
backgroundColor: "{colors.success}"
|
||
textColor: "#FFFFFF"
|
||
rounded: "{rounded.full}"
|
||
glass-card:
|
||
backgroundColor: "rgba(255,255,255,0.06)"
|
||
textColor: "{colors.text-primary-dark}"
|
||
rounded: "{rounded.card}"
|
||
input-glass:
|
||
backgroundColor: "rgba(255,255,255,0.09)"
|
||
textColor: "{colors.text-primary-dark}"
|
||
rounded: "{rounded.input}"
|
||
---
|
||
|
||
## Overview
|
||
|
||
SatsAmp is a personal Android radio player (Kotlin + Jetpack Compose, Material 3) whose surface language is translucent glass panels over a charcoal gradient (dark) or paper gradient (light), lit by a soft radial accent glow. The user picks one of four accents — violet, blue, green, orange — and one of System/Dark/Light modes at runtime; every screen must resolve through the token set, never through hardcoded colors. Accessibility is a hard floor: every accent/mode combination is AA-verified.
|
||
|
||
The normative source of truth is `app/src/main/java/com/rada/ui/theme/Tokens.kt` (`UiTokens`, `tokensFor()`), mirrored by `design/glass-radio-theme.css` for web demos. `Theme.kt` bridges the tokens into the M3 `ColorScheme` and provides `LocalTokens`; the M3 scheme is derived, not independent.
|
||
|
||
## Colors
|
||
|
||
**Eight real themes** = 4 accents × dark/light, each a `UiTokens` instance of 21 roles read via `LocalTokens.current`:
|
||
|
||
- **Base:** dark charcoal gradient `#23232B → #1B1B22` with solid panel `#202029`; light paper gradient `#F2F1F7 → #E7E5F0` with panel `#FBFAFE`.
|
||
- **Glass layers (dark):** fill `rgba(255,255,255,0.06)`, strong fill `0.09`, decorative border `0.10`, interactive border `0.36` (≥3:1 per WCAG 1.4.11). Light mode inverts to translucent white over paper (`0.62`/`0.78`) with ink borders.
|
||
- **textSafe:** a ~90% opaque base-colored overlay always placed under text sitting on glass. Text never touches bare glass.
|
||
- **Text:** dark mode white / `#D9D9DE` / `#8C8C96` (muted is the caption floor, ≥4.5:1); light mode `#1B1B22` at 100% / 80% / 62%.
|
||
- **Accents:** dark-mode fills violet `#7C4DFF`, blue `#4C7DFF`, green `#1FA855`, orange `#F97316`; light-mode fills are deepened (`#6A3DE0`, `#2563EB`, `#0C7A38`, `#C2570A`) to hold contrast on paper. Each accent defines fill, hover fill, `accentOnFill` (white for violet, ink `#141419` for blue/green/orange in dark — this flip is the AA mechanism), `accentText` for text/icons on surfaces, and `accentSoft` (~16% alpha) for active-state backfills.
|
||
- **Status:** success `#34C759` (save/confirm), error `#EF4444`, warning `#F59E0B` — mode-independent constants.
|
||
|
||
## Typography
|
||
|
||
System default font (Roboto), no bundled typefaces. Hierarchy comes from `MaterialTheme.typography` M3 roles; weight emphasis clusters at SemiBold/Bold for titles and controls. Only four local `fontSize` overrides exist (12–13sp captions) — prefer M3 roles over new overrides.
|
||
|
||
## Layout
|
||
|
||
Single-activity Compose app with bottom navigation across library / stations / streams / player surfaces. Full-bleed `GlassBackground` (base gradient + one radial accent glow anchored upper-right, alpha 0.10–0.14) sits under all content. Panels, not page sections, carry grouping; content scrolls over the fixed background.
|
||
|
||
## Elevation & Depth
|
||
|
||
No shadow vocabulary — depth is tonal layering: base gradient → radial glow → translucent glass fill → `textSafe` overlay → content. Compose has no backdrop blur without a library, so "glass" means the translucent layer plus a 1px inner highlight border. Do not introduce `shadowElevation` to fake depth; add a tonal layer instead.
|
||
|
||
## Shapes
|
||
|
||
Two named radii: `SatsAmpGlass.rCard` = 20dp (cards, sheets) and `rInput` = 14dp (inputs, small buttons). `CircleShape` is reserved for playback controls, avatars, and record/save FABs. New surfaces should use a named radius, not a new one-off value.
|
||
|
||
## Components
|
||
|
||
- **Accent button** — `accentFill` background, `accentOnFill` label, 14dp radius, `accentFillHover` on press.
|
||
- **Glass card** — translucent fill + decorative border + 20dp radius; text goes on the `textSafe` overlay.
|
||
- **Input field** — `glassFillStrong`, 14dp radius, `borderInteractive` when focused (`focusRing` = `accentText`).
|
||
- **Save/record control** — `success` green, circular; the one fixed-color control by design (it reads the same in every accent).
|
||
- **Active nav/tab state** — `accentSoft` backfill + `accentText` icon/label, never a filled accent.
|
||
|
||
**Motion system** (`ui/theme/Motion.kt`): one shared vocabulary — `MotionArrival` (0.16,1,0.3,1) for entrances, `MotionExit` (M3 emphasized-accelerate) for faster exits, `MotionStandard` for state; duration budget 130 feedback / 220 state / 340 view / 140 exit. Navigation is shared-axis (details slide in from the right ±¼ travel, hub hops scale-settle 0.94→1); metadata changes rise ⅓-line in and fast-fade out; `LocalReduceMotion` (system animator scale = 0) reduces every path to a short opacity cut.
|
||
|
||
## Do's and Don'ts
|
||
|
||
- **Do** read colors via `LocalTokens.current` or the derived M3 scheme; every new screen must be correct in all 8 theme combinations.
|
||
- **Do** place `textSafe` under any text on glass, and keep `textMuted`-equivalent contrast ≥4.5:1.
|
||
- **Do** follow the active accent for anything tinted — buttons, glows, active states, brush gradients (use `SatsAmpGradients.accentBrush()`, not the static gradients).
|
||
- **Don't** use `SatsAmpGradients.primary`/`primaryHorizontal` or the fixed `PremiumBlue/Cyan/Pink` brand colors — they are violet-locked legacy and break non-violet themes; prefer `accentBrush()`.
|
||
- **Don't** pull from the legacy terminal palette (`TerminalGreen`, `TerminalGold`, …) in `Color.kt`; it belongs to no shipped surface.
|
||
- **Don't** use `PremiumError` `#EF4444` as text on light backgrounds without darkening — it fails AA on paper (known gap; fix or pair with an icon when used on light).
|
||
- **Don't** introduce one-off radii (ad-hoc 12/16dp already exist in places); extend the named set deliberately if the system genuinely needs a new step.
|