inou/design/flutter/inou_theme.dart

270 lines
8.4 KiB
Dart
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// AUTO-GENERATED from tokens.json — do not edit directly
// Run: node design/generate.js
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
/// inou Design System
/// Single source of truth: design/tokens.json
class InouTheme {
InouTheme._();
// ============================================
// COLORS
// ============================================
static const Color bg = Color(0xFFF8F7F6);
static const Color bgCard = Color(0xFFFFFFFF);
static const Color border = Color(0xFFE5E2DE);
static const Color borderHover = Color(0xFFC4BFB8);
static const Color text = Color(0xFF1C1917);
static const Color textMuted = Color(0xFF78716C);
static const Color textSubtle = Color(0xFFA8A29E);
static const Color accent = Color(0xFFB45309);
static const Color accentHover = Color(0xFF92400E);
static const Color accentLight = Color(0xFFFEF3C7);
static const Color danger = Color(0xFFDC2626);
static const Color dangerLight = Color(0xFFFEF2F2);
static const Color success = Color(0xFF059669);
static const Color successLight = Color(0xFFECFDF5);
// 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);
// ============================================
// 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 const double radiusFull = 9999.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 — matches inou.com/static/style.css
// Base: 15px (body)
// ============================================
static String get fontFamily => 'Sora';
// Page Title: 2.5rem / 700 = 37.5px
static TextStyle get pageTitle => GoogleFonts.sora(
fontSize: 37.5,
fontWeight: FontWeight.w700,
color: text,
);
// h1: 2.25rem / 300 / -0.03em = 33.75px (used in forms, etc.)
static TextStyle get h1 => GoogleFonts.sora(
fontSize: 33.75,
fontWeight: FontWeight.w300,
color: text,
letterSpacing: -0.45, // -0.03em × 15px
);
// h1.small: 1.5rem / 300 = 22.5px
static TextStyle get h1Small => GoogleFonts.sora(
fontSize: 22.5,
fontWeight: FontWeight.w300,
color: text,
);
// Section Title: 1.4rem / 600 = 21px (.privacy-container h2)
static TextStyle get sectionTitle => GoogleFonts.sora(
fontSize: 21.0,
fontWeight: FontWeight.w600,
color: text,
);
// h2 (regular): 1.5rem / 300 / -0.02em = 22.5px
static TextStyle get h2 => GoogleFonts.sora(
fontSize: 22.5,
fontWeight: FontWeight.w300,
color: text,
letterSpacing: -0.3, // -0.02em × 15px
);
// Subsection Title: 1.1rem / 600 = 16.5px (.privacy-container h3)
static TextStyle get subsectionTitle => GoogleFonts.sora(
fontSize: 16.5,
fontWeight: FontWeight.w600,
color: text,
);
// h3 (regular): 1.125rem / 500 = 16.875px
static TextStyle get h3 => GoogleFonts.sora(
fontSize: 16.875,
fontWeight: FontWeight.w500,
color: text,
);
// Intro text: 1.15rem / 300 = 17.25px
static TextStyle get intro => GoogleFonts.sora(
fontSize: 17.25,
fontWeight: FontWeight.w300,
color: textMuted,
height: 1.8,
);
// Body light (long-form): 1rem / 300 = 15px
static TextStyle get bodyLight => GoogleFonts.sora(
fontSize: 15.0,
fontWeight: FontWeight.w300,
color: textMuted,
height: 1.8,
);
// Body regular (UI labels): 1rem / 400 = 15px
static TextStyle get body => GoogleFonts.sora(
fontSize: 15.0,
fontWeight: FontWeight.w400,
color: text,
);
// Label / Category: 0.75rem / 600 / caps / 0.1em = 11.25px
static TextStyle get label => GoogleFonts.sora(
fontSize: 11.25,
fontWeight: FontWeight.w600,
color: textSubtle,
letterSpacing: 1.125, // 0.1em × 11.25px
);
// Small text: 0.85rem / 400 = 12.75px
static TextStyle get small => GoogleFonts.sora(
fontSize: 12.75,
fontWeight: FontWeight.w400,
color: textMuted,
);
// Mono (SF Mono fallback)
static TextStyle get mono => const TextStyle(
fontFamily: 'SF Mono',
fontFamilyFallback: ['Monaco', 'Consolas', 'monospace'],
fontSize: 12.75,
color: text,
);
// Legacy aliases for compatibility
static TextStyle get h1Large => pageTitle;
static TextStyle get bodyLarge => intro;
static TextStyle get bodyMedium => body;
static TextStyle get bodySmall => small;
static TextStyle get labelLarge => GoogleFonts.sora(
fontSize: 15.0,
fontWeight: FontWeight.w500,
color: text,
);
static TextStyle get labelSmall => label;
// ============================================
// THEME DATA
// ============================================
static ThemeData get light => ThemeData(
useMaterial3: true,
brightness: Brightness.light,
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: intro,
bodyMedium: body,
bodySmall: small,
labelLarge: labelLarge,
labelSmall: label,
),
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: labelLarge,
),
),
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
foregroundColor: text,
side: BorderSide(color: border),
padding: EdgeInsets.symmetric(horizontal: spaceLg, vertical: spaceMd),
shape: RoundedRectangleBorder(borderRadius: borderRadiusMd),
textStyle: labelLarge,
),
),
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: 2),
),
contentPadding: EdgeInsets.symmetric(horizontal: spaceMd, vertical: spaceMd),
),
dividerTheme: DividerThemeData(
color: border,
thickness: 1,
),
);
}