inou/app/lib/design/inou_theme.dart

318 lines
10 KiB
Dart

/// inou Design System
/// Source: https://inou.com/static/style.css
/// Base: 16px, Sora font, line-height 1.5
///
/// Using LOCAL Sora font (fonts/Sora-*.ttf), not google_fonts package.
import 'package:flutter/material.dart';
class InouTheme {
InouTheme._();
// Font family - local asset
static const String _fontFamily = 'Sora';
// ===========================================
// COLORS (from :root CSS variables)
// ===========================================
static const Color bg = Color(0xFFF8F7F6); // --bg
static const Color bgCard = Color(0xFFFFFFFF); // --bg-card
static const Color border = Color(0xFFE5E2DE); // --border
static const Color borderHover = Color(0xFFC4BFB8); // --border-hover
static const Color text = Color(0xFF1C1917); // --text
static const Color textMuted = Color(0xFF78716C); // --text-muted
static const Color textSubtle = Color(0xFFA8A29E); // --text-subtle
static const Color accent = Color(0xFFB45309); // --accent
static const Color accentHover = Color(0xFF92400E); // --accent-hover
static const Color accentLight = Color(0xFFFEF3C7); // --accent-light
static const Color danger = Color(0xFFDC2626); // --danger
static const Color dangerLight = Color(0xFFFEF2F2); // --danger-light
static const Color success = Color(0xFF059669); // --success
static const Color successLight = Color(0xFFECFDF5); // --success-light
// Indicator colors (data sections)
static const Color indicatorImaging = Color(0xFFB45309);
static const Color indicatorLabs = Color(0xFF059669);
static const Color indicatorUploads = Color(0xFF6366F1);
static const Color indicatorVitals = Color(0xFFEC4899);
static const Color indicatorMedications = Color(0xFF8B5CF6);
static const Color indicatorRecords = Color(0xFF06B6D4);
static const Color indicatorJournal = Color(0xFFF59E0B);
static const Color indicatorPrivacy = Color(0xFF64748B);
static const Color indicatorGenetics = Color(0xFF10B981);
// Message border colors
static const Color errorBorder = Color(0xFFFECACA); // #FECACA
static const Color infoBorder = Color(0xFFFDE68A); // #FDE68A
static const Color successBorder = Color(0xFFA7F3D0); // #A7F3D0
// ===========================================
// SPACING
// ===========================================
static const double spaceXs = 4.0;
static const double spaceSm = 8.0;
static const double spaceMd = 12.0;
static const double spaceLg = 16.0;
static const double spaceXl = 24.0;
static const double spaceXxl = 32.0;
static const double spaceXxxl = 48.0;
// ===========================================
// BORDER RADIUS
// ===========================================
static const double radiusSm = 4.0;
static const double radiusMd = 6.0;
static const double radiusLg = 8.0;
static const double radiusXl = 12.0;
static BorderRadius get borderRadiusSm => BorderRadius.circular(radiusSm);
static BorderRadius get borderRadiusMd => BorderRadius.circular(radiusMd);
static BorderRadius get borderRadiusLg => BorderRadius.circular(radiusLg);
// ===========================================
// LAYOUT
// ===========================================
static const double maxWidth = 1200.0;
static const double maxWidthNarrow = 800.0;
static const double maxWidthForm = 360.0;
// ===========================================
// TYPOGRAPHY
// CSS base: body { font-size: 16px; line-height: 1.5; font-weight: 400; }
// All rem values calculated as: rem * 16
// ===========================================
// h1: 2.25rem (36px), weight 300, line-height 1.2, letter-spacing -0.03em
static TextStyle get h1 => const TextStyle(
fontFamily: _fontFamily,
fontSize: 36.0, // 2.25 * 16
fontWeight: FontWeight.w300,
height: 1.2,
letterSpacing: -0.03 * 36.0, // -0.03em
color: text,
);
// Page title (styleguide): 2.5rem (40px), weight 700
static TextStyle get pageTitle => const TextStyle(
fontFamily: _fontFamily,
fontSize: 40.0, // 2.5 * 16
fontWeight: FontWeight.w800, // ExtraBold
letterSpacing: -0.5, // Tighter tracking to match CSS
height: 1.2, // Line height to match CSS defaults
color: text,
);
// h2: 1.5rem (24px), weight 300, letter-spacing -0.02em
static TextStyle get h2 => const TextStyle(
fontFamily: _fontFamily,
fontSize: 24.0, // 1.5 * 16
fontWeight: FontWeight.w300,
letterSpacing: -0.02 * 24.0, // -0.02em
color: text,
);
// Section title (styleguide): 1.4rem (22.4px), weight 600
static TextStyle get sectionTitle => const TextStyle(
fontFamily: _fontFamily,
fontSize: 22.4, // 1.4 * 16
fontWeight: FontWeight.w600,
color: text,
);
// h3: 1.125rem (18px), weight 500
static TextStyle get h3 => const TextStyle(
fontFamily: _fontFamily,
fontSize: 18.0, // 1.125 * 16
fontWeight: FontWeight.w500,
color: text,
);
// Subsection title (styleguide): 1.1rem (17.6px), weight 600
static TextStyle get subsectionTitle => const TextStyle(
fontFamily: _fontFamily,
fontSize: 17.6, // 1.1 * 16
fontWeight: FontWeight.w600,
color: text,
);
// Intro text: 1.15rem (18.4px), weight 300
static TextStyle get intro => const TextStyle(
fontFamily: _fontFamily,
fontSize: 18.4, // 1.15 * 16
fontWeight: FontWeight.w300,
height: 1.8,
color: textMuted,
);
// Body light (long-form): 1rem (16px), weight 300
static TextStyle get bodyLight => const TextStyle(
fontFamily: _fontFamily,
fontSize: 16.0,
fontWeight: FontWeight.w300,
height: 1.5,
color: text,
);
// Body regular (UI labels): 1rem (16px), weight 400
static TextStyle get body => const TextStyle(
fontFamily: _fontFamily,
fontSize: 16.0,
fontWeight: FontWeight.w400,
height: 1.5,
color: text,
);
// Small text: 0.85rem (13.6px), weight 400
static TextStyle get bodySmall => const TextStyle(
fontFamily: _fontFamily,
fontSize: 13.6, // 0.85 * 16
fontWeight: FontWeight.w400,
color: text,
);
// Label/Category: 0.75rem (12px), weight 600, uppercase, letter-spacing 0.1em
static TextStyle get labelCaps => const TextStyle(
fontFamily: _fontFamily,
fontSize: 12.0, // 0.75 * 16
fontWeight: FontWeight.w600,
letterSpacing: 0.1 * 12.0, // 0.1em
color: textSubtle,
);
// Button/label: 1rem (16px), weight 500
static TextStyle get label => const TextStyle(
fontFamily: _fontFamily,
fontSize: 16.0,
fontWeight: FontWeight.w500,
color: text,
);
static TextStyle get labelLarge => label; // alias
// Badge text: 1rem (16px), weight 500
static TextStyle get badge => const TextStyle(
fontFamily: _fontFamily,
fontSize: 16.0,
fontWeight: FontWeight.w500,
);
static TextStyle get badgeText => badge; // alias
// Logo: 1.75rem (28px), weight 700, letter-spacing -0.02em
static TextStyle get logo => const TextStyle(
fontFamily: _fontFamily,
fontSize: 28.0, // 1.75 * 16
fontWeight: FontWeight.w700,
letterSpacing: -0.02 * 28.0, // -0.02em
);
// Logo tagline: 0.95rem (15.2px), weight 300, letter-spacing 0.04em
static TextStyle get logoTagline => const TextStyle(
fontFamily: _fontFamily,
fontSize: 15.2, // 0.95 * 16
fontWeight: FontWeight.w300,
letterSpacing: 0.04 * 15.2, // 0.04em
color: textMuted,
);
// Mono: SF Mono, 0.85rem (13.6px)
static TextStyle get mono => const TextStyle(
fontFamily: 'SF Mono',
fontFamilyFallback: ['Monaco', 'Consolas', 'monospace'],
fontSize: 13.6, // 0.85 * 16
fontWeight: FontWeight.w400,
color: text,
);
// Profile card h3: 1.25rem (20px)
static TextStyle get profileName => const TextStyle(
fontFamily: _fontFamily,
fontSize: 20.0, // 1.25 * 16
fontWeight: FontWeight.w600,
color: text,
);
// ===========================================
// THEME DATA
// ===========================================
static ThemeData get light => ThemeData(
useMaterial3: true,
brightness: Brightness.light,
fontFamily: _fontFamily,
scaffoldBackgroundColor: bg,
colorScheme: ColorScheme.light(
primary: accent,
onPrimary: Colors.white,
secondary: accentLight,
onSecondary: accent,
surface: bgCard,
onSurface: text,
error: danger,
onError: Colors.white,
outline: border,
),
textTheme: TextTheme(
displayLarge: pageTitle,
displayMedium: h1,
headlineMedium: sectionTitle,
headlineSmall: subsectionTitle,
bodyLarge: body,
bodyMedium: body,
bodySmall: bodySmall,
labelLarge: label,
labelSmall: labelCaps,
),
appBarTheme: AppBarTheme(
backgroundColor: bg,
foregroundColor: text,
elevation: 0,
centerTitle: false,
),
cardTheme: CardTheme(
color: bgCard,
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: borderRadiusLg,
side: BorderSide(color: border),
),
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: accent,
foregroundColor: Colors.white,
elevation: 0,
padding: EdgeInsets.symmetric(horizontal: spaceLg, vertical: spaceMd),
shape: RoundedRectangleBorder(borderRadius: borderRadiusMd),
textStyle: label,
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: text,
side: BorderSide(color: border),
padding: EdgeInsets.symmetric(horizontal: spaceLg, vertical: spaceMd),
shape: RoundedRectangleBorder(borderRadius: borderRadiusMd),
textStyle: label,
),
),
inputDecorationTheme: InputDecorationTheme(
filled: true,
fillColor: bgCard,
border: OutlineInputBorder(
borderRadius: borderRadiusMd,
borderSide: BorderSide(color: border),
),
enabledBorder: OutlineInputBorder(
borderRadius: borderRadiusMd,
borderSide: BorderSide(color: border),
),
focusedBorder: OutlineInputBorder(
borderRadius: borderRadiusMd,
borderSide: BorderSide(color: accent, width: 1),
),
contentPadding: EdgeInsets.symmetric(horizontal: spaceMd, vertical: spaceMd),
),
dividerTheme: DividerThemeData(
color: border,
thickness: 1,
),
);
}