inou/app/lib/design/inou_text.dart

497 lines
12 KiB
Dart

/// inou Text Styles & Typography Widgets
///
/// RULES:
/// - Pages MUST use InouText.* styles or widgets
/// - NO raw TextStyle() in page code
/// - NO fontSize: or fontWeight: in page code
///
/// Usage:
/// Text('Hello', style: InouText.pageTitle)
/// InouText.pageTitle('Hello')
/// InouText.body('Paragraph', color: InouTheme.textMuted)
import 'package:flutter/material.dart';
import 'inou_theme.dart';
/// Typography system for inou
///
/// Base: 16px (1rem), Sora font, line-height 1.5
class InouText {
InouText._();
// ===========================================
// FONT FAMILY
// ===========================================
static const String fontFamily = 'Sora';
// ===========================================
// TEXT STYLES
// ===========================================
/// Page title: 2.5rem (40px), weight 800 (ExtraBold)
/// Use for: Main page headings like "Style Guide", "Privacy Policy"
static const TextStyle pageTitle = TextStyle(
fontFamily: fontFamily,
fontSize: 40.0, // 2.5 * 16
fontWeight: FontWeight.w800,
letterSpacing: -0.5,
height: 1.2,
color: InouTheme.text,
);
/// Hero title: 2.25rem (36px), weight 300 (Light)
/// Use for: Large hero text like "Your data. Your rules."
static const TextStyle heroTitle = TextStyle(
fontFamily: fontFamily,
fontSize: 36.0, // 2.25 * 16
fontWeight: FontWeight.w300,
height: 1.2,
letterSpacing: -1.08, // -0.03em
color: InouTheme.text,
);
/// Section title: 1.4rem (22.4px), weight 600 (SemiBold)
/// Use for: Section headings like "What we collect"
static const TextStyle sectionTitle = TextStyle(
fontFamily: fontFamily,
fontSize: 22.4, // 1.4 * 16
fontWeight: FontWeight.w600,
color: InouTheme.text,
);
/// Subsection title: 1.1rem (17.6px), weight 600 (SemiBold)
/// Use for: Subsection headings like "Account information"
static const TextStyle subsectionTitle = TextStyle(
fontFamily: fontFamily,
fontSize: 17.6, // 1.1 * 16
fontWeight: FontWeight.w600,
color: InouTheme.text,
);
/// H3: 1.125rem (18px), weight 500 (Medium)
/// Use for: Tertiary headings
static const TextStyle h3 = TextStyle(
fontFamily: fontFamily,
fontSize: 18.0, // 1.125 * 16
fontWeight: FontWeight.w500,
color: InouTheme.text,
);
/// Intro text: 1.15rem (18.4px), weight 300 (Light)
/// Use for: Introduction paragraphs, larger body text
static const TextStyle intro = TextStyle(
fontFamily: fontFamily,
fontSize: 18.4,
fontWeight: FontWeight.w300,
height: 1.8,
color: InouTheme.textMuted,
);
/// Body light: 1rem (16px), weight 300 (Light)
/// Use for: Long-form content, articles, descriptions
static const TextStyle bodyLight = TextStyle(
fontFamily: fontFamily,
fontSize: 16.0,
fontWeight: FontWeight.w300,
height: 1.5,
color: InouTheme.text,
);
/// Body regular: 1rem (16px), weight 400 (Regular)
/// Use for: UI labels, default text, buttons
static const TextStyle body = TextStyle(
fontFamily: fontFamily,
fontSize: 16.0,
fontWeight: FontWeight.w400,
height: 1.5,
color: InouTheme.text,
);
/// Body small: 0.85rem (13.6px), weight 400 (Regular)
/// Use for: Secondary text, captions, helper text
static const TextStyle bodySmall = TextStyle(
fontFamily: fontFamily,
fontSize: 13.6, // 0.85 * 16
fontWeight: FontWeight.w400,
color: InouTheme.text,
);
/// Label: 1rem (16px), weight 500 (Medium)
/// Use for: Form labels, button text
static const TextStyle label = TextStyle(
fontFamily: fontFamily,
fontSize: 16.0,
fontWeight: FontWeight.w500,
color: InouTheme.text,
);
/// Label caps: 0.75rem (12px), weight 600, uppercase
/// Use for: Category labels like "TEXT BLOCKS", "TYPOGRAPHY SCALE"
static const TextStyle labelCaps = TextStyle(
fontFamily: fontFamily,
fontSize: 12.0, // 0.75 * 16
fontWeight: FontWeight.w600,
letterSpacing: 1.2, // 0.1em
color: InouTheme.textSubtle,
);
/// Logo: 1.75rem (28px), weight 700 (Bold)
/// Use for: "inou" in header
static const TextStyle logo = TextStyle(
fontFamily: fontFamily,
fontSize: 28.0, // 1.75 * 16
fontWeight: FontWeight.w700,
letterSpacing: -0.56, // -0.02em
);
/// Logo light: 1.75rem (28px), weight 300 (Light)
/// Use for: "health" in header
static const TextStyle logoLight = TextStyle(
fontFamily: fontFamily,
fontSize: 28.0, // 1.75 * 16
fontWeight: FontWeight.w300,
letterSpacing: -0.56, // -0.02em
);
/// Logo tagline: 0.95rem (15.2px), weight 300 (Light)
/// Use for: "ai answers for you" tagline
static const TextStyle logoTagline = TextStyle(
fontFamily: fontFamily,
fontSize: 15.2, // 0.95 * 16
fontWeight: FontWeight.w300,
letterSpacing: 0.608, // 0.04em
color: InouTheme.textMuted,
);
/// Nav item: 1rem (16px), weight 400 (Regular)
/// Use for: Navigation menu items
static const TextStyle nav = TextStyle(
fontFamily: fontFamily,
fontSize: 16.0,
fontWeight: FontWeight.w400,
color: InouTheme.text,
);
/// Nav item active: 1rem (16px), weight 600 (SemiBold)
/// Use for: Active navigation menu items
static const TextStyle navActive = TextStyle(
fontFamily: fontFamily,
fontSize: 16.0,
fontWeight: FontWeight.w600,
color: InouTheme.accent,
);
/// Mono: SF Mono, 0.85rem (13.6px)
/// Use for: Code, technical data, IDs
static const TextStyle mono = TextStyle(
fontFamily: 'SF Mono',
fontFamilyFallback: ['Monaco', 'Consolas', 'monospace'],
fontSize: 13.6, // 0.85 * 16
fontWeight: FontWeight.w400,
color: InouTheme.text,
);
/// Profile name: 1.25rem (20px), weight 600 (SemiBold)
/// Use for: User names in profile cards
static const TextStyle profileName = TextStyle(
fontFamily: fontFamily,
fontSize: 20.0, // 1.25 * 16
fontWeight: FontWeight.w600,
color: InouTheme.text,
);
/// Badge: 1rem (16px), weight 500 (Medium)
/// Use for: Badge/pill text
static const TextStyle badge = TextStyle(
fontFamily: fontFamily,
fontSize: 16.0,
fontWeight: FontWeight.w500,
);
/// Button: 1rem (16px), weight 500 (Medium)
/// Use for: Button text
static const TextStyle button = TextStyle(
fontFamily: fontFamily,
fontSize: 16.0,
fontWeight: FontWeight.w500,
);
/// Input: 1rem (16px), weight 400 (Regular)
/// Use for: Text input fields
static const TextStyle input = TextStyle(
fontFamily: fontFamily,
fontSize: 16.0,
fontWeight: FontWeight.w400,
color: InouTheme.text,
);
/// Input placeholder: 1rem (16px), weight 400 (Regular)
/// Use for: Placeholder text in inputs
static const TextStyle inputPlaceholder = TextStyle(
fontFamily: fontFamily,
fontSize: 16.0,
fontWeight: FontWeight.w400,
color: InouTheme.textSubtle,
);
/// Error text: 0.85rem (13.6px), weight 400 (Regular)
/// Use for: Form validation errors
static const TextStyle error = TextStyle(
fontFamily: fontFamily,
fontSize: 13.6,
fontWeight: FontWeight.w400,
color: InouTheme.danger,
);
/// Link: inherits size, weight 400, accent color
/// Use for: Inline links
static TextStyle link({double? fontSize}) => TextStyle(
fontFamily: fontFamily,
fontSize: fontSize ?? 16.0,
fontWeight: FontWeight.w400,
color: InouTheme.accent,
decoration: TextDecoration.underline,
decorationColor: InouTheme.accent,
);
// ===========================================
// CONVENIENCE WIDGETS
// ===========================================
/// Page title widget
static Widget pageTitleText(
String text, {
Color? color,
TextAlign? textAlign,
}) {
return Text(
text,
style: pageTitle.copyWith(color: color),
textAlign: textAlign,
);
}
/// Hero title widget
static Widget heroTitleText(
String text, {
Color? color,
TextAlign? textAlign,
}) {
return Text(
text,
style: heroTitle.copyWith(color: color),
textAlign: textAlign,
);
}
/// Section title widget
static Widget sectionTitleText(
String text, {
Color? color,
TextAlign? textAlign,
}) {
return Text(
text,
style: sectionTitle.copyWith(color: color),
textAlign: textAlign,
);
}
/// Subsection title widget
static Widget subsectionTitleText(
String text, {
Color? color,
TextAlign? textAlign,
}) {
return Text(
text,
style: subsectionTitle.copyWith(color: color),
textAlign: textAlign,
);
}
/// Body text widget
static Widget bodyText(
String text, {
Color? color,
TextAlign? textAlign,
int? maxLines,
TextOverflow? overflow,
}) {
return Text(
text,
style: body.copyWith(color: color),
textAlign: textAlign,
maxLines: maxLines,
overflow: overflow,
);
}
/// Body light text widget (for long-form)
static Widget bodyLightText(
String text, {
Color? color,
TextAlign? textAlign,
}) {
return Text(
text,
style: bodyLight.copyWith(color: color),
textAlign: textAlign,
);
}
/// Intro text widget
static Widget introText(
String text, {
Color? color,
TextAlign? textAlign,
}) {
return Text(
text,
style: intro.copyWith(color: color),
textAlign: textAlign,
);
}
/// Label caps widget (auto-uppercases)
static Widget labelCapsText(
String text, {
Color? color,
TextAlign? textAlign,
}) {
return Text(
text.toUpperCase(),
style: labelCaps.copyWith(color: color),
textAlign: textAlign,
);
}
/// Mono text widget
static Widget monoText(
String text, {
Color? color,
TextAlign? textAlign,
}) {
return Text(
text,
style: mono.copyWith(color: color),
textAlign: textAlign,
);
}
/// Small text widget
static Widget smallText(
String text, {
Color? color,
TextAlign? textAlign,
}) {
return Text(
text,
style: bodySmall.copyWith(color: color),
textAlign: textAlign,
);
}
/// Error text widget
static Widget errorText(
String text, {
TextAlign? textAlign,
}) {
return Text(
text,
style: error,
textAlign: textAlign,
);
}
// ===========================================
// RICH TEXT HELPERS
// ===========================================
/// Build rich text with multiple styled spans
static Widget rich(
List<InlineSpan> children, {
TextAlign? textAlign,
}) {
return Text.rich(
TextSpan(children: children),
textAlign: textAlign,
);
}
}
/// Styled text spans for use with InouText.rich()
class InouSpan {
InouSpan._();
/// Accent colored text
static TextSpan accent(String text, {TextStyle? baseStyle}) {
return TextSpan(
text: text,
style: (baseStyle ?? InouText.body).copyWith(color: InouTheme.accent),
);
}
/// Muted colored text
static TextSpan muted(String text, {TextStyle? baseStyle}) {
return TextSpan(
text: text,
style: (baseStyle ?? InouText.body).copyWith(color: InouTheme.textMuted),
);
}
/// Subtle colored text
static TextSpan subtle(String text, {TextStyle? baseStyle}) {
return TextSpan(
text: text,
style: (baseStyle ?? InouText.body).copyWith(color: InouTheme.textSubtle),
);
}
/// Bold text
static TextSpan bold(String text, {TextStyle? baseStyle}) {
return TextSpan(
text: text,
style: (baseStyle ?? InouText.body).copyWith(fontWeight: FontWeight.w700),
);
}
/// SemiBold text
static TextSpan semiBold(String text, {TextStyle? baseStyle}) {
return TextSpan(
text: text,
style: (baseStyle ?? InouText.body).copyWith(fontWeight: FontWeight.w600),
);
}
/// Light text
static TextSpan light(String text, {TextStyle? baseStyle}) {
return TextSpan(
text: text,
style: (baseStyle ?? InouText.body).copyWith(fontWeight: FontWeight.w300),
);
}
/// Plain text (default style)
static TextSpan plain(String text, {TextStyle? style}) {
return TextSpan(text: text, style: style);
}
/// Link text
static TextSpan link(
String text, {
VoidCallback? onTap,
TextStyle? baseStyle,
}) {
return TextSpan(
text: text,
style: (baseStyle ?? InouText.body).copyWith(
color: InouTheme.accent,
decoration: TextDecoration.underline,
decorationColor: InouTheme.accent,
),
// Note: For tap handling, wrap in GestureDetector or use url_launcher
);
}
}