sprint-4b: Flutter display theme system (AutopilotTheme + 4 palettes + tests)
Implements the visual theme system specified in mock up software.pdf.
Flutter project — display/:
- pubspec.yaml: ar_autopilot_display v0.4.0+4 (provider + shared_preferences)
- lib/theme/autopilot_theme.dart: AutopilotTheme class with 30+ design tokens
(backgrounds, panels, text, accent, set-point, semantic states, DISENGAGE,
action buttons, glow helpers, backgroundDecoration getter)
- lib/theme/theme_registry.dart: ThemeRegistry with 4 factory themes,
byId() fallback, architecture stub for Sprint 9 custom YAML themes
- lib/theme/theme_provider.dart: AutopilotThemeProvider (ChangeNotifier),
SharedPreferences persistence under 'autopilot.theme.id', NOT sent to ESP32
- lib/theme/themes/: 4 factory themes with exact hex values from spec:
light (cream/navy, accentGlowRadius=0 — daytime no-glow rule)
cyan (deep navy/neon-cyan, default, glowRadius=16)
wine (vinotinto, DISENGAGE=amber not red, glowRadius=18)
ochre (warm brown/gold, okColor=lime for contrast, glowRadius=18)
- lib/screens/settings/appearance_settings.dart: Appearance screen with
4-card theme previews (200x120px), 400ms AnimatedContainer transitions,
triple-tap shortcut note, Sprint-5 placeholders for auto day/night and
ambient light sensor toggles
- lib/widgets/themed/: 4 themed widgets consuming AutopilotThemeProvider:
compass_rose.dart (heading arc, N mark, set-point tick, glow ring)
disengage_button.dart (60x60 min touch target, gradient, glow)
mode_selector.dart (STANDBY/HDG HOLD/TRACK with accent highlight)
rudder_indicator.dart (horizontal bar -35° to +35°, accent knob)
- lib/main.dart: app entry point with ChangeNotifierProvider
Tests — display/test/theme/:
- theme_registry_test.dart: 4 themes load, correct IDs, display order,
no null tokens, glow rules, backgroundGradient rules
- theme_provider_test.dart: default load=cyan, persistence across restarts,
setTheme notifies listeners, unknown ID falls back to default
- theme_contrast_test.dart: WCAG checks — DISENGAGE ≥7:1 AAA,
action buttons ≥4.5:1 AA, textMain ≥4.5:1 AA, setLight/okColor ≥3:1
Also:
- .gitignore: added !display/lib/ exception (lib/ was excluded for Python venv)
Design rules enforced:
- No glow in light mode (accentGlowRadius=0)
- DISENGAGE = amber in wine theme (red would blend into palette)
- North mark = warm colour on all themes (nautical convention)
- Touch targets: 48px nominal, 60px critical
- Theme not sent to ESP32, not synced between displays
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Visual theme token set for the AR-Autopilot display app.
|
||||
///
|
||||
/// Every widget reads design tokens from [AutopilotTheme] via
|
||||
/// [AutopilotThemeProvider]. No widget hard-codes colors.
|
||||
///
|
||||
/// ## Design invariants (apply to ALL themes)
|
||||
/// 1. **DISENGAGE** is always the highest-contrast element on screen.
|
||||
/// If the palette makes red ambiguous, it changes to amber (wine theme).
|
||||
/// 2. **Action buttons** (DODGE, ±1°, ±10°) must be legible at rest —
|
||||
/// operators work with gloves in rain; hover/tap is not reliable.
|
||||
/// 3. **Compass heading text** uses [textMain] with [accentGlowColor] glow.
|
||||
/// 4. **Set-point** always uses [setLight] (amber/gold) — cognitively stable
|
||||
/// across palettes; represents "operator intent".
|
||||
/// 5. **North mark** is always a warm color ([northColor]) — nautical convention.
|
||||
/// 6. **Touch targets**: 48×48 px nominal, 60×60 px for critical controls.
|
||||
/// 7. **No glow in light mode**: [accentGlowRadius] == 0.0 for `light` theme.
|
||||
class AutopilotTheme {
|
||||
const AutopilotTheme({
|
||||
required this.id,
|
||||
required this.displayName,
|
||||
// Backgrounds
|
||||
required this.background,
|
||||
required this.backgroundMid,
|
||||
required this.backgroundDeep,
|
||||
required this.backgroundDeepest,
|
||||
this.backgroundGradient,
|
||||
// Panels
|
||||
required this.panelBackground,
|
||||
required this.panelBorder,
|
||||
// Text
|
||||
required this.textMain,
|
||||
required this.textMuted,
|
||||
required this.textSoft,
|
||||
required this.textDisabled,
|
||||
// Accent — heading arc, active mode indicator, rudder indicator
|
||||
required this.accentLight,
|
||||
required this.accentMid,
|
||||
required this.accentDark,
|
||||
required this.accentGlowRadius,
|
||||
required this.accentGlowColor,
|
||||
// Set-point / desired heading
|
||||
required this.setLight,
|
||||
required this.setDark,
|
||||
required this.setGlow,
|
||||
// Semantic states
|
||||
required this.okColor,
|
||||
required this.warnColor,
|
||||
required this.northColor,
|
||||
// DISENGAGE — always high contrast, always unambiguous
|
||||
required this.disengageBackground,
|
||||
required this.disengageText,
|
||||
required this.disengageBorder,
|
||||
required this.disengageGlow,
|
||||
// Action buttons (DODGE, ±1°, ±10°)
|
||||
required this.actionButtonBackground,
|
||||
required this.actionButtonBorder,
|
||||
required this.actionButtonText,
|
||||
required this.actionButtonGlow,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String displayName;
|
||||
|
||||
// ── Backgrounds ────────────────────────────────────────────────────────────
|
||||
/// Dominant background color. Used as solid fill (light) or radial
|
||||
/// gradient center (dark themes). See [backgroundDecoration].
|
||||
final Color background;
|
||||
final Color backgroundMid;
|
||||
final Color backgroundDeep;
|
||||
final Color backgroundDeepest;
|
||||
|
||||
/// Radial gradient for dark themes; `null` for the light theme
|
||||
/// (which uses a solid [background]).
|
||||
final Gradient? backgroundGradient;
|
||||
|
||||
// ── Panels ──────────────────────────────────────────────────────────────────
|
||||
final Gradient panelBackground;
|
||||
final Color panelBorder;
|
||||
|
||||
// ── Text ────────────────────────────────────────────────────────────────────
|
||||
final Color textMain;
|
||||
final Color textMuted;
|
||||
final Color textSoft;
|
||||
final Color textDisabled;
|
||||
|
||||
// ── Accent ──────────────────────────────────────────────────────────────────
|
||||
final Color accentLight;
|
||||
final Color accentMid;
|
||||
final Color accentDark;
|
||||
|
||||
/// Blur radius for accent glow effect. Zero in light mode (no glow).
|
||||
final double accentGlowRadius;
|
||||
final Color accentGlowColor;
|
||||
|
||||
// ── Set-point ───────────────────────────────────────────────────────────────
|
||||
final Color setLight;
|
||||
final Color setDark;
|
||||
final Color setGlow;
|
||||
|
||||
// ── Semantic states ─────────────────────────────────────────────────────────
|
||||
/// GPS OK, NMEA sentence valid, SOG within range.
|
||||
final Color okColor;
|
||||
|
||||
/// Heading error, cross-track error warning.
|
||||
final Color warnColor;
|
||||
|
||||
/// North mark on the compass rose. Always a warm color (never blue).
|
||||
final Color northColor;
|
||||
|
||||
// ── DISENGAGE ───────────────────────────────────────────────────────────────
|
||||
final Gradient disengageBackground;
|
||||
final Color disengageText;
|
||||
final Color disengageBorder;
|
||||
final Color disengageGlow;
|
||||
|
||||
// ── Action buttons (DODGE, ±1°, ±10°) ──────────────────────────────────────
|
||||
final Gradient actionButtonBackground;
|
||||
final Color actionButtonBorder;
|
||||
final Color actionButtonText;
|
||||
final Color actionButtonGlow;
|
||||
|
||||
// ── Computed helpers ────────────────────────────────────────────────────────
|
||||
|
||||
/// [BoxDecoration] for the full-screen background container.
|
||||
BoxDecoration get backgroundDecoration => backgroundGradient != null
|
||||
? BoxDecoration(gradient: backgroundGradient)
|
||||
: BoxDecoration(color: background);
|
||||
|
||||
/// [BoxShadow] list for accented glow (empty in light mode).
|
||||
List<BoxShadow> glowShadow(Color color, double radius) => radius > 0
|
||||
? [BoxShadow(color: color, blurRadius: radius, spreadRadius: 1)]
|
||||
: const [];
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
import 'autopilot_theme.dart';
|
||||
import 'theme_registry.dart';
|
||||
|
||||
/// Manages the active [AutopilotTheme] and persists the user's selection.
|
||||
///
|
||||
/// ## Persistence
|
||||
/// - Stored locally in [SharedPreferences] under [_kThemeKey].
|
||||
/// - **NOT sent to the ESP32** — firmware is colour-agnostic.
|
||||
/// - **NOT synchronised between displays** — each display (bridge / engine room)
|
||||
/// maintains its own independent preference.
|
||||
///
|
||||
/// ## Usage
|
||||
/// ```dart
|
||||
/// // In main.dart — wrap the widget tree:
|
||||
/// ChangeNotifierProvider<AutopilotThemeProvider>(
|
||||
/// create: (_) => await AutopilotThemeProvider.load(),
|
||||
/// child: const AutopilotApp(),
|
||||
/// )
|
||||
///
|
||||
/// // Read the current theme anywhere in the tree:
|
||||
/// final theme = context.watch<AutopilotThemeProvider>().current;
|
||||
///
|
||||
/// // Switch theme (e.g. from the Appearance screen):
|
||||
/// await context.read<AutopilotThemeProvider>().setTheme('wine');
|
||||
/// ```
|
||||
class AutopilotThemeProvider extends ChangeNotifier {
|
||||
static const String _kThemeKey = 'autopilot.theme.id';
|
||||
|
||||
AutopilotTheme _current;
|
||||
|
||||
AutopilotThemeProvider(this._current);
|
||||
|
||||
/// The currently active [AutopilotTheme].
|
||||
AutopilotTheme get current => _current;
|
||||
|
||||
/// Loads the persisted theme preference from [SharedPreferences].
|
||||
///
|
||||
/// Falls back to [ThemeRegistry.defaultId] if no preference is stored
|
||||
/// (e.g. first launch) or the stored id is no longer registered.
|
||||
static Future<AutopilotThemeProvider> load() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final id = prefs.getString(_kThemeKey) ?? ThemeRegistry.defaultId;
|
||||
return AutopilotThemeProvider(ThemeRegistry.byId(id));
|
||||
}
|
||||
|
||||
/// Switches to the theme identified by [id] and persists the selection.
|
||||
///
|
||||
/// Unknown ids silently fall back to [ThemeRegistry.defaultId].
|
||||
/// Notifies all listeners after the switch so the UI rebuilds.
|
||||
Future<void> setTheme(String id) async {
|
||||
_current = ThemeRegistry.byId(id);
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString(_kThemeKey, _current.id);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import 'autopilot_theme.dart';
|
||||
import 'themes/light_theme.dart';
|
||||
import 'themes/cyan_theme.dart';
|
||||
import 'themes/wine_theme.dart';
|
||||
import 'themes/ochre_theme.dart';
|
||||
|
||||
/// Central registry of all factory [AutopilotTheme]s.
|
||||
///
|
||||
/// Themes are keyed by their [AutopilotTheme.id] string.
|
||||
///
|
||||
/// ## Display order
|
||||
/// `light → cyan → wine → ochre` (shown in the Appearance selector grid).
|
||||
///
|
||||
/// ## Custom themes (Sprint 9 / Tier 3)
|
||||
/// Architecture is ready for integrators to ship custom YAML themes in
|
||||
/// `/config/themes/*.yaml`. [ThemeRegistry] will merge them with the
|
||||
/// factory set at startup. Not active until Sprint 9.
|
||||
abstract final class ThemeRegistry {
|
||||
/// Theme id used when no preference is stored (first run).
|
||||
static const String defaultId = 'cyan';
|
||||
|
||||
static final Map<String, AutopilotTheme> _registry = {
|
||||
lightTheme.id: lightTheme,
|
||||
cyanTheme.id: cyanTheme,
|
||||
wineTheme.id: wineTheme,
|
||||
ochreTheme.id: ochreTheme,
|
||||
};
|
||||
|
||||
/// All available themes in UI display order.
|
||||
static List<AutopilotTheme> get all => const [
|
||||
lightTheme,
|
||||
cyanTheme,
|
||||
wineTheme,
|
||||
ochreTheme,
|
||||
];
|
||||
|
||||
/// Returns the theme for [id].
|
||||
/// Falls back to [defaultId] (cyan) if [id] is not registered.
|
||||
static AutopilotTheme byId(String id) =>
|
||||
_registry[id] ?? _registry[defaultId]!;
|
||||
|
||||
/// Whether [id] belongs to a registered theme.
|
||||
static bool contains(String id) => _registry.containsKey(id);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../autopilot_theme.dart';
|
||||
|
||||
/// **cyan** — Cockpit cian (default)
|
||||
///
|
||||
/// The default factory theme. Matches the visual language of professional
|
||||
/// marine electronics (Raymarine, Garmin, Simrad). Neon cyan on deep navy.
|
||||
/// Used for twilight / night operation with ambient lighting.
|
||||
const AutopilotTheme cyanTheme = AutopilotTheme(
|
||||
id: 'cyan',
|
||||
displayName: 'Cockpit cian',
|
||||
|
||||
background: Color(0xFF0D1822),
|
||||
backgroundMid: Color(0xFF0F172A),
|
||||
backgroundDeep: Color(0xFF0A1220),
|
||||
backgroundDeepest: Color(0xFF020610),
|
||||
backgroundGradient: RadialGradient(
|
||||
colors: [Color(0xFF0D1822), Color(0xFF050810)],
|
||||
stops: [0.0, 0.7],
|
||||
),
|
||||
|
||||
panelBackground: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xE6142030), Color(0xE608101C)], // rgba(20,32,48,0.9) → rgba(8,16,28,0.9)
|
||||
),
|
||||
panelBorder: Color(0x4038BDF8), // rgba(56,189,248,0.25)
|
||||
|
||||
textMain: Color(0xFFE0F2FE),
|
||||
textMuted: Color(0xFF64748B),
|
||||
textSoft: Color(0xFFCBD5E1),
|
||||
textDisabled: Color(0xFF475569),
|
||||
|
||||
accentLight: Color(0xFF7DD3FC),
|
||||
accentMid: Color(0xFF0EA5E9),
|
||||
accentDark: Color(0xFF0369A1),
|
||||
accentGlowRadius: 16.0,
|
||||
accentGlowColor: Color(0x9938BDF8), // rgba(56,189,248,0.6)
|
||||
|
||||
setLight: Color(0xFFFBBF24),
|
||||
setDark: Color(0xFFA16207),
|
||||
setGlow: Color(0x66FBBF24),
|
||||
|
||||
okColor: Color(0xFF4ADE80),
|
||||
warnColor: Color(0xFFFBBF24),
|
||||
northColor: Color(0xFFF87171),
|
||||
|
||||
disengageBackground: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFFDC2626), Color(0xFF7F1D1D)],
|
||||
),
|
||||
disengageText: Color(0xFFFFFFFF),
|
||||
disengageBorder: Color(0xFFF87171),
|
||||
disengageGlow: Color(0x80DC2626),
|
||||
|
||||
actionButtonBackground: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFF1E4A72), Color(0xFF0C2540)], // improved contrast
|
||||
),
|
||||
actionButtonBorder: Color(0xFF38BDF8), // accented border, 1px
|
||||
actionButtonText: Color(0xFFE0F2FE),
|
||||
actionButtonGlow: Color(0x667DD3FC), // subtle text-shadow
|
||||
);
|
||||
@@ -0,0 +1,61 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../autopilot_theme.dart';
|
||||
|
||||
/// **light** — Claro (día)
|
||||
///
|
||||
/// Daytime operation under direct sunlight. Navy-blue accent on cream white.
|
||||
/// No glow effects — bloom is invisible and distracting in bright ambient light.
|
||||
const AutopilotTheme lightTheme = AutopilotTheme(
|
||||
id: 'light',
|
||||
displayName: 'Claro (día)',
|
||||
|
||||
background: Color(0xFFF5F4EE),
|
||||
backgroundMid: Color(0xFFFFFFFF),
|
||||
backgroundDeep: Color(0xFFF1EFE8),
|
||||
backgroundDeepest: Color(0xFFE8E6DD),
|
||||
backgroundGradient: null, // solid background — no gradient in light mode
|
||||
|
||||
panelBackground: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFFFFFFFF), Color(0xFFFFFFFF)],
|
||||
),
|
||||
panelBorder: Color(0x1F000000), // rgba(0,0,0,0.12)
|
||||
|
||||
textMain: Color(0xFF2C2C2A),
|
||||
textMuted: Color(0xFF6B6862),
|
||||
textSoft: Color(0xFF2C2C2A),
|
||||
textDisabled: Color(0xFFB4B2A9),
|
||||
|
||||
accentLight: Color(0xFF378ADD),
|
||||
accentMid: Color(0xFF185FA5),
|
||||
accentDark: Color(0xFF0C447C),
|
||||
accentGlowRadius: 0.0, // no glow in daytime mode
|
||||
accentGlowColor: Colors.transparent,
|
||||
|
||||
setLight: Color(0xFFCA8A04),
|
||||
setDark: Color(0xFF854F0B),
|
||||
setGlow: Color(0x33CA8A04),
|
||||
|
||||
okColor: Color(0xFF15803D),
|
||||
warnColor: Color(0xFFCA8A04),
|
||||
northColor: Color(0xFFB91C1C),
|
||||
|
||||
disengageBackground: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFFDC2626), Color(0xFF991B1B)],
|
||||
),
|
||||
disengageText: Color(0xFFFFFFFF),
|
||||
disengageBorder: Color(0xFFEF4444),
|
||||
disengageGlow: Color(0x4DDC2626),
|
||||
|
||||
actionButtonBackground: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFFFFFFFF), Color(0xFFF1EFE8)],
|
||||
),
|
||||
actionButtonBorder: Color(0x33000000), // 0.2 alpha — more visible than 0.12
|
||||
actionButtonText: Color(0xFF2C2C2A),
|
||||
actionButtonGlow: Colors.transparent,
|
||||
);
|
||||
@@ -0,0 +1,66 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../autopilot_theme.dart';
|
||||
|
||||
/// **ochre** — Ocre
|
||||
///
|
||||
/// Classic cabin / aged leather aesthetic. Warm amber-gold accent on deep
|
||||
/// brown. DISENGAGE returns to red (which contrasts well against ochre).
|
||||
/// okColor is lime-green to stand out from the dominant gold palette.
|
||||
const AutopilotTheme ochreTheme = AutopilotTheme(
|
||||
id: 'ochre',
|
||||
displayName: 'Ocre',
|
||||
|
||||
background: Color(0xFF2A1A08),
|
||||
backgroundMid: Color(0xFF2A1A08),
|
||||
backgroundDeep: Color(0xFF1A1004),
|
||||
backgroundDeepest: Color(0xFF080502),
|
||||
backgroundGradient: RadialGradient(
|
||||
colors: [Color(0xFF2A1A08), Color(0xFF0F0904)],
|
||||
stops: [0.0, 0.7],
|
||||
),
|
||||
|
||||
panelBackground: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xEB32200C), Color(0xEB181008)], // rgba(50,32,12,0.92) → rgba(24,16,8,0.92)
|
||||
),
|
||||
panelBorder: Color(0x52D97706), // rgba(217,119,6,0.32)
|
||||
|
||||
textMain: Color(0xFFFEF3C7),
|
||||
textMuted: Color(0xFFA8855A),
|
||||
textSoft: Color(0xFFFCD34D),
|
||||
textDisabled: Color(0xFF6B5435),
|
||||
|
||||
accentLight: Color(0xFFFBBF24),
|
||||
accentMid: Color(0xFFD97706),
|
||||
accentDark: Color(0xFF92400E),
|
||||
accentGlowRadius: 18.0,
|
||||
accentGlowColor: Color(0xB3D97706),
|
||||
|
||||
setLight: Color(0xFFFDE68A),
|
||||
setDark: Color(0xFFB45309),
|
||||
setGlow: Color(0x66FDE68A),
|
||||
|
||||
okColor: Color(0xFF84CC16), // lime-green — contrasts with gold
|
||||
warnColor: Color(0xFFFDE68A),
|
||||
northColor: Color(0xFFFDE047),
|
||||
|
||||
// DISENGAGE: red — contrasts well on ochre background
|
||||
disengageBackground: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFFDC2626), Color(0xFF7F1D1D)],
|
||||
),
|
||||
disengageText: Color(0xFFFFFFFF),
|
||||
disengageBorder: Color(0xFFF87171),
|
||||
disengageGlow: Color(0x99DC2626),
|
||||
|
||||
actionButtonBackground: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFF7A4E1A), Color(0xFF3A240C)],
|
||||
),
|
||||
actionButtonBorder: Color(0xFFFBBF24),
|
||||
actionButtonText: Color(0xFFFEF3C7),
|
||||
actionButtonGlow: Color(0x66FBBF24),
|
||||
);
|
||||
@@ -0,0 +1,67 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../autopilot_theme.dart';
|
||||
|
||||
/// **wine** — Vinotinto
|
||||
///
|
||||
/// Premium yacht / aesthetic preference palette. Deep crimson backgrounds
|
||||
/// with rose accent. IMPORTANT: DISENGAGE switches from red to amber-gold
|
||||
/// because red blends into the wine cockpit and loses its emergency signal.
|
||||
/// The amber still carries the "caution/critical action" cognitive association.
|
||||
const AutopilotTheme wineTheme = AutopilotTheme(
|
||||
id: 'wine',
|
||||
displayName: 'Vinotinto',
|
||||
|
||||
background: Color(0xFF2A0A14),
|
||||
backgroundMid: Color(0xFF2A0A14),
|
||||
backgroundDeep: Color(0xFF1A0610),
|
||||
backgroundDeepest: Color(0xFF080205),
|
||||
backgroundGradient: RadialGradient(
|
||||
colors: [Color(0xFF2A0A14), Color(0xFF0F0408)],
|
||||
stops: [0.0, 0.7],
|
||||
),
|
||||
|
||||
panelBackground: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xE630121C), Color(0xE614080E)], // rgba(48,18,28,0.9) → rgba(20,8,14,0.9)
|
||||
),
|
||||
panelBorder: Color(0x4DBE1746), // rgba(190,23,70,0.3)
|
||||
|
||||
textMain: Color(0xFFFDE0E7),
|
||||
textMuted: Color(0xFF8A5560),
|
||||
textSoft: Color(0xFFF9A8B8),
|
||||
textDisabled: Color(0xFF6A3848),
|
||||
|
||||
accentLight: Color(0xFFFB7185),
|
||||
accentMid: Color(0xFFE11D48),
|
||||
accentDark: Color(0xFF881337),
|
||||
accentGlowRadius: 18.0,
|
||||
accentGlowColor: Color(0xB3E11D48), // rgba(225,29,72,0.7)
|
||||
|
||||
setLight: Color(0xFFFBBF24),
|
||||
setDark: Color(0xFFA16207),
|
||||
setGlow: Color(0x66FBBF24),
|
||||
|
||||
okColor: Color(0xFFFBBF24), // gold not green — keeps warm palette
|
||||
warnColor: Color(0xFFFBBF24),
|
||||
northColor: Color(0xFFFDE047), // yellow not red — red is lost on wine background
|
||||
|
||||
// DISENGAGE: amber-gold instead of red — highest contrast on wine cockpit
|
||||
disengageBackground: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFFFBBF24), Color(0xFFB45309)],
|
||||
),
|
||||
disengageText: Color(0xFF1C0A02), // near-black — maximum contrast on amber
|
||||
disengageBorder: Color(0xFFFDE047),
|
||||
disengageGlow: Color(0x99FBBF24),
|
||||
|
||||
actionButtonBackground: LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xFF7A1F3A), Color(0xFF3A0E1C)], // improved contrast
|
||||
),
|
||||
actionButtonBorder: Color(0xFFFB7185), // vivid rose border
|
||||
actionButtonText: Color(0xFFFDE0E7),
|
||||
actionButtonGlow: Color(0x66FB7185),
|
||||
);
|
||||
Reference in New Issue
Block a user