Match reference palette: charcoal page, blue interactive accent, pink-red reserved for recording titles and selected rows

This commit is contained in:
avi 2026-09-21 13:59:02 -05:00
commit 30a9facf89
3 changed files with 56 additions and 31 deletions

View file

@ -82,6 +82,8 @@ fun Modifier.shonarFocus(
fun SonarCard( fun SonarCard(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onClick: (() -> Unit)? = null, onClick: (() -> Unit)? = null,
containerColor: Color = Shonar.Surface,
borderColor: Color = Shonar.Border,
content: @Composable () -> Unit, content: @Composable () -> Unit,
) { ) {
val clickModifier = if (onClick != null) val clickModifier = if (onClick != null)
@ -90,8 +92,8 @@ fun SonarCard(
Surface( Surface(
modifier = modifier.then(clickModifier), modifier = modifier.then(clickModifier),
shape = Shonar.ShapeCard, shape = Shonar.ShapeCard,
color = Shonar.Surface, color = containerColor,
border = BorderStroke(1.dp, Shonar.Border), border = BorderStroke(Shonar.BorderW, borderColor),
content = content, content = content,
) )
} }

View file

@ -409,7 +409,14 @@ fun LibraryScreen(state: DesktopState) {
items(entries, key = { it.file.absolutePath }) { e -> items(entries, key = { it.file.absolutePath }) { e ->
val isSelected = e.file.absolutePath in selected val isSelected = e.file.absolutePath in selected
SonarCard(Modifier.fillMaxWidth(), SonarCard(Modifier.fillMaxWidth(),
onClick = { state.openDetail(e.file) }) { onClick = { state.openDetail(e.file) },
// Selected rows take the reference's muted rose
// wash instead of a plain surface lift.
containerColor = if (isSelected) Shonar.HighlightTint
else Shonar.Surface,
borderColor = if (isSelected) Shonar.Highlight
.copy(alpha = 0.5f) else Shonar.Border,
) {
Row(Modifier.fillMaxWidth().padding(Shonar.CardPadding), Row(Modifier.fillMaxWidth().padding(Shonar.CardPadding),
verticalAlignment = Alignment.CenterVertically) { verticalAlignment = Alignment.CenterVertically) {
Checkbox( Checkbox(
@ -417,8 +424,11 @@ fun LibraryScreen(state: DesktopState) {
onCheckedChange = { state.toggleSelected(e.file.absolutePath) }, onCheckedChange = { state.toggleSelected(e.file.absolutePath) },
) )
Column(Modifier.weight(1f)) { Column(Modifier.weight(1f)) {
// Recording titles carry the reference's
// hot pink highlight; metadata stays gray.
Text(e.file.nameWithoutExtension, Text(e.file.nameWithoutExtension,
style = MaterialTheme.typography.titleSmall, style = MaterialTheme.typography.titleSmall,
color = Shonar.Highlight,
maxLines = 1, maxLines = 1,
overflow = TextOverflow.Ellipsis) overflow = TextOverflow.Ellipsis)
Text( Text(

View file

@ -70,43 +70,52 @@ data class ShonarPalette(
val textMuted: Color, val textMuted: Color,
val error: Color, val error: Color,
val onError: Color, val onError: Color,
/** Hot pink-red used to highlight recording entries (titles,
* selected-row wash) — decorative emphasis, not an error color. */
val highlight: Color = Color(0xFFFB3F7F),
val highlightTint: Color = Color(0x26FB3F7F),
) )
/** Near-black neutral charcoal workspace, per the reference: no navy /** Charcoal with a faint blue cast, per the reference: page #16181C,
* cast, one subtle tonal step per surface level, hairline borders a * lifted blue-charcoal cards, hairline borders. Blue owns all
* few shades above each surface, pink accent used with restraint. */ * interactive accent; the reference's hot pink is reserved for
* highlighting recordings (see [ShonarPalette.highlight]). */
private val DarkPalette = ShonarPalette( private val DarkPalette = ShonarPalette(
background = Color(0xFF141517), // page: neutral near-black background = Color(0xFF16181C), // page: charcoal, blue-cast
surface = Color(0xFF1B1D21), // cards, fields, pills surface = Color(0xFF1C2029), // cards, fields, pills
surfaceAlt = Color(0xFF101113), // pressed / hover / inset surfaceAlt = Color(0xFF12151C), // pressed / hover / inset
border = Color(0xFF26282E), // hairline on dark border = Color(0xFF2A2E38), // hairline on dark
accent = Color(0xFFF45B87), // pink-magenta: active, primary accent = Color(0xFF6190E5), // blue: chips, links, progress
accentStrong = Color(0xFFDE4373), // filled-accent hover depth accentStrong = Color(0xFF4C6FAD), // filled-accent hover depth
accentGlow = Color(0x33F45B87), // soft halo (~20% pink) accentGlow = Color(0x336190E5), // soft halo (~20% blue)
inkOnAccent = Color(0xFF1F0A13), // text on filled accent inkOnAccent = Color(0xFF0B1526), // text on filled accent
textPrimary = Color(0xFFE6E7EA), textPrimary = Color(0xFFE6E8EC),
textSecondary = Color(0xFF9A9DA5), textSecondary = Color(0xFF969CAA),
textMuted = Color(0xFF6E7178), textMuted = Color(0xFF6B7080),
error = Color(0xFFF27272), // red readable on charcoal error = Color(0xFFF85149), // true red, distinct from highlight
onError = Color(0xFF1A0B0D), onError = Color(0xFF1A0B0D),
highlight = Color(0xFFFB3F7F), // hot pink-red: recording titles
highlightTint = Color(0x26FB3F7F), // ~15% pink row wash
) )
/** Light mode: warm-neutral paper, same tonal-step hierarchy; the /** Light mode: neutral-cool paper; blue and pink darken to AA values
* accent darkens to a rose that meets AA contrast on white. */ * (the highlight pink would be illegible at #FB3F7F on white). */
private val LightPalette = ShonarPalette( private val LightPalette = ShonarPalette(
background = Color(0xFFF5F4F6), // page: neutral paper background = Color(0xFFF4F5F8), // page: cool paper
surface = Color(0xFFFFFFFF), // cards, fields, pills surface = Color(0xFFFFFFFF), // cards, fields, pills
surfaceAlt = Color(0xFFECEBEE), // pressed / hover / inset surfaceAlt = Color(0xFFE9ECF2), // pressed / hover / inset
border = Color(0xFFDBDADF), // hairline on light border = Color(0xFFD7DBE4), // hairline on light
accent = Color(0xFFBE2A5C), // deep rose: readable on paper accent = Color(0xFF0550AE), // deep blue: AA on paper
accentStrong = Color(0xFF9D1F4B), // filled-accent hover depth accentStrong = Color(0xFF0B3E78), // filled-accent hover depth
accentGlow = Color(0x26BE2A5C), // soft halo (~15% rose) accentGlow = Color(0x260969DA), // soft halo (~15% blue)
inkOnAccent = Color(0xFFFFF5F8), // text on filled accent inkOnAccent = Color(0xFFF4F8FF), // text on filled accent
textPrimary = Color(0xFF1D1E22), textPrimary = Color(0xFF1C2028),
textSecondary = Color(0xFF5A5C63), textSecondary = Color(0xFF585F6C),
textMuted = Color(0xFF84868D), textMuted = Color(0xFF828A97),
error = Color(0xFFBA1A1A), // AA red on white error = Color(0xFFBA1A1A), // AA red on white
onError = Color(0xFFFFF8F7), onError = Color(0xFFFFF8F7),
highlight = Color(0xFFC2185B), // AA rose: recording highlight
highlightTint = Color(0x1AC2185B), // faint rose row wash
) )
/** Which palette the app renders in. Persisted as "dark" / "light". */ /** Which palette the app renders in. Persisted as "dark" / "light". */
@ -139,6 +148,10 @@ object Shonar {
val BrandTeal = Color(0xFF5EEAD4) val BrandTeal = Color(0xFF5EEAD4)
val BrandTealDeep = Color(0xFF4FD1C5) val BrandTealDeep = Color(0xFF4FD1C5)
/** Pink-red recording highlight (see [ShonarPalette.highlight]). */
val Highlight: Color @Composable get() = LocalPalette.current.highlight
val HighlightTint: Color @Composable get() = LocalPalette.current.highlightTint
// ---- shape tokens ---- // ---- shape tokens ----
val RadiusCard = 14.dp // cards + fields val RadiusCard = 14.dp // cards + fields
val RadiusButton = 12.dp // rectangular buttons val RadiusButton = 12.dp // rectangular buttons
@ -236,7 +249,7 @@ private fun schemeFor(p: ShonarPalette) =
if (p === DarkPalette) ShonarDarkScheme else ShonarLightScheme if (p === DarkPalette) ShonarDarkScheme else ShonarLightScheme
private val ShonarDarkScheme = darkColorScheme( private val ShonarDarkScheme = darkColorScheme(
primary = DarkPalette.accentStrong, primary = DarkPalette.accent,
onPrimary = DarkPalette.inkOnAccent, onPrimary = DarkPalette.inkOnAccent,
primaryContainer = DarkPalette.accentGlow, primaryContainer = DarkPalette.accentGlow,
onPrimaryContainer = DarkPalette.accent, onPrimaryContainer = DarkPalette.accent,