nkbmusic/admin/lib/theme.dart
metatroncubeswdev cf645071aa Add NK Admin dashboard (Flutter web) for admin.nkbmusic.com
Email-code sign-in for admins only; Overview with a needs-attention list;
Songs with search, playback, editing, approve/hide and bulk edit; Albums
and Singers with merge; creator applications queue. Adds the admin views,
functions and creator_applications table, a deploy-admin script and a
how-to guide. The admin nginx config no longer caches unhashed JS and
sends its security headers again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 11:35:44 -04:00

133 lines
5.0 KiB
Dart

import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
/// Nanga Kathai palette (same as the app, lib/app/theme.dart).
abstract final class NkColors {
static const ground = Color(0xFF0B1A14);
static const surface = Color(0xFF14281F);
static const raised = Color(0xFF1F3A2B);
static const outline = Color(0xFF2A4537);
static const teaLeaf = Color(0xFF7FA857);
static const sun = Color(0xFFF5B335);
static const roofRed = Color(0xFFC9502F);
static const cream = Color(0xFFFBF7EE);
static const muted = Color(0xFF9FB3A6);
static const info = Color(0xFF6FA8C9);
}
ThemeData buildNkTheme() {
final base = ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
scaffoldBackgroundColor: NkColors.ground,
colorScheme: const ColorScheme.dark(
primary: NkColors.sun,
onPrimary: NkColors.ground,
secondary: NkColors.teaLeaf,
surface: NkColors.ground,
onSurface: NkColors.cream,
surfaceContainer: NkColors.surface,
surfaceContainerHigh: NkColors.raised,
surfaceContainerHighest: NkColors.raised,
outline: NkColors.outline,
outlineVariant: NkColors.outline,
error: NkColors.roofRed,
),
);
final body = base.textTheme.apply(
fontFamily: GoogleFonts.manrope().fontFamily,
bodyColor: NkColors.cream,
displayColor: NkColors.cream,
);
TextStyle display(double size, [FontWeight w = FontWeight.w800]) =>
GoogleFonts.sora(fontSize: size, fontWeight: w, color: NkColors.cream, height: 1.15);
OutlineInputBorder border(Color c) =>
OutlineInputBorder(borderRadius: BorderRadius.circular(12), borderSide: BorderSide(color: c, width: 1.5));
return base.copyWith(
textTheme: body.copyWith(
headlineMedium: display(28),
headlineSmall: display(22),
titleLarge: display(18, FontWeight.w700),
titleMedium: body.titleMedium?.copyWith(fontWeight: FontWeight.w700),
bodySmall: body.bodySmall?.copyWith(color: NkColors.muted),
),
filledButtonTheme: FilledButtonThemeData(
style: FilledButton.styleFrom(
backgroundColor: NkColors.sun,
foregroundColor: NkColors.ground,
minimumSize: const Size(64, 44),
shape: const StadiumBorder(),
textStyle: GoogleFonts.manrope(fontSize: 15, fontWeight: FontWeight.w700),
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: NkColors.cream,
minimumSize: const Size(64, 42),
side: const BorderSide(color: NkColors.outline, width: 1.5),
shape: const StadiumBorder(),
textStyle: GoogleFonts.manrope(fontSize: 14, fontWeight: FontWeight.w700),
),
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: NkColors.sun,
textStyle: GoogleFonts.manrope(fontWeight: FontWeight.w700),
),
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: NkColors.surface,
isDense: true,
hintStyle: const TextStyle(color: NkColors.muted),
labelStyle: const TextStyle(color: NkColors.muted),
border: border(NkColors.outline),
enabledBorder: border(NkColors.outline),
focusedBorder: border(NkColors.sun),
),
chipTheme: base.chipTheme.copyWith(
backgroundColor: NkColors.raised,
selectedColor: NkColors.sun,
side: BorderSide.none,
shape: const StadiumBorder(),
labelStyle: GoogleFonts.manrope(fontWeight: FontWeight.w600, color: NkColors.cream),
secondaryLabelStyle: GoogleFonts.manrope(fontWeight: FontWeight.w700, color: NkColors.ground),
showCheckmark: false,
),
cardTheme: CardThemeData(
color: NkColors.surface,
elevation: 0,
margin: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: const BorderSide(color: NkColors.outline),
),
),
dialogTheme: DialogThemeData(
backgroundColor: NkColors.surface,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
),
dividerTheme: const DividerThemeData(color: NkColors.outline, space: 1),
navigationRailTheme: NavigationRailThemeData(
backgroundColor: NkColors.surface,
indicatorColor: NkColors.raised,
selectedIconTheme: const IconThemeData(color: NkColors.sun),
unselectedIconTheme: const IconThemeData(color: NkColors.muted),
selectedLabelTextStyle: GoogleFonts.manrope(color: NkColors.sun, fontWeight: FontWeight.w700),
unselectedLabelTextStyle: GoogleFonts.manrope(color: NkColors.muted, fontWeight: FontWeight.w600),
),
checkboxTheme: CheckboxThemeData(
fillColor: WidgetStateProperty.resolveWith((s) => s.contains(WidgetState.selected) ? NkColors.sun : null),
checkColor: const WidgetStatePropertyAll(NkColors.ground),
),
snackBarTheme: const SnackBarThemeData(
backgroundColor: NkColors.raised,
contentTextStyle: TextStyle(color: NkColors.cream),
behavior: SnackBarBehavior.floating,
width: 420,
),
);
}