theme(849): unify presets behind one palette template + bespoke AMOLED status colors (#852)

* refactor(theme): unify all presets behind one palette template

* docs(theme): update THEMES.md and AGENTS.md for the palette template

* fix(theme): restore per-preset error slot contrast, merge StatusColors into HermesStatusColors

- onErrorContainer becomes an explicit per-preset field (was derived from
  onError — same-color pairs in Gruvbox/Catppuccin light made error text
  invisible; 6 screens render this slot)
- Default dark + AMOLED onError -> White (were <3:1 on their error color)
- drop StatusColors + statusColorsOf mapper; PaletteColors.status is typed
  HermesStatusColors directly (identical field lists, pure copy ceremony)
- ThemePalette getters now by lazy; schemeFor/statusFor accessors;
  requireNotNull on the default-theme fallback (DefaultTheme is FULL)
- resolvers internal for testability

* test(theme): contrast, mode-validation, and fallback contract tests

- every shipped mode's onError/error + onErrorContainer/errorContainer
  pair meets >= 3:1 WCAG contrast (kills the error-slot regression class)
- ThemeMode init validation rejects illegal palette combinations
- AMOLED darkOnly contract + light-mode fallback to Default light

* refactor(theme): Catppuccin — named swatches from the official palette spec

Hex values verified identical (zero color drift): repeated colors (Crust,
Surface0/1, Base, Text...) now live once as named swatches matching
catppuccin.com; bespoke light primary container tints stay local.
Mocha/Latte pair unchanged.

* refactor(theme): Gruvbox — authentic bright/faded accent scheme with named swatches

Redesign per gruvbox spec: bright accents on dark bg0-bg4, faded accents
on light bg0-bg4 (correct contrast play for light mode). Named swatches,
single Gray shared across modes. Dark error pair fixed for >= 3:1
(onErrorContainer -> DarkFg1 cream, was bright red on Bg3 = 1.87:1).

* feat(theme): replace default preset with Slate — modern slate blues + teal accents

The brand default (voltage purple/amber) becomes Slate: bright teal/blue
accents on dark bg0-bg4, faded variants on light bg0-bg4. Named swatches,
single neutral gray shared across modes. Slate is also the fallback for
darkOnly/lightOnly presets (AMOLED light mode).

* feat(theme): add Nord preset — Polar Night/Snow Storm surfaces, Frost/Aurora accents

Nord's single accent set is reused across both modes (pastel accents stay
light, so on* colors use Polar Night dark text in both modes — documented
in-file). Surface ladder shares outer tiers (Nord ships 4 dark + 3 light
tones for 5 tiers). Dark error pair fixed for >= 3:1 (onErrorContainer ->
Nord6, was Nord11 on Nord2 = 2.0:1). Wired into ThemePreset, the settings
picker, and docs; default label now reads Default (Slate).

* refactor(theme): Monochrome + AMOLED — pure grayscale ramp, true-black AMOLED variant

Monochrome: every swatch R=G=B, roles by lightness only (status colors
included — documented trade-off: pair with icons/labels). AMOLED: dark-only
true-black variant (pure #000000, OLED pixels off; #121212 baseline of
Monochrome doesn't do that), containers collapse where nothing darker
exists. Both ship maximum-contrast grayscale pairs (12.8:1+ on error
slots).

* chore(theme): drop Neon Noir, finalize docs + picker label

- remove NeonNoirScheme.kt + ThemePreset.NEON_NOIR everywhere (dispatcher,
  picker, strings EN/KO, tests, AGENTS.md, THEMES.md) — 6 presets remain:
  Default, Monochrome, Gruvbox, Catppuccin, AMOLED, Nord
- picker label for the default preset is now plain 'Default'
- THEMES.md: preset conventions section (named swatches, bright/faded
  split, grayscale-status caveat, contrast test gate) + verify step runs
  testDebugUnitTest

* fix(theme): make drawer icon colors visibly follow the active preset

Selected icon now tints with the theme's primary accent (teal for Slate,
orange for Gruvbox, mauve for Catppuccin, frost-blue for Nord) instead of
onPrimaryContainer — the new presets' containers are all dark gray with
near-white text, so the selected row looked identical across themes.
Unselected icon/text colors pinned explicitly to onSurfaceVariant.

* chore(ui): remove lightning emoji from the app

- drawer title + About name: '⚡ Hermes' -> 'Hermes' (EN + KO)
- skill markers (chat self-improvement badge, memory graph): ⚡ -> 🛠
- in-progress indicators (subagent inspection): ⚡ -> 🔄

---------

Co-authored-by: M57 <hy4ri@users.noreply.github.com>
This commit is contained in:
M57andM57 authored and GitHub committed 2026-08-08 15:58:25 +03:00
1 parent 52e33a96b7
commit ab42e19b7f
21 files changed
+1312 -862

No files matched your search

