318 lines
10 KiB
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,
|
|
),
|
|
);
|
|
}
|