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:
1 parent
52e33a96b7
commit
ab42e19b7f
21 files changed
+1312
-862
No files matched your search
@@ -2,67 +2,11 @@ package com.m57.hermescontrol.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
// Hermes Mobile brand palette — v3 (Material You rewrite).
|
||||
//
|
||||
// On Android 12+ (API 31+) the app uses dynamic colour derived from the user's
|
||||
// wallpaper — this palette is NOT used for primary/surface roles on those
|
||||
// devices (see HermesControlTheme). On API 26–30 it serves as the full colour
|
||||
// scheme. Semantic status colours and chat-bubble colours are ALWAYS
|
||||
// brand-defined. All pairs verified for WCAG AA contrast (4.5:1 text, 3:1
|
||||
// large text / icons).
|
||||
|
||||
// ── Brand: Voltage Purple (primary) ──────────────────────────────────────
|
||||
|
||||
val HermesPurple = Color(0xFF7C5CFF)
|
||||
val HermesPurpleLight = Color(0xFFAC93FF)
|
||||
val HermesPurpleDark = Color(0xFF5A3FE0)
|
||||
val HermesPurpleContainer = Color(0xFF2B2159)
|
||||
val HermesPurpleOnContainer = Color(0xFFD9CCFF)
|
||||
|
||||
// ── Brand: Plasma Amber (secondary) ─────────────────────────────────────
|
||||
|
||||
val HermesAmber = Color(0xFFFFB627)
|
||||
val HermesAmberLight = Color(0xFFFFE082)
|
||||
val HermesAmberDark = Color(0xFFC68400)
|
||||
|
||||
// ── Dark surfaces (5-step elevation ladder) ─────────────────────────────
|
||||
|
||||
val DarkBackground = Color(0xFF0B0B11)
|
||||
val DarkSurface = Color(0xFF121218)
|
||||
val DarkSurfaceVariant = Color(0xFF1C1C26)
|
||||
val DarkSurfaceContainerLowest = Color(0xFF07070C)
|
||||
val DarkSurfaceContainerLow = Color(0xFF0F0F14)
|
||||
val DarkSurfaceContainer = Color(0xFF1C1C26)
|
||||
val DarkSurfaceContainerHigh = Color(0xFF262633)
|
||||
val DarkSurfaceContainerHighest = Color(0xFF30303D)
|
||||
val DarkOnSurface = Color(0xFFE8E6EE)
|
||||
val DarkOnSurfaceVariant = Color(0xFFB6B2C4)
|
||||
val DarkOutline = Color(0xFF4A4A5A)
|
||||
val DarkOutlineVariant = Color(0xFF2F2F3D)
|
||||
val DarkInverseSurface = Color(0xFFE8E6EE)
|
||||
val DarkInverseOnSurface = Color(0xFF1A1A24)
|
||||
val DarkScrim = Color(0xFF000000)
|
||||
|
||||
// ── Light surfaces (5-step elevation ladder) ────────────────────────────
|
||||
|
||||
val LightBackground = Color(0xFFF7F6FB)
|
||||
val LightSurface = Color(0xFFFFFFFF)
|
||||
val LightSurfaceVariant = Color(0xFFEFEDF4)
|
||||
val LightSurfaceContainerLowest = Color(0xFFFFFFFF)
|
||||
val LightSurfaceContainerLow = Color(0xFFF3F1F8)
|
||||
val LightSurfaceContainer = Color(0xFFEFEDF4)
|
||||
val LightSurfaceContainerHigh = Color(0xFFE6E3EE)
|
||||
val LightSurfaceContainerHighest = Color(0xFFDDD9EA)
|
||||
val LightOnSurface = Color(0xFF1A1A24)
|
||||
val LightOnSurfaceVariant = Color(0xFF56536A)
|
||||
val LightOutline = Color(0xFF79748E)
|
||||
val LightOutlineVariant = Color(0xFFC9C5D6)
|
||||
val LightInverseSurface = Color(0xFF1A1A24)
|
||||
val LightInverseOnSurface = Color(0xFFE8E6EE)
|
||||
val LightScrim = Color(0xFF000000)
|
||||
|
||||
// ── Semantic status colours (always brand-defined) ──────────────────────
|
||||
// Shared color tokens — only what code OUTSIDE the theme presets consumes.
|
||||
// The preset palettes themselves are self-contained template fills in
|
||||
// presets/ (see PaletteTemplate.kt); they don't import from here.
|
||||
|
||||
// Semantic status colors (always brand-defined).
|
||||
val StatusGreen = Color(0xFF3DDC84)
|
||||
val StatusGreenContainer = Color(0xFF143A23)
|
||||
val StatusRed = Color(0xFFFF5C5C)
|
||||
@@ -75,31 +19,25 @@ val StatusGrey = Color(0xFF9E9E9E)
|
||||
val StatusGreyDark = Color(0xFF1A1A24)
|
||||
val StatusGreyLight = Color(0xFFF5F5F5)
|
||||
|
||||
// ── Chat-specific colours (default theme fallback tokens) ─────────────────
|
||||
|
||||
val AssistantBubble = Color(0xFF1C1C26)
|
||||
val AssistantBubbleLight = Color(0xFFEDEAF4)
|
||||
val SystemMessageColor = Color(0xFF8B879A)
|
||||
val ToolChipColor = Color(0xFF262633)
|
||||
val ToolChipColorLight = Color(0xFFE6E3EE)
|
||||
|
||||
// ── Syntax highlighting tokens (code blocks) ─────────────────────────────
|
||||
// VS Code Dark+ palette — good general readability across all presets.
|
||||
// These are consumed exclusively by CodeBlockCard (MessageCards.kt).
|
||||
// Chat-specific fallback tokens (default theme surface text colors).
|
||||
val DarkOnSurface = Color(0xFFE8E6EE)
|
||||
val LightOnSurface = Color(0xFF1A1A24)
|
||||
|
||||
// Syntax highlighting tokens (code blocks) — VS Code Dark+ palette — good
|
||||
// general readability across all presets. Consumed by CodeBlockCard.
|
||||
val CodeKeyword = Color(0xFF569CD6) // blue — for val, fun, class, etc.
|
||||
val CodeString = Color(0xFFCE9178) // orange — for "string literals"
|
||||
val CodeComment = Color(0xFF6A9955) // green — for // comments
|
||||
val CodeNumber = Color(0xFFB5CEA8) // light green — for 42, 0xFF
|
||||
val CodePunctuation = Color(0xFFD4D4D4) // gray — for {, (, ;, etc.
|
||||
|
||||
// ── Code block / terminal surface colours (issue #659) ───────────────
|
||||
// Code block / terminal surface colors (issue #659).
|
||||
val CodeTerminalBg = Color(0xFF1E1E1E)
|
||||
val CodeTerminalBorder = Color(0xFF333333)
|
||||
val CodeTerminalText = Color(0xFFD4D4D4)
|
||||
val CodeTerminalMuted = Color(0xFF808080)
|
||||
|
||||
// ── Diff viewer tokens (issue #738) ──────────────────────────────────
|
||||
// Diff viewer tokens (issue #738).
|
||||
val CodeDiffAddBg = Color(0x263DDC84)
|
||||
val CodeDiffAddText = Color(0xFF3DDC84)
|
||||
val CodeDiffDeleteBg = Color(0x26FF5C5C)
|
||||
@@ -107,9 +45,8 @@ val CodeDiffDeleteText = Color(0xFFFF5C5C)
|
||||
val CodeDiffHunkBg = Color(0x264DA8FF)
|
||||
val CodeDiffHunkText = Color(0xFF4DA8FF)
|
||||
|
||||
// ── Custom vector icons ─────────────────────────────────────────────
|
||||
// Default fill for hand-declared ImageVector icons (e.g. NeurologyIcon).
|
||||
// Mirrors Material's own icon set: vectors are drawn opaque black and
|
||||
// Icon()'s tint (LocalContentColor) replaces the RGB at draw time — only
|
||||
// the alpha channel survives, so this must stay opaque.
|
||||
// Custom vector icons — default fill for hand-declared ImageVector icons
|
||||
// (e.g. NeurologyIcon). Mirrors Material's own icon set: vectors are drawn
|
||||
// opaque black and Icon()'s tint (LocalContentColor) replaces the RGB at
|
||||
// draw time — only the alpha channel survives, so this must stay opaque.
|
||||
val IconDefaultFill = Color.Black
|
||||
@@ -24,6 +24,7 @@ data class HermesStatusColors(
|
||||
val error: Color,
|
||||
val errorContainer: Color,
|
||||
val onError: Color,
|
||||
val onErrorContainer: Color = StatusGreyLight,
|
||||
val info: Color,
|
||||
val infoContainer: Color,
|
||||
val onInfo: Color,
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
package com.m57.hermescontrol.theme
|
||||
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
/**
|
||||
* Theme template — the ONE shape every preset follows.
|
||||
*
|
||||
* A theme preset is a single [ThemePalette] built from raw [PaletteColors]
|
||||
* specs. Every preset file has the same skeleton: fill in the colors, done.
|
||||
* The mapping from raw colors to the Material 3 [ColorScheme] slots and the
|
||||
* [HermesStatusColors] model lives here — no per-preset behavior.
|
||||
*
|
||||
* A theme can declare which modes it ships via [ThemeMode]:
|
||||
* - [ThemeMode.FULL] — both dark and light palettes.
|
||||
* - [ThemeMode.DARK_ONLY] — dark palette only; the dispatcher falls back to
|
||||
* the default light palette in light mode (e.g. AMOLED — a light AMOLED
|
||||
* makes no sense).
|
||||
* - [ThemeMode.LIGHT_ONLY] — light palette only; symmetric fallback.
|
||||
*/
|
||||
|
||||
enum class ThemeMode { FULL, DARK_ONLY, LIGHT_ONLY }
|
||||
|
||||
/**
|
||||
* Raw color spec for ONE mode (dark or light) of a theme.
|
||||
*
|
||||
* The Material error slots are derived from [status] — the theme author
|
||||
* defines semantic colors once and the scheme inherits them.
|
||||
*/
|
||||
data class PaletteColors(
|
||||
val primary: Color,
|
||||
val onPrimary: Color,
|
||||
val primaryContainer: Color,
|
||||
val onPrimaryContainer: Color,
|
||||
val secondary: Color,
|
||||
val onSecondary: Color,
|
||||
val secondaryContainer: Color,
|
||||
val onSecondaryContainer: Color,
|
||||
val tertiary: Color,
|
||||
val onTertiary: Color,
|
||||
val tertiaryContainer: Color,
|
||||
val onTertiaryContainer: Color,
|
||||
val background: Color,
|
||||
val onBackground: Color,
|
||||
val surface: Color,
|
||||
val onSurface: Color,
|
||||
val surfaceVariant: Color,
|
||||
val onSurfaceVariant: Color,
|
||||
val surfaceContainerLowest: Color,
|
||||
val surfaceContainerLow: Color,
|
||||
val surfaceContainer: Color,
|
||||
val surfaceContainerHigh: Color,
|
||||
val surfaceContainerHighest: Color,
|
||||
val inverseSurface: Color,
|
||||
val inverseOnSurface: Color,
|
||||
val inversePrimary: Color,
|
||||
val outline: Color,
|
||||
val outlineVariant: Color,
|
||||
val scrim: Color,
|
||||
val status: HermesStatusColors,
|
||||
)
|
||||
|
||||
/** A complete theme — one per preset file, same shape everywhere. */
|
||||
data class ThemePalette(
|
||||
val mode: ThemeMode,
|
||||
val dark: PaletteColors?,
|
||||
val light: PaletteColors?,
|
||||
) {
|
||||
init {
|
||||
require(mode != ThemeMode.FULL || (dark != null && light != null)) {
|
||||
"FULL theme must provide both dark and light palettes"
|
||||
}
|
||||
require(mode != ThemeMode.DARK_ONLY || (dark != null && light == null)) {
|
||||
"DARK_ONLY theme must provide a dark palette and no light palette"
|
||||
}
|
||||
require(mode != ThemeMode.LIGHT_ONLY || (dark == null && light != null)) {
|
||||
"LIGHT_ONLY theme must provide a light palette and no dark palette"
|
||||
}
|
||||
}
|
||||
|
||||
val darkScheme: ColorScheme? by lazy { dark?.let(::darkSchemeOf) }
|
||||
val lightScheme: ColorScheme? by lazy { light?.let(::lightSchemeOf) }
|
||||
val darkStatus: HermesStatusColors? by lazy { dark?.status }
|
||||
val lightStatus: HermesStatusColors? by lazy { light?.status }
|
||||
|
||||
fun schemeFor(dark: Boolean): ColorScheme? = if (dark) darkScheme else lightScheme
|
||||
|
||||
fun statusFor(dark: Boolean): HermesStatusColors? = if (dark) darkStatus else lightStatus
|
||||
}
|
||||
|
||||
/** Full theme: bespoke dark + light palettes. */
|
||||
fun buildTheme(
|
||||
dark: PaletteColors,
|
||||
light: PaletteColors,
|
||||
): ThemePalette = ThemePalette(ThemeMode.FULL, dark, light)
|
||||
|
||||
/** Dark-only theme (e.g. AMOLED). Light mode falls back to the default theme. */
|
||||
fun buildThemeDarkOnly(dark: PaletteColors): ThemePalette = ThemePalette(ThemeMode.DARK_ONLY, dark, null)
|
||||
|
||||
/** Light-only theme. Dark mode falls back to the default theme. */
|
||||
fun buildThemeLightOnly(light: PaletteColors): ThemePalette = ThemePalette(ThemeMode.LIGHT_ONLY, null, light)
|
||||
|
||||
private fun darkSchemeOf(c: PaletteColors): ColorScheme =
|
||||
darkColorScheme(
|
||||
primary = c.primary,
|
||||
onPrimary = c.onPrimary,
|
||||
primaryContainer = c.primaryContainer,
|
||||
onPrimaryContainer = c.onPrimaryContainer,
|
||||
secondary = c.secondary,
|
||||
onSecondary = c.onSecondary,
|
||||
secondaryContainer = c.secondaryContainer,
|
||||
onSecondaryContainer = c.onSecondaryContainer,
|
||||
tertiary = c.tertiary,
|
||||
onTertiary = c.onTertiary,
|
||||
tertiaryContainer = c.tertiaryContainer,
|
||||
onTertiaryContainer = c.onTertiaryContainer,
|
||||
background = c.background,
|
||||
onBackground = c.onBackground,
|
||||
surface = c.surface,
|
||||
onSurface = c.onSurface,
|
||||
surfaceVariant = c.surfaceVariant,
|
||||
onSurfaceVariant = c.onSurfaceVariant,
|
||||
surfaceTint = c.primary,
|
||||
surfaceContainerLowest = c.surfaceContainerLowest,
|
||||
surfaceContainerLow = c.surfaceContainerLow,
|
||||
surfaceContainer = c.surfaceContainer,
|
||||
surfaceContainerHigh = c.surfaceContainerHigh,
|
||||
surfaceContainerHighest = c.surfaceContainerHighest,
|
||||
inverseSurface = c.inverseSurface,
|
||||
inverseOnSurface = c.inverseOnSurface,
|
||||
inversePrimary = c.inversePrimary,
|
||||
error = c.status.error,
|
||||
onError = c.status.onError,
|
||||
errorContainer = c.status.errorContainer,
|
||||
onErrorContainer = c.status.onErrorContainer,
|
||||
outline = c.outline,
|
||||
outlineVariant = c.outlineVariant,
|
||||
scrim = c.scrim,
|
||||
)
|
||||
|
||||
private fun lightSchemeOf(c: PaletteColors): ColorScheme =
|
||||
lightColorScheme(
|
||||
primary = c.primary,
|
||||
onPrimary = c.onPrimary,
|
||||
primaryContainer = c.primaryContainer,
|
||||
onPrimaryContainer = c.onPrimaryContainer,
|
||||
secondary = c.secondary,
|
||||
onSecondary = c.onSecondary,
|
||||
secondaryContainer = c.secondaryContainer,
|
||||
onSecondaryContainer = c.onSecondaryContainer,
|
||||
tertiary = c.tertiary,
|
||||
onTertiary = c.onTertiary,
|
||||
tertiaryContainer = c.tertiaryContainer,
|
||||
onTertiaryContainer = c.onTertiaryContainer,
|
||||
background = c.background,
|
||||
onBackground = c.onBackground,
|
||||
surface = c.surface,
|
||||
onSurface = c.onSurface,
|
||||
surfaceVariant = c.surfaceVariant,
|
||||
onSurfaceVariant = c.onSurfaceVariant,
|
||||
surfaceTint = c.primary,
|
||||
surfaceContainerLowest = c.surfaceContainerLowest,
|
||||
surfaceContainerLow = c.surfaceContainerLow,
|
||||
surfaceContainer = c.surfaceContainer,
|
||||
surfaceContainerHigh = c.surfaceContainerHigh,
|
||||
surfaceContainerHighest = c.surfaceContainerHighest,
|
||||
inverseSurface = c.inverseSurface,
|
||||
inverseOnSurface = c.inverseOnSurface,
|
||||
inversePrimary = c.inversePrimary,
|
||||
error = c.status.error,
|
||||
onError = c.status.onError,
|
||||
errorContainer = c.status.errorContainer,
|
||||
onErrorContainer = c.status.onErrorContainer,
|
||||
outline = c.outline,
|
||||
outlineVariant = c.outlineVariant,
|
||||
scrim = c.scrim,
|
||||
)
|
||||
@@ -8,106 +8,116 @@ All theme code is under:
|
||||
|
||||
```
|
||||
app/src/main/java/com/m57/hermescontrol/theme/
|
||||
├── Theme.kt # dispatcher — resolves scheme + status colors per preset
|
||||
├── Color.kt # brand design tokens (DarkSurface, HermesPurple, StatusGreen, …)
|
||||
├── Theme.kt # dispatcher — preset lookup + mode fallback
|
||||
├── PaletteTemplate.kt # the template — the ONE shape every theme follows
|
||||
├── Color.kt # shared tokens for non-theme code (status fallbacks, code blocks, …)
|
||||
├── HermesStatusColors.kt # semantic status color model (success/warning/error/info + on*)
|
||||
├── Type.kt / Shapes.kt # typography + shape tokens
|
||||
├── Spacing.kt / Motion.kt # spacing + motion tokens
|
||||
└── presets/ # one file per ThemePreset
|
||||
└── presets/ # one file per ThemePreset — same skeleton everywhere
|
||||
├── DefaultScheme.kt
|
||||
├── MonochromeScheme.kt
|
||||
├── GruvboxScheme.kt
|
||||
├── CatppuccinScheme.kt
|
||||
├── AmoledScheme.kt
|
||||
└── NeonNoirScheme.kt
|
||||
├── AmoledScheme.kt # dark-only (ThemeMode.DARK_ONLY)
|
||||
└── NordScheme.kt
|
||||
```
|
||||
|
||||
## The 4-token shape
|
||||
## The template — one shape for every theme
|
||||
|
||||
Every file in `presets/` is uniform — it exports the same four tokens:
|
||||
|
||||
| Token | Type | Purpose |
|
||||
|-------|------|---------|
|
||||
| `XxxDarkColorScheme` | `ColorScheme` | dark-mode Material 3 scheme |
|
||||
| `XxxLightColorScheme` | `ColorScheme` | light-mode Material 3 scheme |
|
||||
| `XxxDarkStatusColors` | `HermesStatusColors` | dark semantic status colors |
|
||||
| `XxxLightStatusColors` | `HermesStatusColors` | light semantic status colors |
|
||||
|
||||
A preset with no bespoke status set (AMOLED) aliases the defaults:
|
||||
Every preset file is a **single `ThemePalette`** built from raw color specs via
|
||||
the template in `PaletteTemplate.kt`. A theme is a pure color spec: fill in
|
||||
`PaletteColors` (the Material slots) + the semantic status set
|
||||
(`HermesStatusColors`) and the app does the rest. No per-preset behavior, no
|
||||
aliases, no shared-token imports.
|
||||
|
||||
```kotlin
|
||||
val AmoledDarkStatusColors = DefaultDarkStatusColors
|
||||
val AmoledLightStatusColors = DefaultLightStatusColors
|
||||
val MyTheme =
|
||||
buildTheme(
|
||||
dark =
|
||||
PaletteColors(
|
||||
primary = Color(0xFF…),
|
||||
// …every slot…
|
||||
status =
|
||||
HermesStatusColors(
|
||||
success = Color(0xFF…),
|
||||
// …
|
||||
),
|
||||
),
|
||||
light = PaletteColors(/* … */),
|
||||
)
|
||||
```
|
||||
|
||||
AMOLED has no bespoke light scheme, so it aliases the default light scheme too:
|
||||
The template maps `PaletteColors` into the Material 3 `ColorScheme` slots. The
|
||||
Material `error` slots (`error`, `onError`, `errorContainer`, `onErrorContainer`)
|
||||
are **derived** from the theme's status set — the theme author defines semantic
|
||||
colors once.
|
||||
|
||||
```kotlin
|
||||
val AmoledLightColorScheme = DefaultLightColorScheme
|
||||
```
|
||||
## Preset conventions
|
||||
|
||||
- **Named swatches** for any hex reused across slots (Gruvbox bg ladder,
|
||||
Catppuccin's official names, Nord's nord0–nord15, Monochrome's mono ramp).
|
||||
A theme with repeated hexes should define them once at the top of the file.
|
||||
- **Bright/faded accent split** (Gruvbox, Slate): bright variants against dark
|
||||
surfaces, muted "faded" variants against light surfaces so contrast holds
|
||||
without neon saturation. Single-accent-set themes (Nord) instead keep dark
|
||||
"on" text in both modes — pastel accents stay light, so Snow-Storm-style
|
||||
light text would fail contrast.
|
||||
- **Grayscale status colors** (Monochrome, AMOLED) separate success/warning/
|
||||
error/info by lightness only — consumers must pair with icons or labels.
|
||||
- Every shipped mode's error slot pairs are enforced at **>= 3:1 contrast** by
|
||||
`ThemePaletteTest` — a preset that breaks this fails the unit-test gate.
|
||||
|
||||
## Theme modes
|
||||
|
||||
A theme declares which modes it ships via the factory you build it with:
|
||||
|
||||
| Factory | Mode | Meaning |
|
||||
|---------|------|---------|
|
||||
| `buildTheme(...)` | `FULL` | bespoke dark + light palettes |
|
||||
| `buildThemeDarkOnly(...)` | `DARK_ONLY` | dark only; light mode falls back to the default theme (AMOLED) |
|
||||
| `buildThemeLightOnly(...)` | `LIGHT_ONLY` | light only; dark mode falls back to the default theme |
|
||||
|
||||
The dispatcher falls back to the brand **default** theme for any mode a preset
|
||||
doesn't ship — never to a sibling preset.
|
||||
|
||||
## The dispatcher
|
||||
|
||||
`Theme.kt` is a **pure lookup** — no special-casing. Given a `ThemePreset`
|
||||
and a dark flag, it returns the matching scheme/status from the preset file:
|
||||
`Theme.kt` is a **pure lookup** — no special-casing. Given a `ThemePreset` and
|
||||
a dark flag, `themeFor()` returns the preset's `ThemePalette`, then the scheme /
|
||||
status are read off it (with the mode fallback above):
|
||||
|
||||
```kotlin
|
||||
private fun resolveColorScheme(preset: ThemePreset, darkTheme: Boolean) = when (preset) {
|
||||
ThemePreset.DEFAULT -> if (darkTheme) DefaultDarkColorScheme else DefaultLightColorScheme
|
||||
ThemePreset.MONOCHROME -> if (darkTheme) MonochromeDarkColorScheme else MonochromeLightColorScheme
|
||||
// …one line per preset…
|
||||
}
|
||||
|
||||
private fun resolveStatusColors(preset: ThemePreset, darkTheme: Boolean) = when (preset) {
|
||||
ThemePreset.DEFAULT -> if (darkTheme) DefaultDarkStatusColors else DefaultLightStatusColors
|
||||
private fun themeFor(preset: ThemePreset): ThemePalette = when (preset) {
|
||||
ThemePreset.DEFAULT -> DefaultTheme
|
||||
ThemePreset.MONOCHROME -> MonochromeTheme
|
||||
// …one line per preset…
|
||||
}
|
||||
```
|
||||
|
||||
Dynamic (Material You) color on API 31+ overrides the preset scheme when
|
||||
`useDynamicColors = true`.
|
||||
|
||||
## DEFAULT uses design tokens — that's intentional
|
||||
|
||||
`DefaultScheme.kt` imports ~47 named tokens from `Color.kt`
|
||||
(`DarkBackground`, `HermesPurple`, `StatusGreenContainer`, …) and builds its
|
||||
schemes from them. This is **by design**: DEFAULT is the brand reference theme,
|
||||
so a palette change in `Color.kt` flows straight through.
|
||||
|
||||
The other presets are hand-authored community palettes and use **raw hex**
|
||||
(`Color(0xFFFE8019)`), so they only import `Color` (+ `HermesStatusColors`).
|
||||
Do **not** "fix" DEFAULT to raw hex to match them — that would break the token
|
||||
linkage. The asymmetry is semantic, not sloppy.
|
||||
`useDynamicColors = true`. Semantic status colors are always resolved from the
|
||||
active preset via `LocalHermesStatusColors`.
|
||||
|
||||
## Adding a new theme
|
||||
|
||||
1. **Create** `presets/<Name>Scheme.kt` exporting the 4 tokens:
|
||||
```kotlin
|
||||
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
|
||||
|
||||
val MyThemeDarkColorScheme = darkColorScheme(/* … */)
|
||||
val MyThemeLightColorScheme = lightColorScheme(/* … */)
|
||||
val MyThemeDarkStatusColors = HermesStatusColors(/* … */)
|
||||
val MyThemeLightStatusColors = HermesStatusColors(/* … */)
|
||||
```
|
||||
2. **Add 2 lines** to each `when` in `Theme.kt` (`resolveColorScheme` +
|
||||
`resolveStatusColors`), importing the new tokens at the top of the file.
|
||||
1. **Create** `presets/<Name>Scheme.kt` as a template fill (see above) —
|
||||
`buildTheme` for a full theme, `buildThemeDarkOnly` / `buildThemeLightOnly`
|
||||
for single-mode themes. Every status color must be bespoke — no aliasing.
|
||||
2. **Add 1 line** to `themeFor()` in `Theme.kt` + import the new val.
|
||||
3. **Add the enum entry** `MY_THEME` to `ThemePreset` in `Theme.kt`.
|
||||
4. **Wire the UI**: add the label + selection in
|
||||
`ui/settings/components/AppearanceSection.kt` (and any string resource).
|
||||
5. **Verify**:
|
||||
```bash
|
||||
./ktlint --format app/src/main/java/com/m57/hermescontrol/theme/presets/<Name>Scheme.kt
|
||||
./gradlew compileDebugKotlin
|
||||
./gradlew ktlintCheck testDebugUnitTest
|
||||
```
|
||||
`ThemePaletteTest` asserts >= 3:1 contrast on every shipped mode's error
|
||||
slot pairs and the ThemeMode invariants — it must stay green.
|
||||
|
||||
## Conventions
|
||||
|
||||
- ktlint 1.2.1 is enforced in CI. Run `./ktlint --format` before committing.
|
||||
- ktlint 1.2.1 is enforced in CI. Run `./gradlew ktlintCheck` before committing
|
||||
(the gradle task is the gate, not the standalone `ktlint` binary).
|
||||
- Import order is ASCII-lexicographic (uppercase before lowercase).
|
||||
- Every change goes through a PR — never push directly to `main`.
|
||||
@@ -2,6 +2,7 @@ package com.m57.hermescontrol.theme
|
||||
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.ColorScheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
@@ -9,75 +10,64 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.m57.hermescontrol.theme.presets.AmoledDarkColorScheme
|
||||
import com.m57.hermescontrol.theme.presets.AmoledDarkStatusColors
|
||||
import com.m57.hermescontrol.theme.presets.AmoledLightColorScheme
|
||||
import com.m57.hermescontrol.theme.presets.AmoledLightStatusColors
|
||||
import com.m57.hermescontrol.theme.presets.CatppuccinDarkColorScheme
|
||||
import com.m57.hermescontrol.theme.presets.CatppuccinDarkStatusColors
|
||||
import com.m57.hermescontrol.theme.presets.CatppuccinLightColorScheme
|
||||
import com.m57.hermescontrol.theme.presets.CatppuccinLightStatusColors
|
||||
import com.m57.hermescontrol.theme.presets.DefaultDarkColorScheme
|
||||
import com.m57.hermescontrol.theme.presets.DefaultDarkStatusColors
|
||||
import com.m57.hermescontrol.theme.presets.DefaultLightColorScheme
|
||||
import com.m57.hermescontrol.theme.presets.DefaultLightStatusColors
|
||||
import com.m57.hermescontrol.theme.presets.GruvboxDarkColorScheme
|
||||
import com.m57.hermescontrol.theme.presets.GruvboxDarkStatusColors
|
||||
import com.m57.hermescontrol.theme.presets.GruvboxLightColorScheme
|
||||
import com.m57.hermescontrol.theme.presets.GruvboxLightStatusColors
|
||||
import com.m57.hermescontrol.theme.presets.MonochromeDarkColorScheme
|
||||
import com.m57.hermescontrol.theme.presets.MonochromeDarkStatusColors
|
||||
import com.m57.hermescontrol.theme.presets.MonochromeLightColorScheme
|
||||
import com.m57.hermescontrol.theme.presets.MonochromeLightStatusColors
|
||||
import com.m57.hermescontrol.theme.presets.NeonNoirDarkColorScheme
|
||||
import com.m57.hermescontrol.theme.presets.NeonNoirDarkStatusColors
|
||||
import com.m57.hermescontrol.theme.presets.NeonNoirLightColorScheme
|
||||
import com.m57.hermescontrol.theme.presets.NeonNoirLightStatusColors
|
||||
import com.m57.hermescontrol.theme.presets.AmoledTheme
|
||||
import com.m57.hermescontrol.theme.presets.CatppuccinTheme
|
||||
import com.m57.hermescontrol.theme.presets.DefaultTheme
|
||||
import com.m57.hermescontrol.theme.presets.GruvboxTheme
|
||||
import com.m57.hermescontrol.theme.presets.MonochromeTheme
|
||||
import com.m57.hermescontrol.theme.presets.NordTheme
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
enum class ThemePreference { SYSTEM, LIGHT, DARK }
|
||||
|
||||
@Serializable
|
||||
enum class ThemePreset { DEFAULT, MONOCHROME, GRUVBOX, CATPPUCCIN, AMOLED, NEON_NOIR }
|
||||
enum class ThemePreset { DEFAULT, MONOCHROME, GRUVBOX, CATPPUCCIN, AMOLED, NORD }
|
||||
|
||||
val LocalThemePreference = compositionLocalOf { ThemePreference.SYSTEM }
|
||||
val LocalThemePreset = compositionLocalOf { ThemePreset.DEFAULT }
|
||||
|
||||
/**
|
||||
* The 6 preset themes — one file each, all built from the same
|
||||
* [PaletteTemplate] shape (see `PaletteTemplate.kt`).
|
||||
*/
|
||||
private fun themeFor(preset: ThemePreset): ThemePalette =
|
||||
when (preset) {
|
||||
ThemePreset.DEFAULT -> DefaultTheme
|
||||
ThemePreset.MONOCHROME -> MonochromeTheme
|
||||
ThemePreset.GRUVBOX -> GruvboxTheme
|
||||
ThemePreset.CATPPUCCIN -> CatppuccinTheme
|
||||
ThemePreset.AMOLED -> AmoledTheme
|
||||
ThemePreset.NORD -> NordTheme
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the Material 3 [ColorScheme] for a preset + dark flag.
|
||||
*
|
||||
* Every preset exports both a dark and light scheme. AMOLED's light scheme is
|
||||
* an explicit alias of the brand default light scheme.
|
||||
* A preset that doesn't ship the requested mode (DARK_ONLY / LIGHT_ONLY —
|
||||
* e.g. AMOLED has no light palette) falls back to the default theme's
|
||||
* palette for that mode.
|
||||
*/
|
||||
private fun resolveColorScheme(
|
||||
internal fun resolveColorScheme(
|
||||
preset: ThemePreset,
|
||||
darkTheme: Boolean,
|
||||
) = when (preset) {
|
||||
ThemePreset.DEFAULT -> if (darkTheme) DefaultDarkColorScheme else DefaultLightColorScheme
|
||||
ThemePreset.MONOCHROME -> if (darkTheme) MonochromeDarkColorScheme else MonochromeLightColorScheme
|
||||
ThemePreset.GRUVBOX -> if (darkTheme) GruvboxDarkColorScheme else GruvboxLightColorScheme
|
||||
ThemePreset.CATPPUCCIN -> if (darkTheme) CatppuccinDarkColorScheme else CatppuccinLightColorScheme
|
||||
ThemePreset.AMOLED -> if (darkTheme) AmoledDarkColorScheme else AmoledLightColorScheme
|
||||
ThemePreset.NEON_NOIR -> if (darkTheme) NeonNoirDarkColorScheme else NeonNoirLightColorScheme
|
||||
): ColorScheme {
|
||||
val theme = themeFor(preset)
|
||||
// DefaultTheme is FULL — its scheme is never null for either mode.
|
||||
return theme.schemeFor(darkTheme) ?: requireNotNull(DefaultTheme.schemeFor(darkTheme))
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the semantic status colors for a preset + dark flag.
|
||||
*
|
||||
* Every preset exports both a dark and light status set. MONOCHROME and AMOLED
|
||||
* alias the brand default status colors (they have no bespoke semantic set).
|
||||
* Same fallback rule as the scheme.
|
||||
*/
|
||||
private fun resolveStatusColors(
|
||||
internal fun resolveStatusColors(
|
||||
preset: ThemePreset,
|
||||
darkTheme: Boolean,
|
||||
) = when (preset) {
|
||||
ThemePreset.DEFAULT -> if (darkTheme) DefaultDarkStatusColors else DefaultLightStatusColors
|
||||
ThemePreset.MONOCHROME -> if (darkTheme) MonochromeDarkStatusColors else MonochromeLightStatusColors
|
||||
ThemePreset.GRUVBOX -> if (darkTheme) GruvboxDarkStatusColors else GruvboxLightStatusColors
|
||||
ThemePreset.CATPPUCCIN -> if (darkTheme) CatppuccinDarkStatusColors else CatppuccinLightStatusColors
|
||||
ThemePreset.AMOLED -> if (darkTheme) AmoledDarkStatusColors else AmoledLightStatusColors
|
||||
ThemePreset.NEON_NOIR -> if (darkTheme) NeonNoirDarkStatusColors else NeonNoirLightStatusColors
|
||||
): HermesStatusColors {
|
||||
val theme = themeFor(preset)
|
||||
// DefaultTheme is FULL — its status colors are never null for either mode.
|
||||
return theme.statusFor(darkTheme) ?: requireNotNull(DefaultTheme.statusFor(darkTheme))
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
)
|
||||
Reference in new issue
Block a user