@@ -1,63 +1,90 @@
package com.m57.hermescontrol.theme.presets
import androidx.compose.material3.darkColorScheme
import androidx.compose.ui.graphics.Color
import com.m57.hermescontrol.theme.HermesAmber
import com.m57.hermescontrol.theme.HermesAmberLight
import com.m57.hermescontrol.theme.HermesPurple
import com.m57.hermescontrol.theme.HermesPurpleOnContainer
import com.m57.hermescontrol.theme.HermesStatusColors
import com.m57.hermescontrol.theme.PaletteColors
import com.m57.hermescontrol.theme.buildThemeDarkOnly
// ---------------------------------------------------------------------
// AMOLED — true-black variant of MonochromeTheme. Dark only:
// background is pure #000000 so OLED panels switch those pixels off,
// which MonochromeTheme's #121212 baseline doesn't give you.
// ---------------------------------------------------------------------
private val AmoledFloor = Color(0xFF000000) // background/surface/lowest — nothing darker exists
private val AmoledContainer = Color(0xFF0D0D0D) // surfaceContainer
private val AmoledContainerHigh = Color(0xFF1A1A1A) // surfaceContainerHigh, surfaceVariant, status containers
private val AmoledContainerHighest = Color(0xFF262626) // surfaceContainerHighest, primaryContainer
private val AmoledOutline = Color(0xFF808080)
private val AmoledOutlineVariant = Color(0xFF404040)
private val AmoledTextDim = Color(0xFFB3B3B3) // onSurfaceVariant
private val AmoledTextBright = Color(0xFFFFFFFF) // pure white — max contrast on black; primary, error, text
private val AmoledSecondary = Color(0xFFCCCCCC) // secondary accent + warning
private val AmoledTertiary = Color(0xFF999999) // tertiary accent + success
private val AmoledInfo = Color(0xFF808080) // info — same value as outline, distinct role
private val AmoledInversePrimary = Color(0xFF121212) // dark tone on the (light, fallback) inverseSurface
/**
* AMOLED color scheme (dark only).
* AMOLED theme — dark-only variant of MonochromeTheme for
* true-black displays. Background is pure #000000 rather than the
* #121212 baseline the standard Monochrome preset uses, so surfaceContainerLowest
* and surfaceContainerLow collapse to the same value — there's nothing
* darker than black to separate them into. Light mode isn't shipped
* (a light AMOLED theme is a contradiction); the dispatcher falls back
* to the default theme.
*
* AMOLED has no light variant — callers fall back to the brand default light
* scheme for light mode.
* Same accessibility note as MonochromeTheme: status colors are pure
* grayscale, separated only by lightness, so don't rely on them alone
* to convey success/warning/error/info — pair with an icon or label.
*/
val AmoledDarkColorScheme =
darkColorScheme(
primary = HermesPurple,
onPrimary = Color.White,
primaryContainer = Color(0xFF1A1040),
onPrimaryContainer = HermesPurpleOnContainer,
inversePrimary = Color(0xFF6750A4),
secondary = HermesAmber,
onSecondary = Color.Black,
secondaryContainer = Color(0xFF2A2000),
onSecondaryContainer = HermesAmberLight,
tertiary = HermesAmberLight,
onTertiary = Color.Black,
tertiaryContainer = Color(0xFF2A2000),
onTertiaryContainer = HermesAmberLight,
background = Color.Black,
onBackground = Color(0xFFE8E6EE),
surface = Color.Black,
onSurface = Color(0xFFE8E6EE),
surfaceVariant = Color(0xFF121218),
onSurfaceVariant = Color(0xFFB6B2C4),
surfaceTint = HermesPurple,
surfaceContainerLowest = Color.Black,
surfaceContainerLow = Color(0xFF08080A),
surfaceContainer = Color(0xFF0E0E12),
surfaceContainerHigh = Color(0xFF16161C),
surfaceContainerHighest = Color(0xFF1F1F28),
inverseSurface = Color(0xFFE8E6EE),
inverseOnSurface = Color.Black,
error = Color(0xFFCF6679),
onError = Color.Black,
errorContainer = Color(0xFF3700B3),
onErrorContainer = Color(0xFFF2B8B5),
outline = Color(0xFF3A3A4A),
outlineVariant = Color(0xFF252532),
scrim = Color.Black,
val AmoledTheme =
buildThemeDarkOnly(
dark =
PaletteColors(
primary = AmoledTextBright,
onPrimary = AmoledFloor,
primaryContainer = AmoledContainerHighest,
onPrimaryContainer = AmoledTextBright,
secondary = AmoledSecondary,
onSecondary = AmoledFloor,
secondaryContainer = AmoledContainerHigh,
onSecondaryContainer = AmoledSecondary,
tertiary = AmoledTertiary,
onTertiary = AmoledFloor,
tertiaryContainer = AmoledContainerHigh,
onTertiaryContainer = AmoledTertiary,
background = AmoledFloor,
onBackground = AmoledTextBright,
surface = AmoledFloor,
onSurface = AmoledTextBright,
surfaceVariant = AmoledContainerHigh,
onSurfaceVariant = AmoledTextDim,
surfaceContainerLowest = AmoledFloor,
surfaceContainerLow = AmoledFloor,
surfaceContainer = AmoledContainer,
surfaceContainerHigh = AmoledContainerHigh,
surfaceContainerHighest = AmoledContainerHighest,
inverseSurface = AmoledTextBright,
inverseOnSurface = AmoledFloor,
inversePrimary = AmoledInversePrimary,
outline = AmoledOutline,
outlineVariant = AmoledOutlineVariant,
scrim = Color.Black,
status =
HermesStatusColors(
success = AmoledTertiary,
successContainer = AmoledContainerHigh,
onSuccess = AmoledFloor,
warning = AmoledSecondary,
warningContainer = AmoledContainerHigh,
onWarning = AmoledFloor,
error = AmoledTextBright,
errorContainer = AmoledContainerHigh,
onError = AmoledFloor,
onErrorContainer = AmoledTextBright,
info = AmoledInfo,
infoContainer = AmoledContainerHigh,
onInfo = AmoledFloor,
),
),
)
/**
* AMOLED has no bespoke light scheme — reuse the brand default light scheme.
*/
val AmoledLightColorScheme = DefaultLightColorScheme
/** AMOLED status colors (dark) — reuses the brand default set. */
val AmoledDarkStatusColors = DefaultDarkStatusColors
/** AMOLED status colors (light) — reuses the brand default set. */
val AmoledLightStatusColors = DefaultLightStatusColors
@@ -1,118 +1,152 @@
package com.m57.hermescontrol.theme.presets
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.ui.graphics.Color
import com.m57.hermescontrol.theme.HermesStatusColors
import com.m57.hermescontrol.theme.PaletteColors
import com.m57.hermescontrol.theme.buildTheme
/** Catppuccin color scheme (dark). */
val CatppuccinDarkColorScheme =
darkColorScheme(
primary = Color(0xFFCBA6F7), // Mauve
onPrimary = Color(0xFF11111B), // Crust
primaryContainer = Color(0xFF45475A), // Surface 1
onPrimaryContainer = Color(0xFFCDD6F4), // Text
inversePrimary = Color(0xFF8839EF), // Latte Mauve
secondary = Color(0xFF89B4FA), // Blue
onSecondary = Color(0xFF11111B), // Crust
secondaryContainer = Color(0xFF313244), // Surface 0
onSecondaryContainer = Color(0xFFBAC2DE), // Subtext 1
tertiary = Color(0xFFF5C2E7), // Pink
onTertiary = Color(0xFF11111B), // Crust
tertiaryContainer = Color(0xFF45475A), // Surface 1
onTertiaryContainer = Color(0xFFCDD6F4), // Text
background = Color(0xFF1E1E2E), // Base
onBackground = Color(0xFFCDD6F4), // Text
surface = Color(0xFF1E1E2E), // Base
onSurface = Color(0xFFCDD6F4), // Text
surfaceVariant = Color(0xFF313244), // Surface 0
onSurfaceVariant = Color(0xFFA6ADC8), // Subtext 0
surfaceTint = Color(0xFFCBA6F7), // Mauve
surfaceContainerLowest = Color(0xFF11111B), // Crust
surfaceContainerLow = Color(0xFF181825), // Mantle
surfaceContainer = Color(0xFF1E1E2E), // Base
surfaceContainerHigh = Color(0xFF313244), // Surface 0
surfaceContainerHighest = Color(0xFF45475A), // Surface 1
inverseSurface = Color(0xFFCDD6F4), // Text
inverseOnSurface = Color(0xFF1E1E2E), // Base
error = Color(0xFFF38BA8), // Red
onError = Color(0xFF11111B), // Crust
errorContainer = Color(0xFF313244), // Surface 0
onErrorContainer = Color(0xFFF38BA8), // Red
outline = Color(0xFF6C7086), // Overlay 0
outlineVariant = Color(0xFF45475A), // Surface 1
scrim = Color(0xFF11111B), // Crust
)
// ---------------------------------------------------------------------
// Mocha (dark) — named swatches, so repeated hexes (Crust, Surface0/1,
// Base, Text, Blue...) live in exactly one place.
// ---------------------------------------------------------------------
/** Catppuccin color scheme (light). */
val CatppuccinLightColorScheme =
lightColorScheme(
primary = Color(0xFF8839EF), // Mauve
onPrimary = Color(0xFFEFF1F5), // Base
primaryContainer = Color(0xFFE6CAFA), // Light Mauve Container
onPrimaryContainer = Color(0xFF380075),
inversePrimary = Color(0xFFCBA6F7), // Mocha Mauve
secondary = Color(0xFF1E66F5), // Blue
onSecondary = Color(0xFFEFF1F5), // Base
secondaryContainer = Color(0xFFE6E9EF), // Mantle
onSecondaryContainer = Color(0xFF4C4F69), // Text
tertiary = Color(0xFFEA76CB), // Pink
onTertiary = Color(0xFFEFF1F5), // Base
tertiaryContainer = Color(0xFFBCC0CC), // Surface 1
onTertiaryContainer = Color(0xFF4C4F69), // Text
background = Color(0xFFEFF1F5), // Base
onBackground = Color(0xFF4C4F69), // Text
surface = Color(0xFFEFF1F5), // Base
onSurface = Color(0xFF4C4F69), // Text
surfaceVariant = Color(0xFFCCD0DA), // Surface 0
onSurfaceVariant = Color(0xFF6C6F85), // Subtext 0
surfaceTint = Color(0xFF8839EF), // Mauve
surfaceContainerLowest = Color(0xFFDCE0E8), // Crust
surfaceContainerLow = Color(0xFFE6E9EF), // Mantle
surfaceContainer = Color(0xFFEFF1F5), // Base
surfaceContainerHigh = Color(0xFFCCD0DA), // Surface 0
surfaceContainerHighest = Color(0xFFBCC0CC), // Surface 1
inverseSurface = Color(0xFF4C4F69), // Text
inverseOnSurface = Color(0xFFEFF1F5), // Base
error = Color(0xFFD20F39), // Red
onError = Color(0xFFEFF1F5), // Base
errorContainer = Color(0xFFCCD0DA), // Surface 0
onErrorContainer = Color(0xFFD20F39), // Red
outline = Color(0xFF9CA0B0), // Overlay 0
outlineVariant = Color(0xFFBCC0CC), // Surface 1
scrim = Color(0xFFDCE0E8), // Crust
)
private val MochaMauve = Color(0xFFCBA6F7)
private val MochaBlue = Color(0xFF89B4FA)
private val MochaPink = Color(0xFFF5C2E7)
private val MochaGreen = Color(0xFFA6E3A1)
private val MochaYellow = Color(0xFFF9E2AF)
private val MochaRed = Color(0xFFF38BA8)
private val MochaText = Color(0xFFCDD6F4)
private val MochaSubtext1 = Color(0xFFBAC2DE)
private val MochaSubtext0 = Color(0xFFA6ADC8)
private val MochaOverlay0 = Color(0xFF6C7086)
private val MochaSurface1 = Color(0xFF45475A)
private val MochaSurface0 = Color(0xFF313244)
private val MochaBase = Color(0xFF1E1E2E)
private val MochaMantle = Color(0xFF181825)
private val MochaCrust = Color(0xFF11111B)
/** Catppuccin status colors (dark). */
val CatppuccinDarkStatusColors =
HermesStatusColors(
success = Color(0xFFA6E3A1), // Green
successContainer = Color(0xFF313244), // Surface 0
onSuccess = Color(0xFF11111B), // Crust
warning = Color(0xFFF9E2AF), // Yellow
warningContainer = Color(0xFF313244), // Surface 0
onWarning = Color(0xFF11111B), // Crust
error = Color(0xFFF38BA8), // Red
errorContainer = Color(0xFF313244), // Surface 0
onError = Color(0xFF11111B), // Crust
info = Color(0xFF89B4FA), // Blue
infoContainer = Color(0xFF313244), // Surface 0
onInfo = Color(0xFF11111B), // Crust
)
// ---------------------------------------------------------------------
// Latte (light) — named swatches
// ---------------------------------------------------------------------
/** Catppuccin status colors (light). */
val CatppuccinLightStatusColors =
HermesStatusColors(
success = Color(0xFF40A02B), // Green
successContainer = Color(0xFFCCD0DA), // Surface 0
onSuccess = Color(0xFFEFF1F5), // Base
warning = Color(0xFFDF8E1D), // Yellow
warningContainer = Color(0xFFCCD0DA), // Surface 0
onWarning = Color(0xFFEFF1F5), // Base
error = Color(0xFFD20F39), // Red
errorContainer = Color(0xFFCCD0DA), // Surface 0
onError = Color(0xFFEFF1F5), // Base
info = Color(0xFF1E66F5), // Blue
infoContainer = Color(0xFFCCD0DA), // Surface 0
onInfo = Color(0xFFEFF1F5), // Base
private val LatteMauve = Color(0xFF8839EF)
private val LatteBlue = Color(0xFF1E66F5)
private val LattePink = Color(0xFFEA76CB)
private val LatteGreen = Color(0xFF40A02B)
private val LatteYellow = Color(0xFFDF8E1D)
private val LatteRed = Color(0xFFD20F39)
private val LatteText = Color(0xFF4C4F69)
private val LatteSubtext0 = Color(0xFF6C6F85)
private val LatteOverlay0 = Color(0xFF9CA0B0)
private val LatteSurface1 = Color(0xFFBCC0CC)
private val LatteSurface0 = Color(0xFFCCD0DA)
private val LatteBase = Color(0xFFEFF1F5)
private val LatteMantle = Color(0xFFE6E9EF)
private val LatteCrust = Color(0xFFDCE0E8)
// Bespoke tint used only for the light primary container — not part of
// the official Catppuccin swatch set, kept local to this file.
private val LattePrimaryContainer = Color(0xFFE6CAFA)
private val LatteOnPrimaryContainer = Color(0xFF380075)
/** Catppuccin theme — Mocha (dark) / Latte (light). */
val CatppuccinTheme =
buildTheme(
dark =
PaletteColors(
primary = MochaMauve,
onPrimary = MochaCrust,
primaryContainer = MochaSurface1,
onPrimaryContainer = MochaText,
secondary = MochaBlue,
onSecondary = MochaCrust,
secondaryContainer = MochaSurface0,
onSecondaryContainer = MochaSubtext1,
tertiary = MochaPink,
onTertiary = MochaCrust,
tertiaryContainer = MochaSurface1,
onTertiaryContainer = MochaText,
background = MochaBase,
onBackground = MochaText,
surface = MochaBase,
onSurface = MochaText,
surfaceVariant = MochaSurface0,
onSurfaceVariant = MochaSubtext0,
surfaceContainerLowest = MochaCrust,
surfaceContainerLow = MochaMantle,
surfaceContainer = MochaBase,
surfaceContainerHigh = MochaSurface0,
surfaceContainerHighest = MochaSurface1,
inverseSurface = MochaText,
inverseOnSurface = MochaBase,
inversePrimary = LatteMauve,
outline = MochaOverlay0,
outlineVariant = MochaSurface1,
scrim = MochaCrust,
status =
HermesStatusColors(
success = MochaGreen,
successContainer = MochaSurface0,
onSuccess = MochaCrust,
warning = MochaYellow,
warningContainer = MochaSurface0,
onWarning = MochaCrust,
error = MochaRed,
errorContainer = MochaSurface0,
onError = MochaCrust,
onErrorContainer = MochaRed,
info = MochaBlue,
infoContainer = MochaSurface0,
onInfo = MochaCrust,
),
),
light =
PaletteColors(
primary = LatteMauve,
onPrimary = LatteBase,
primaryContainer = LattePrimaryContainer,
onPrimaryContainer = LatteOnPrimaryContainer,
secondary = LatteBlue,
onSecondary = LatteBase,
secondaryContainer = LatteMantle,
onSecondaryContainer = LatteText,
tertiary = LattePink,
onTertiary = LatteBase,
tertiaryContainer = LatteSurface1,
onTertiaryContainer = LatteText,
background = LatteBase,
onBackground = LatteText,
surface = LatteBase,
onSurface = LatteText,
surfaceVariant = LatteSurface0,
onSurfaceVariant = LatteSubtext0,
surfaceContainerLowest = LatteCrust,
surfaceContainerLow = LatteMantle,
surfaceContainer = LatteBase,
surfaceContainerHigh = LatteSurface0,
surfaceContainerHighest = LatteSurface1,
inverseSurface = LatteText,
inverseOnSurface = LatteBase,
inversePrimary = MochaMauve,
outline = LatteOverlay0,
outlineVariant = LatteSurface1,
scrim = LatteCrust,
status =
HermesStatusColors(
success = LatteGreen,
successContainer = LatteSurface0,
onSuccess = LatteBase,
warning = LatteYellow,
warningContainer = LatteSurface0,
onWarning = LatteBase,
error = LatteRed,
errorContainer = LatteSurface0,
onError = LatteBase,
onErrorContainer = LatteRed,
info = LatteBlue,
infoContainer = LatteSurface0,
onInfo = LatteBase,
),
),
)
@@ -1,165 +1,152 @@
package com.m57.hermescontrol.theme.presets
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.ui.graphics.Color
import com.m57.hermescontrol.theme.DarkBackground
import com.m57.hermescontrol.theme.DarkInverseOnSurface
import com.m57.hermescontrol.theme.DarkInverseSurface
import com.m57.hermescontrol.theme.DarkOnSurface
import com.m57.hermescontrol.theme.DarkOnSurfaceVariant
import com.m57.hermescontrol.theme.DarkOutline
import com.m57.hermescontrol.theme.DarkOutlineVariant
import com.m57.hermescontrol.theme.DarkScrim
import com.m57.hermescontrol.theme.DarkSurface
import com.m57.hermescontrol.theme.DarkSurfaceContainer
import com.m57.hermescontrol.theme.DarkSurfaceContainerHigh
import com.m57.hermescontrol.theme.DarkSurfaceContainerHighest
import com.m57.hermescontrol.theme.DarkSurfaceContainerLow
import com.m57.hermescontrol.theme.DarkSurfaceContainerLowest
import com.m57.hermescontrol.theme.DarkSurfaceVariant
import com.m57.hermescontrol.theme.HermesAmber
import com.m57.hermescontrol.theme.HermesAmberDark
import com.m57.hermescontrol.theme.HermesAmberLight
import com.m57.hermescontrol.theme.HermesPurple
import com.m57.hermescontrol.theme.HermesPurpleContainer
import com.m57.hermescontrol.theme.HermesPurpleDark
import com.m57.hermescontrol.theme.HermesPurpleLight
import com.m57.hermescontrol.theme.HermesPurpleOnContainer
import com.m57.hermescontrol.theme.HermesStatusColors
import com.m57.hermescontrol.theme.LightBackground
import com.m57.hermescontrol.theme.LightInverseOnSurface
import com.m57.hermescontrol.theme.LightInverseSurface
import com.m57.hermescontrol.theme.LightOnSurface
import com.m57.hermescontrol.theme.LightOnSurfaceVariant
import com.m57.hermescontrol.theme.LightOutline
import com.m57.hermescontrol.theme.LightOutlineVariant
import com.m57.hermescontrol.theme.LightScrim
import com.m57.hermescontrol.theme.LightSurface
import com.m57.hermescontrol.theme.LightSurfaceContainer
import com.m57.hermescontrol.theme.LightSurfaceContainerHigh
import com.m57.hermescontrol.theme.LightSurfaceContainerHighest
import com.m57.hermescontrol.theme.LightSurfaceContainerLow
import com.m57.hermescontrol.theme.LightSurfaceContainerLowest
import com.m57.hermescontrol.theme.LightSurfaceVariant
import com.m57.hermescontrol.theme.StatusBlue
import com.m57.hermescontrol.theme.StatusBlueContainer
import com.m57.hermescontrol.theme.StatusGreen
import com.m57.hermescontrol.theme.StatusGreenContainer
import com.m57.hermescontrol.theme.StatusGreyLight
import com.m57.hermescontrol.theme.StatusRed
import com.m57.hermescontrol.theme.StatusRedContainer
import com.m57.hermescontrol.theme.StatusYellow
import com.m57.hermescontrol.theme.StatusYellowContainer
import com.m57.hermescontrol.theme.PaletteColors
import com.m57.hermescontrol.theme.buildTheme
/** Brand default color scheme (dark) — Voltage Purple primary. */
val DefaultDarkColorScheme =
darkColorScheme(
primary = HermesPurple,
onPrimary = Color.White,
primaryContainer = HermesPurpleContainer,
onPrimaryContainer = HermesPurpleOnContainer,
inversePrimary = HermesPurpleDark,
secondary = HermesAmber,
onSecondary = Color.Black,
secondaryContainer = Color(0xFF3D2F0F),
onSecondaryContainer = HermesAmberLight,
tertiary = HermesAmberLight,
onTertiary = Color.Black,
tertiaryContainer = Color(0xFF3D2F0F),
onTertiaryContainer = HermesAmberLight,
background = DarkBackground,
onBackground = DarkOnSurface,
surface = DarkSurface,
onSurface = DarkOnSurface,
surfaceVariant = DarkSurfaceVariant,
onSurfaceVariant = DarkOnSurfaceVariant,
surfaceTint = HermesPurple,
surfaceContainerLowest = DarkSurfaceContainerLowest,
surfaceContainerLow = DarkSurfaceContainerLow,
surfaceContainer = DarkSurfaceContainer,
surfaceContainerHigh = DarkSurfaceContainerHigh,
surfaceContainerHighest = DarkSurfaceContainerHighest,
inverseSurface = DarkInverseSurface,
inverseOnSurface = DarkInverseOnSurface,
error = StatusRed,
onError = Color.White,
errorContainer = StatusRedContainer,
onErrorContainer = Color(0xFFFFB4B4),
outline = DarkOutline,
outlineVariant = DarkOutlineVariant,
scrim = DarkScrim,
)
// ---------------------------------------------------------------------
// Slate Dark — named swatches
// ---------------------------------------------------------------------
/** Brand default color scheme (light) — Voltage Purple primary. */
val DefaultLightColorScheme =
lightColorScheme(
primary = HermesPurpleDark,
onPrimary = Color.White,
primaryContainer = HermesPurpleLight,
onPrimaryContainer = Color(0xFF1E0F66),
inversePrimary = HermesPurpleLight,
secondary = HermesAmberDark,
onSecondary = Color.White,
secondaryContainer = HermesAmberLight,
onSecondaryContainer = Color(0xFF3D2F0F),
tertiary = HermesAmberDark,
onTertiary = Color.White,
tertiaryContainer = HermesAmberLight,
onTertiaryContainer = Color(0xFF3D2F0F),
background = LightBackground,
onBackground = LightOnSurface,
surface = LightSurface,
onSurface = LightOnSurface,
surfaceVariant = LightSurfaceVariant,
onSurfaceVariant = LightOnSurfaceVariant,
surfaceTint = HermesPurple,
surfaceContainerLowest = LightSurfaceContainerLowest,
surfaceContainerLow = LightSurfaceContainerLow,
surfaceContainer = LightSurfaceContainer,
surfaceContainerHigh = LightSurfaceContainerHigh,
surfaceContainerHighest = LightSurfaceContainerHighest,
inverseSurface = LightInverseSurface,
inverseOnSurface = LightInverseOnSurface,
error = Color(0xFFB3261E),
onError = Color.White,
errorContainer = Color(0xFFF9DEDC),
onErrorContainer = Color(0xFF410E0B),
outline = LightOutline,
outlineVariant = LightOutlineVariant,
scrim = LightScrim,
)
private val SlateDarkBg0 = Color(0xFF0F1416)
private val SlateDarkBg1 = Color(0xFF171C1E)
private val SlateDarkBg2 = Color(0xFF1B2022)
private val SlateDarkBg3 = Color(0xFF252B2D)
private val SlateDarkBg4 = Color(0xFF303638)
private val SlateDarkFg1 = Color(0xFFDEE3E5)
private val SlateDarkFg2 = Color(0xFFCEE7EC)
private val SlateDarkFg3 = Color(0xFFBEC8CA)
private val SlateGray = Color(0xFF889294) // same neutral gray in both modes
/** Default status colors (dark). */
val DefaultDarkStatusColors =
HermesStatusColors(
success = StatusGreen,
successContainer = StatusGreenContainer,
onSuccess = StatusGreyLight,
warning = StatusYellow,
warningContainer = StatusYellowContainer,
onWarning = StatusGreyLight,
error = StatusRed,
errorContainer = StatusRedContainer,
onError = StatusGreyLight,
info = StatusBlue,
infoContainer = StatusBlueContainer,
onInfo = StatusGreyLight,
)
// "Bright" accents — high-visibility variants against dark surfaces.
private val SlateBrightTeal = Color(0xFF80D5D2)
private val SlateBrightBlue = Color(0xFFA3C9EC)
private val SlateBrightCyan = Color(0xFFB2CBD0)
private val SlateBrightGreen = Color(0xFF81D692)
private val SlateBrightYellow = Color(0xFFECC248)
private val SlateBrightRed = Color(0xFFFFB4AB)
private val SlateBrightInfo = Color(0xFF8ECEFF)
/** Default status colors (light). */
val DefaultLightStatusColors =
HermesStatusColors(
success = Color(0xFF1B873A),
successContainer = Color(0xFFD7F5E0),
onSuccess = Color(0xFFFDF8FF),
warning = HermesAmberDark,
warningContainer = Color(0xFFFFEAB3),
onWarning = Color(0xFFFDF8FF),
error = Color(0xFFB3261E),
errorContainer = Color(0xFFF9DEDC),
onError = Color(0xFFFDF8FF),
info = Color(0xFF2E6FBD),
infoContainer = Color(0xFFD4E7FF),
onInfo = Color(0xFFFDF8FF),
// ---------------------------------------------------------------------
// Slate Light — named swatches
// ---------------------------------------------------------------------
private val SlateLightBg0 = Color(0xFFF5FAFC)
private val SlateLightBg1 = Color(0xFFEFF4F6)
private val SlateLightBg2 = Color(0xFFE9EEF0)
private val SlateLightBg3 = Color(0xFFE3E8EA)
private val SlateLightBg4 = Color(0xFFDEE3E5)
private val SlateLightFg1 = Color(0xFF171C1E)
private val SlateLightFg2 = Color(0xFF051F23)
private val SlateLightFg3 = Color(0xFF3F484A)
// "Faded" accents — muted variants for light background contrast.
private val SlateFadedTeal = Color(0xFF006A68)
private val SlateFadedBlue = Color(0xFF3B6184)
private val SlateFadedCyan = Color(0xFF4A6367)
private val SlateFadedGreen = Color(0xFF126D2B)
private val SlateFadedYellow = Color(0xFF725C00)
private val SlateFadedRed = Color(0xFFBA1A1A)
private val SlateFadedInfo = Color(0xFF006393)
/** Default theme (Slate) — modern slate blues with teal accent notes. */
val DefaultTheme =
buildTheme(
dark =
PaletteColors(
primary = SlateBrightTeal,
onPrimary = SlateDarkBg0,
primaryContainer = SlateDarkBg4,
onPrimaryContainer = SlateDarkFg1,
secondary = SlateBrightCyan,
onSecondary = SlateDarkBg0,
secondaryContainer = SlateDarkBg3,
onSecondaryContainer = SlateDarkFg2,
tertiary = SlateBrightBlue,
onTertiary = SlateDarkBg0,
tertiaryContainer = SlateDarkBg4,
onTertiaryContainer = SlateDarkFg1,
background = SlateDarkBg0,
onBackground = SlateDarkFg1,
surface = SlateDarkBg0,
onSurface = SlateDarkFg1,
surfaceVariant = SlateDarkBg1,
onSurfaceVariant = SlateDarkFg3,
surfaceContainerLowest = SlateDarkBg0,
surfaceContainerLow = SlateDarkBg1,
surfaceContainer = SlateDarkBg2,
surfaceContainerHigh = SlateDarkBg3,
surfaceContainerHighest = SlateDarkBg4,
inverseSurface = SlateDarkFg1,
inverseOnSurface = SlateDarkBg0,
inversePrimary = SlateFadedTeal,
outline = SlateGray,
outlineVariant = SlateDarkBg3,
scrim = SlateDarkBg0,
status =
HermesStatusColors(
success = SlateBrightGreen,
successContainer = SlateDarkBg3,
onSuccess = SlateDarkBg0,
warning = SlateBrightYellow,
warningContainer = SlateDarkBg3,
onWarning = SlateDarkBg0,
error = SlateBrightRed,
errorContainer = SlateDarkBg3,
onError = SlateDarkBg0,
onErrorContainer = SlateBrightRed,
info = SlateBrightInfo,
infoContainer = SlateDarkBg3,
onInfo = SlateDarkBg0,
),
),
light =
PaletteColors(
primary = SlateFadedTeal,
onPrimary = SlateLightBg0,
primaryContainer = SlateLightBg4,
onPrimaryContainer = SlateLightFg1,
secondary = SlateFadedCyan,
onSecondary = SlateLightBg0,
secondaryContainer = SlateLightBg3,
onSecondaryContainer = SlateLightFg2,
tertiary = SlateFadedBlue,
onTertiary = SlateLightBg0,
tertiaryContainer = SlateLightBg4,
onTertiaryContainer = SlateLightFg1,
background = SlateLightBg0,
onBackground = SlateLightFg1,
surface = SlateLightBg0,
onSurface = SlateLightFg1,
surfaceVariant = SlateLightBg1,
onSurfaceVariant = SlateLightFg3,
surfaceContainerLowest = SlateLightBg0,
surfaceContainerLow = SlateLightBg1,
surfaceContainer = SlateLightBg2,
surfaceContainerHigh = SlateLightBg3,
surfaceContainerHighest = SlateLightBg4,
inverseSurface = SlateLightFg1,
inverseOnSurface = SlateLightBg0,
inversePrimary = SlateBrightTeal,
outline = SlateGray,
outlineVariant = SlateLightBg3,
scrim = SlateLightBg0,
status =
HermesStatusColors(
success = SlateFadedGreen,
successContainer = SlateLightBg3,
onSuccess = SlateLightBg0,
warning = SlateFadedYellow,
warningContainer = SlateLightBg3,
onWarning = SlateLightBg0,
error = SlateFadedRed,
errorContainer = SlateLightBg3,
onError = SlateLightBg0,
onErrorContainer = SlateFadedRed,
info = SlateFadedInfo,
infoContainer = SlateLightBg3,
onInfo = SlateLightBg0,
),
),
)
@@ -1,118 +1,151 @@
package com.m57.hermescontrol.theme.presets
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.ui.graphics.Color
import com.m57.hermescontrol.theme.HermesStatusColors
import com.m57.hermescontrol.theme.PaletteColors
import com.m57.hermescontrol.theme.buildTheme
/** Gruvbox color scheme (dark). */
val GruvboxDarkColorScheme =
darkColorScheme(
primary = Color(0xFFFE8019),
onPrimary = Color(0xFF282828),
primaryContainer = Color(0xFFD65D0E),
onPrimaryContainer = Color(0xFFFBF1C7),
inversePrimary = Color(0xFFD65D0E),
secondary = Color(0xFFFABD2F),
onSecondary = Color(0xFF282828),
secondaryContainer = Color(0xFF3C3836),
onSecondaryContainer = Color(0xFFBDAE93),
tertiary = Color(0xFF8EC07C),
onTertiary = Color(0xFF282828),
tertiaryContainer = Color(0xFF458588),
onTertiaryContainer = Color(0xFFFBF1C7),
background = Color(0xFF282828),
onBackground = Color(0xFFFBF1C7),
surface = Color(0xFF32302F),
onSurface = Color(0xFFFBF1C7),
surfaceVariant = Color(0xFF3C3836),
onSurfaceVariant = Color(0xFFBDAE93),
surfaceTint = Color(0xFFFE8019),
surfaceContainerLowest = Color(0xFF1D2021),
surfaceContainerLow = Color(0xFF282828),
surfaceContainer = Color(0xFF32302F),
surfaceContainerHigh = Color(0xFF3C3836),
surfaceContainerHighest = Color(0xFF504945),
inverseSurface = Color(0xFFFBF1C7),
inverseOnSurface = Color(0xFF282828),
error = Color(0xFFFB4934),
onError = Color(0xFF282828),
errorContainer = Color(0xFFCC241D),
onErrorContainer = Color(0xFFFBF1C7),
outline = Color(0xFF665C54),
outlineVariant = Color(0xFF504945),
scrim = Color(0xFF1D2021),
)
// ---------------------------------------------------------------------
// Gruvbox Dark (medium contrast) — named swatches
// ---------------------------------------------------------------------
/** Gruvbox color scheme (light). */
val GruvboxLightColorScheme =
lightColorScheme(
primary = Color(0xFFD65D0E),
onPrimary = Color(0xFFFBF1C7),
primaryContainer = Color(0xFFFE8019),
onPrimaryContainer = Color(0xFF282828),
inversePrimary = Color(0xFFFE8019),
secondary = Color(0xFF458588),
onSecondary = Color(0xFFFBF1C7),
secondaryContainer = Color(0xFFEBDBB2),
onSecondaryContainer = Color(0xFF3C3836),
tertiary = Color(0xFF689D6A),
onTertiary = Color(0xFFFBF1C7),
tertiaryContainer = Color(0xFFD5C4A1),
onTertiaryContainer = Color(0xFF282828),
background = Color(0xFFFBF1C7),
onBackground = Color(0xFF282828),
surface = Color(0xFFF2E5BC),
onSurface = Color(0xFF282828),
surfaceVariant = Color(0xFFEBDBB2),
onSurfaceVariant = Color(0xFF7C6F64),
surfaceTint = Color(0xFFD65D0E),
surfaceContainerLowest = Color(0xFFFFFFFF),
surfaceContainerLow = Color(0xFFF9F5D7),
surfaceContainer = Color(0xFFFBF1C7),
surfaceContainerHigh = Color(0xFFF2E5BC),
surfaceContainerHighest = Color(0xFFEBDBB2),
inverseSurface = Color(0xFF282828),
inverseOnSurface = Color(0xFFFBF1C7),
error = Color(0xFFCC241D),
onError = Color(0xFFFBF1C7),
errorContainer = Color(0xFFF9F5D7),
onErrorContainer = Color(0xFF9D0006),
outline = Color(0xFFA89984),
outlineVariant = Color(0xFFD5C4A1),
scrim = Color(0xFF3C3836),
)
private val GruvboxDarkBg0 = Color(0xFF282828)
private val GruvboxDarkBg1 = Color(0xFF3C3836)
private val GruvboxDarkBg2 = Color(0xFF504945)
private val GruvboxDarkBg3 = Color(0xFF665C54)
private val GruvboxDarkBg4 = Color(0xFF7C6F64)
private val GruvboxDarkFg1 = Color(0xFFEBDBB2)
private val GruvboxDarkFg2 = Color(0xFFD5C4A1)
private val GruvboxDarkFg3 = Color(0xFFBDAE93)
private val GruvboxGray = Color(0xFF928374) // same neutral gray in both modes
/** Gruvbox status colors (dark). */
val GruvboxDarkStatusColors =
HermesStatusColors(
success = Color(0xFFB8BB26),
successContainer = Color(0xFF32302F),
onSuccess = Color(0xFF282828),
warning = Color(0xFFFABD2F),
warningContainer = Color(0xFF3C3836),
onWarning = Color(0xFF282828),
error = Color(0xFFFB4934),
errorContainer = Color(0xFF282828),
onError = Color(0xFF282828),
info = Color(0xFF83A598),
infoContainer = Color(0xFF3C3836),
onInfo = Color(0xFF282828),
)
// "Bright" accents — the variants gruvbox uses against a dark background.
private val GruvboxBrightRed = Color(0xFFFB4934)
private val GruvboxBrightGreen = Color(0xFFB8BB26)
private val GruvboxBrightYellow = Color(0xFFFABD2F)
private val GruvboxBrightBlue = Color(0xFF83A598)
private val GruvboxBrightPurple = Color(0xFFD3869B)
private val GruvboxBrightOrange = Color(0xFFFE8019)
/** Gruvbox status colors (light). */
val GruvboxLightStatusColors =
HermesStatusColors(
success = Color(0xFF98971A),
successContainer = Color(0xFFEBDBB2),
onSuccess = Color(0xFFEBDBB2),
warning = Color(0xFFD79921),
warningContainer = Color(0xFFEBDBB2),
onWarning = Color(0xFFEBDBB2),
error = Color(0xFFCC241D),
errorContainer = Color(0xFFEBDBB2),
onError = Color(0xFFEBDBB2),
info = Color(0xFF458588),
infoContainer = Color(0xFFEBDBB2),
onInfo = Color(0xFFEBDBB2),
// ---------------------------------------------------------------------
// Gruvbox Light (medium contrast) — named swatches
// ---------------------------------------------------------------------
private val GruvboxLightBg0 = Color(0xFFFBF1C7)
private val GruvboxLightBg1 = Color(0xFFEBDBB2)
private val GruvboxLightBg2 = Color(0xFFD5C4A1)
private val GruvboxLightBg3 = Color(0xFFBDAE93)
private val GruvboxLightBg4 = Color(0xFFA89984)
private val GruvboxLightFg1 = Color(0xFF3C3836)
private val GruvboxLightFg2 = Color(0xFF504945)
private val GruvboxLightFg3 = Color(0xFF665C54)
// "Faded" accents — the muted variants gruvbox uses against a light
// background, so contrast holds up without the neon dark-mode saturation.
private val GruvboxFadedRed = Color(0xFF9D0006)
private val GruvboxFadedGreen = Color(0xFF79740E)
private val GruvboxFadedYellow = Color(0xFFB57614)
private val GruvboxFadedBlue = Color(0xFF076678)
private val GruvboxFadedPurple = Color(0xFF8F3F71)
private val GruvboxFadedOrange = Color(0xFFAF3A03)
/** Gruvbox theme — bright accents on dark bg0–bg4, faded accents on light bg0–bg4. */
val GruvboxTheme =
buildTheme(
dark =
PaletteColors(
primary = GruvboxBrightOrange,
onPrimary = GruvboxDarkBg0,
primaryContainer = GruvboxDarkBg4,
onPrimaryContainer = GruvboxDarkFg1,
secondary = GruvboxBrightBlue,
onSecondary = GruvboxDarkBg0,
secondaryContainer = GruvboxDarkBg3,
onSecondaryContainer = GruvboxDarkFg2,
tertiary = GruvboxBrightPurple,
onTertiary = GruvboxDarkBg0,
tertiaryContainer = GruvboxDarkBg4,
onTertiaryContainer = GruvboxDarkFg1,
background = GruvboxDarkBg0,
onBackground = GruvboxDarkFg1,
surface = GruvboxDarkBg0,
onSurface = GruvboxDarkFg1,
surfaceVariant = GruvboxDarkBg1,
onSurfaceVariant = GruvboxDarkFg3,
surfaceContainerLowest = GruvboxDarkBg0,
surfaceContainerLow = GruvboxDarkBg1,
surfaceContainer = GruvboxDarkBg2,
surfaceContainerHigh = GruvboxDarkBg3,
surfaceContainerHighest = GruvboxDarkBg4,
inverseSurface = GruvboxDarkFg1,
inverseOnSurface = GruvboxDarkBg0,
inversePrimary = GruvboxFadedOrange,
outline = GruvboxGray,
outlineVariant = GruvboxDarkBg3,
scrim = GruvboxDarkBg0,
status =
HermesStatusColors(
success = GruvboxBrightGreen,
successContainer = GruvboxDarkBg3,
onSuccess = GruvboxDarkBg0,
warning = GruvboxBrightYellow,
warningContainer = GruvboxDarkBg3,
onWarning = GruvboxDarkBg0,
error = GruvboxBrightRed,
errorContainer = GruvboxDarkBg3,
onError = GruvboxDarkBg0,
onErrorContainer = GruvboxDarkFg1,
info = GruvboxBrightBlue,
infoContainer = GruvboxDarkBg3,
onInfo = GruvboxDarkBg0,
),
),
light =
PaletteColors(
primary = GruvboxFadedOrange,
onPrimary = GruvboxLightBg0,
primaryContainer = GruvboxLightBg4,
onPrimaryContainer = GruvboxLightFg1,
secondary = GruvboxFadedBlue,
onSecondary = GruvboxLightBg0,
secondaryContainer = GruvboxLightBg3,
onSecondaryContainer = GruvboxLightFg2,
tertiary = GruvboxFadedPurple,
onTertiary = GruvboxLightBg0,
tertiaryContainer = GruvboxLightBg4,
onTertiaryContainer = GruvboxLightFg1,
background = GruvboxLightBg0,
onBackground = GruvboxLightFg1,
surface = GruvboxLightBg0,
onSurface = GruvboxLightFg1,
surfaceVariant = GruvboxLightBg1,
onSurfaceVariant = GruvboxLightFg3,
surfaceContainerLowest = GruvboxLightBg0,
surfaceContainerLow = GruvboxLightBg1,
surfaceContainer = GruvboxLightBg2,
surfaceContainerHigh = GruvboxLightBg3,
surfaceContainerHighest = GruvboxLightBg4,
inverseSurface = GruvboxLightFg1,
inverseOnSurface = GruvboxLightBg0,
inversePrimary = GruvboxBrightOrange,
outline = GruvboxGray,
outlineVariant = GruvboxLightBg3,
scrim = GruvboxLightBg0,
status =
HermesStatusColors(
success = GruvboxFadedGreen,
successContainer = GruvboxLightBg3,
onSuccess = GruvboxLightBg0,
warning = GruvboxFadedYellow,
warningContainer = GruvboxLightBg3,
onWarning = GruvboxLightBg0,
error = GruvboxFadedRed,
errorContainer = GruvboxLightBg3,
onError = GruvboxLightBg0,
onErrorContainer = GruvboxFadedRed,
info = GruvboxFadedBlue,
infoContainer = GruvboxLightBg3,
onInfo = GruvboxLightBg0,
),
),
)
@@ -1,118 +1,150 @@
package com.m57.hermescontrol.theme.presets
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.ui.graphics.Color
import com.m57.hermescontrol.theme.HermesStatusColors
import com.m57.hermescontrol.theme.PaletteColors
import com.m57.hermescontrol.theme.buildTheme
/** Monochrome color scheme (dark). */
val MonochromeDarkColorScheme =
darkColorScheme(
primary = Color.White,
onPrimary = Color.Black,
primaryContainer = Color(0xFF404040),
onPrimaryContainer = Color.White,
inversePrimary = Color(0xFF121212),
secondary = Color(0xFFB0B0B0),
onSecondary = Color.Black,
secondaryContainer = Color(0xFF222222),
onSecondaryContainer = Color(0xFFE0E0E0),
tertiary = Color(0xFFD0D0D0),
onTertiary = Color.Black,
tertiaryContainer = Color(0xFF333333),
onTertiaryContainer = Color(0xFFE0E0E0),
background = Color(0xFF121212),
onBackground = Color(0xFFE0E0E0),
surface = Color(0xFF1E1E1E),
onSurface = Color(0xFFE0E0E0),
surfaceVariant = Color(0xFF2B2B2B),
onSurfaceVariant = Color(0xFFB0B0B0),
surfaceTint = Color.White,
surfaceContainerLowest = Color(0xFF0F0F0F),
surfaceContainerLow = Color(0xFF1A1A1A),
surfaceContainer = Color(0xFF1E1E1E),
surfaceContainerHigh = Color(0xFF252525),
surfaceContainerHighest = Color(0xFF2C2C2C),
inverseSurface = Color(0xFFE0E0E0),
inverseOnSurface = Color(0xFF121212),
error = Color(0xFFE0E0E0),
onError = Color.Black,
errorContainer = Color(0xFF404040),
onErrorContainer = Color(0xFFE0E0E0),
outline = Color(0xFF666666),
outlineVariant = Color(0xFF444444),
scrim = Color.Black,
)
// ---------------------------------------------------------------------
// Monochrome Dark — every swatch is R=G=B; roles are distinguished
// purely by lightness, not hue.
// ---------------------------------------------------------------------
/** Monochrome color scheme (light). */
val MonochromeLightColorScheme =
lightColorScheme(
primary = Color.Black,
onPrimary = Color.White,
primaryContainer = Color(0xFFC8C8C8),
onPrimaryContainer = Color.Black,
inversePrimary = Color(0xFFE0E0E0),
secondary = Color(0xFF4A4A4A),
onSecondary = Color.White,
secondaryContainer = Color(0xFFF0F0F0),
onSecondaryContainer = Color(0xFF111111),
tertiary = Color(0xFF333333),
onTertiary = Color.White,
tertiaryContainer = Color(0xFFE8E8E8),
onTertiaryContainer = Color(0xFF111111),
background = Color(0xFFFFFFFF),
onBackground = Color(0xFF111111),
surface = Color(0xFFF5F5F5),
onSurface = Color(0xFF111111),
surfaceVariant = Color(0xFFE0E0E0),
onSurfaceVariant = Color(0xFF4A4A4A),
surfaceTint = Color.Black,
surfaceContainerLowest = Color(0xFFFFFFFF),
surfaceContainerLow = Color(0xFFF8F8F8),
surfaceContainer = Color(0xFFF5F5F5),
surfaceContainerHigh = Color(0xFFEEEEEE),
surfaceContainerHighest = Color(0xFFE3E3E3),
inverseSurface = Color(0xFF111111),
inverseOnSurface = Color(0xFFE0E0E0),
error = Color(0xFF111111),
onError = Color.White,
errorContainer = Color(0xFFD6D6D6),
onErrorContainer = Color(0xFF111111),
outline = Color(0xFF888888),
outlineVariant = Color(0xFFCCCCCC),
scrim = Color.Black,
)
private val MonoDarkFloor = Color(0xFF0A0A0A) // surfaceContainerLowest
private val MonoDarkBg = Color(0xFF121212) // background, surface, containerLow, all "on*" ink
private val MonoDarkContainer = Color(0xFF1E1E1E) // surfaceContainer
private val MonoDarkContainerHigh = Color(0xFF2A2A2A) // surfaceContainerHigh, surfaceVariant, status containers
private val MonoDarkContainerHighest = Color(0xFF363636) // surfaceContainerHighest, primaryContainer
private val MonoDarkOutline = Color(0xFF808080)
private val MonoDarkOutlineVariant = Color(0xFF404040)
private val MonoDarkTextDim = Color(0xFFB3B3B3) // onSurfaceVariant
private val MonoDarkTextBright = Color(0xFFF2F2F2) // onBackground/onSurface/inverseSurface/primary/error
private val MonoDarkSecondary = Color(0xFFCCCCCC) // secondary accent + warning
private val MonoDarkTertiary = Color(0xFF999999) // tertiary accent + success
private val MonoDarkInfo = Color(0xFF808080) // info — same value as outline, distinct role
/** Monochrome status colors (dark). */
val MonochromeDarkStatusColors =
HermesStatusColors(
success = Color(0xFFE0E0E0),
successContainer = Color(0xFF2A2A2A),
onSuccess = Color(0xFF121212),
warning = Color(0xFFCCCCCC),
warningContainer = Color(0xFF333333),
onWarning = Color(0xFF121212),
error = Color(0xFFFFFFFF),
errorContainer = Color(0xFF404040),
onError = Color(0xFF121212),
info = Color(0xFFB0B0B0),
infoContainer = Color(0xFF242424),
onInfo = Color(0xFF121212),
)
// ---------------------------------------------------------------------
// Monochrome Light — the same ramp, inverted
// ---------------------------------------------------------------------
/** Monochrome status colors (light). */
val MonochromeLightStatusColors =
HermesStatusColors(
success = Color(0xFF222222),
successContainer = Color(0xFFE5E5E5),
onSuccess = Color(0xFFFFFFFF),
warning = Color(0xFF444444),
warningContainer = Color(0xFFE0E0E0),
onWarning = Color(0xFFFFFFFF),
error = Color(0xFF000000),
errorContainer = Color(0xFFD6D6D6),
onError = Color(0xFFFFFFFF),
info = Color(0xFF555555),
infoContainer = Color(0xFFEAEAEA),
onInfo = Color(0xFFFFFFFF),
private val MonoLightCeiling = Color(0xFFFFFFFF) // surfaceContainerLowest
private val MonoLightBg = Color(0xFFFAFAFA) // background, surface, containerLow, all "on*" ink
private val MonoLightContainer = Color(0xFFF0F0F0) // surfaceContainer
private val MonoLightContainerHigh = Color(0xFFE0E0E0) // surfaceContainerHigh, surfaceVariant, status containers
private val MonoLightContainerHighest = Color(0xFFD1D1D1) // surfaceContainerHighest, primaryContainer
private val MonoLightOutline = Color(0xFF808080)
private val MonoLightOutlineVariant = Color(0xFFCCCCCC)
private val MonoLightTextDim = Color(0xFF4D4D4D) // onSurfaceVariant
private val MonoLightTextBright = Color(0xFF121212) // onBackground/onSurface/inverseSurface/primary/error
private val MonoLightSecondary = Color(0xFF333333) // secondary accent + warning
private val MonoLightTertiary = Color(0xFF666666) // tertiary accent + success
private val MonoLightInfo = Color(0xFF808080) // info — same value as outline, distinct role
/**
* Monochrome theme — pure grayscale, no hue in any slot including status
* colors. `success`/`warning`/`error`/`info` are separated only by
* lightness (darkest/most emphasized = error, lightest/most muted = info),
* so anything that leans on this theme for status meaning should not rely
* on color alone — pair it with an icon, label, or weight change. This is
* the one real accessibility trade-off of going fully monochrome; every
* other preset keeps status colors hued specifically to avoid it.
*/
val MonochromeTheme =
buildTheme(
dark =
PaletteColors(
primary = MonoDarkTextBright,
onPrimary = MonoDarkBg,
primaryContainer = MonoDarkContainerHighest,
onPrimaryContainer = MonoDarkTextBright,
secondary = MonoDarkSecondary,
onSecondary = MonoDarkBg,
secondaryContainer = MonoDarkContainerHigh,
onSecondaryContainer = MonoDarkSecondary,
tertiary = MonoDarkTertiary,
onTertiary = MonoDarkBg,
tertiaryContainer = MonoDarkContainerHigh,
onTertiaryContainer = MonoDarkTertiary,
background = MonoDarkBg,
onBackground = MonoDarkTextBright,
surface = MonoDarkBg,
onSurface = MonoDarkTextBright,
surfaceVariant = MonoDarkContainerHigh,
onSurfaceVariant = MonoDarkTextDim,
surfaceContainerLowest = MonoDarkFloor,
surfaceContainerLow = MonoDarkBg,
surfaceContainer = MonoDarkContainer,
surfaceContainerHigh = MonoDarkContainerHigh,
surfaceContainerHighest = MonoDarkContainerHighest,
inverseSurface = MonoDarkTextBright,
inverseOnSurface = MonoDarkBg,
inversePrimary = MonoLightTextBright,
outline = MonoDarkOutline,
outlineVariant = MonoDarkOutlineVariant,
scrim = Color.Black,
status =
HermesStatusColors(
success = MonoDarkTertiary,
successContainer = MonoDarkContainerHigh,
onSuccess = MonoDarkBg,
warning = MonoDarkSecondary,
warningContainer = MonoDarkContainerHigh,
onWarning = MonoDarkBg,
error = MonoDarkTextBright,
errorContainer = MonoDarkContainerHigh,
onError = MonoDarkBg,
onErrorContainer = MonoDarkTextBright,
info = MonoDarkInfo,
infoContainer = MonoDarkContainerHigh,
onInfo = MonoDarkBg,
),
),
light =
PaletteColors(
primary = MonoLightTextBright,
onPrimary = MonoLightBg,
primaryContainer = MonoLightContainerHighest,
onPrimaryContainer = MonoLightTextBright,
secondary = MonoLightSecondary,
onSecondary = MonoLightBg,
secondaryContainer = MonoLightContainerHigh,
onSecondaryContainer = MonoLightSecondary,
tertiary = MonoLightTertiary,
onTertiary = MonoLightBg,
tertiaryContainer = MonoLightContainerHigh,
onTertiaryContainer = MonoLightTertiary,
background = MonoLightBg,
onBackground = MonoLightTextBright,
surface = MonoLightBg,
onSurface = MonoLightTextBright,
surfaceVariant = MonoLightContainerHigh,
onSurfaceVariant = MonoLightTextDim,
surfaceContainerLowest = MonoLightCeiling,
surfaceContainerLow = MonoLightBg,
surfaceContainer = MonoLightContainer,
surfaceContainerHigh = MonoLightContainerHigh,
surfaceContainerHighest = MonoLightContainerHighest,
inverseSurface = MonoLightTextBright,
inverseOnSurface = MonoLightBg,
inversePrimary = MonoDarkTextBright,
outline = MonoLightOutline,
outlineVariant = MonoLightOutlineVariant,
scrim = Color.Black,
status =
HermesStatusColors(
success = MonoLightTertiary,
successContainer = MonoLightContainerHigh,
onSuccess = MonoLightBg,
warning = MonoLightSecondary,
warningContainer = MonoLightContainerHigh,
onWarning = MonoLightBg,
error = MonoLightTextBright,
errorContainer = MonoLightContainerHigh,
onError = MonoLightBg,
onErrorContainer = MonoLightTextBright,
info = MonoLightInfo,
infoContainer = MonoLightContainerHigh,
onInfo = MonoLightBg,
),
),
)
@@ -1,118 +0,0 @@
package com.m57.hermescontrol.theme.presets
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.ui.graphics.Color
import com.m57.hermescontrol.theme.HermesStatusColors
/** Neon Noir color scheme (dark). */
val NeonNoirDarkColorScheme =
darkColorScheme(
primary = Color(0xFFFF6BF4),
onPrimary = Color(0xFF1A001A),
primaryContainer = Color(0xFF3A003A),
onPrimaryContainer = Color(0xFFFFCCF2),
inversePrimary = Color(0xFFC440A8),
secondary = Color(0xFFA78BFA),
onSecondary = Color(0xFF0F0B1A),
secondaryContainer = Color(0xFF2B1F59),
onSecondaryContainer = Color(0xFFD9CCFF),
tertiary = Color(0xFF60F0FF),
onTertiary = Color(0xFF003B40),
tertiaryContainer = Color(0xFF004F59),
onTertiaryContainer = Color(0xFFBFFAFF),
background = Color(0xFF0F0B1A),
onBackground = Color(0xFFE8E0F0),
surface = Color(0xFF1A1530),
onSurface = Color(0xFFE8E0F0),
surfaceVariant = Color(0xFF2B2540),
onSurfaceVariant = Color(0xFF9A90B0),
surfaceTint = Color(0xFFFF6BF4),
surfaceContainerLowest = Color(0xFF0A0712),
surfaceContainerLow = Color(0xFF140F22),
surfaceContainer = Color(0xFF1A1530),
surfaceContainerHigh = Color(0xFF252040),
surfaceContainerHighest = Color(0xFF2F2A50),
inverseSurface = Color(0xFFE8E0F0),
inverseOnSurface = Color(0xFF0F0B1A),
error = Color(0xFFFF3D68),
onError = Color.White,
errorContainer = Color(0xFF3A0A14),
onErrorContainer = Color(0xFFFFB3C0),
outline = Color(0xFF3A3060),
outlineVariant = Color(0xFF2E2545),
scrim = Color(0xFF000000),
)
/** Neon Noir color scheme (light). */
val NeonNoirLightColorScheme =
lightColorScheme(
primary = Color(0xFFC440A8),
onPrimary = Color.White,
primaryContainer = Color(0xFFFFD9F2),
onPrimaryContainer = Color(0xFF3A003A),
inversePrimary = Color(0xFFFF6BF4),
secondary = Color(0xFF7C4DFF),
onSecondary = Color.White,
secondaryContainer = Color(0xFFEDE0FF),
onSecondaryContainer = Color(0xFF1E0066),
tertiary = Color(0xFF0098A6),
onTertiary = Color.White,
tertiaryContainer = Color(0xFFBFFAFF),
onTertiaryContainer = Color(0xFF003B40),
background = Color(0xFFF5F0FA),
onBackground = Color(0xFF0F0B1A),
surface = Color.White,
onSurface = Color(0xFF0F0B1A),
surfaceVariant = Color(0xFFEDE6F5),
onSurfaceVariant = Color(0xFF7A7090),
surfaceTint = Color(0xFFC440A8),
surfaceContainerLowest = Color(0xFFFFFFFF),
surfaceContainerLow = Color(0xFFFAF5FF),
surfaceContainer = Color(0xFFFDF8FF),
surfaceContainerHigh = Color(0xFFF0EDF5),
surfaceContainerHighest = Color(0xFFE6E0ED),
inverseSurface = Color(0xFF0F0B1A),
inverseOnSurface = Color(0xFFE8E0F0),
error = Color(0xFFB3261E),
onError = Color.White,
errorContainer = Color(0xFFF9DEDC),
onErrorContainer = Color(0xFF410E0B),
outline = Color(0xFFBFB8CC),
outlineVariant = Color(0xFFD9D2E3),
scrim = Color(0xFF000000),
)
/** Neon Noir status colors (dark). */
val NeonNoirDarkStatusColors =
HermesStatusColors(
success = Color(0xFF6FEA8B),
successContainer = Color(0xFF0A3A14),
onSuccess = Color(0xFF0F0B1A),
warning = Color(0xFFFFB347),
warningContainer = Color(0xFF3A2000),
onWarning = Color(0xFF0F0B1A),
error = Color(0xFFFF3D68),
errorContainer = Color(0xFF3A0A14),
onError = Color(0xFF0F0B1A),
info = Color(0xFF60F0FF),
infoContainer = Color(0xFF003B40),
onInfo = Color(0xFF0F0B1A),
)
/** Neon Noir status colors (light). */
val NeonNoirLightStatusColors =
HermesStatusColors(
success = Color(0xFF1B873A),
successContainer = Color(0xFFD7F5E0),
onSuccess = Color(0xFFFDF8FF),
warning = Color(0xFFB87800),
warningContainer = Color(0xFFFFEAB3),
onWarning = Color(0xFFFDF8FF),
error = Color(0xFFB3261E),
errorContainer = Color(0xFFF9DEDC),
onError = Color(0xFFFDF8FF),
info = Color(0xFF2E6FBD),
infoContainer = Color(0xFFD4E7FF),
onInfo = Color(0xFFFDF8FF),
)
@@ -0,0 +1,144 @@
package com.m57.hermescontrol.theme.presets
import androidx.compose.ui.graphics.Color
import com.m57.hermescontrol.theme.HermesStatusColors
import com.m57.hermescontrol.theme.PaletteColors
import com.m57.hermescontrol.theme.buildTheme
// ---------------------------------------------------------------------
// Nord — named swatches, official numbering (nord0–nord15).
// Polar Night (0–3, dark neutrals) and Snow Storm (4–6, light neutrals)
// supply the surfaces; Frost (7–10) and Aurora (11–15) supply the
// accents. Unlike Gruvbox, Nord defines one accent set — not a
// separate bright/faded pair per mode — so the same accent swatches
// are reused in both palettes below.
// ---------------------------------------------------------------------
private val Nord0 = Color(0xFF2E3440) // Polar Night — darkest
private val Nord1 = Color(0xFF3B4252)
private val Nord2 = Color(0xFF434C5E)
private val Nord3 = Color(0xFF4C566A)
private val Nord4 = Color(0xFFD8DEE9) // Snow Storm
private val Nord5 = Color(0xFFE5E9F0)
private val Nord6 = Color(0xFFECEFF4) // Snow Storm — lightest
private val Nord8 = Color(0xFF88C0D0) // Frost — primary accent
private val Nord9 = Color(0xFF81A1C1) // Frost — secondary / info
private val Nord11 = Color(0xFFBF616A) // Aurora red — error
private val Nord13 = Color(0xFFEBCB8B) // Aurora yellow — warning
private val Nord14 = Color(0xFFA3BE8C) // Aurora green — success
private val Nord15 = Color(0xFFB48EAD) // Aurora purple — tertiary
/**
* Nord theme.
*
* Nord only ships 4 Polar Night tones and 3 Snow Storm tones, one tone
* short of the 5-tier surface-container ladder, so the two outermost
* tiers on each side share a value (surfaceContainerHigh ==
* surfaceContainerHighest) rather than inventing an off-palette hex.
*
* Frost/Aurora accents are pastel by design, so onPrimary/onSecondary/
* onTertiary/status "on" colors use Polar Night (dark text) in *both*
* modes — reusing Snow Storm for light-mode "on" text (the convention in
* the Catppuccin/Gruvbox presets) would fail contrast against colors like
* Aurora yellow, which stays light even without a separate light-mode
* variant.
*/
val NordTheme =
buildTheme(
dark =
PaletteColors(
primary = Nord8,
onPrimary = Nord0,
primaryContainer = Nord3,
onPrimaryContainer = Nord6,
secondary = Nord9,
onSecondary = Nord0,
secondaryContainer = Nord2,
onSecondaryContainer = Nord4,
tertiary = Nord15,
onTertiary = Nord0,
tertiaryContainer = Nord3,
onTertiaryContainer = Nord6,
background = Nord0,
onBackground = Nord6,
surface = Nord0,
onSurface = Nord6,
surfaceVariant = Nord1,
onSurfaceVariant = Nord4,
surfaceContainerLowest = Nord0,
surfaceContainerLow = Nord1,
surfaceContainer = Nord2,
surfaceContainerHigh = Nord3,
surfaceContainerHighest = Nord3,
inverseSurface = Nord6,
inverseOnSurface = Nord0,
inversePrimary = Nord8,
outline = Nord3,
outlineVariant = Nord2,
scrim = Nord0,
status =
HermesStatusColors(
success = Nord14,
successContainer = Nord2,
onSuccess = Nord0,
warning = Nord13,
warningContainer = Nord2,
onWarning = Nord0,
error = Nord11,
errorContainer = Nord2,
onError = Nord0,
onErrorContainer = Nord6,
info = Nord9,
infoContainer = Nord2,
onInfo = Nord0,
),
),
light =
PaletteColors(
primary = Nord8,
onPrimary = Nord0,
primaryContainer = Nord4,
onPrimaryContainer = Nord0,
secondary = Nord9,
onSecondary = Nord0,
secondaryContainer = Nord5,
onSecondaryContainer = Nord1,
tertiary = Nord15,
onTertiary = Nord0,
tertiaryContainer = Nord4,
onTertiaryContainer = Nord0,
background = Nord6,
onBackground = Nord0,
surface = Nord6,
onSurface = Nord0,
surfaceVariant = Nord5,
onSurfaceVariant = Nord2,
surfaceContainerLowest = Nord6,
surfaceContainerLow = Nord5,
surfaceContainer = Nord5,
surfaceContainerHigh = Nord4,
surfaceContainerHighest = Nord4,
inverseSurface = Nord0,
inverseOnSurface = Nord6,
inversePrimary = Nord8,
outline = Nord2,
outlineVariant = Nord4,
scrim = Nord6,
status =
HermesStatusColors(
success = Nord14,
successContainer = Nord5,
onSuccess = Nord0,
warning = Nord13,
warningContainer = Nord5,
onWarning = Nord0,
error = Nord11,
errorContainer = Nord5,
onError = Nord0,
onErrorContainer = Nord11,
info = Nord9,
infoContainer = Nord5,
onInfo = Nord0,
),
),
)