import 'package:flutter/material.dart'; /// Design ADR-1: the named type scale, split out of [PluriWaveTokens] into /// its own ThemeExtension so screens stop copy-pasting /// `.copyWith(fontWeight: w900, letterSpacing: …)`. The set is closed at six /// styles — a seventh requires amending ADR-1. /// /// [eyebrowLabel] never applies `toUpperCase()` — casing is a /// locale-hostile transform (Turkish dotless i, and scripts with no case at /// all). The style carries weight/letter-spacing only; each ARB value is /// authored in its own display form. @immutable class PluriWaveTypography extends ThemeExtension { const PluriWaveTypography({ required this.heroTime, required this.sectionTitle, required this.screenTitle, required this.cardTitle, required this.bodyStrong, required this.eyebrowLabel, }); /// 88 / w800 / height 1.0 / ls -2.0. Alarm ringing, inline time editor. /// Every call site must wrap this in `FittedBox(fit: BoxFit.scaleDown)` — /// it renders at 176px under a 2.0 text scaler. final TextStyle heroTime; /// 23 / w800 / ls -0.6. Root screen section headings. final TextStyle sectionTitle; /// 19 / w800 / ls -0.4. PluriPushScaffold header title. final TextStyle screenTitle; /// 14.5 / w700. Station cards, settings rows, alarm cards. final TextStyle cardTitle; /// 13 / w600. Card subtitles, meta lines. final TextStyle bodyStrong; /// 11 / w800 / ls 0.8. "EN DIRECTO", "PROGRAMADOS", settings group /// headers. final TextStyle eyebrowLabel; /// Builds the scale from [family] — the SAME resolved Google Fonts /// TextTheme instance [PluriWaveTheme.dark] already computes. Building the /// styles anywhere else would re-merge the font family, which is the /// copy-paste this extension exists to eliminate. factory PluriWaveTypography.from(TextTheme family) { final fallback = family.bodyMedium ?? const TextStyle(); TextStyle style( double fontSize, FontWeight fontWeight, { double? letterSpacing, double? height, Color? color, }) { return fallback.copyWith( fontSize: fontSize, fontWeight: fontWeight, letterSpacing: letterSpacing, height: height, color: color, ); } return PluriWaveTypography( heroTime: style(88, FontWeight.w800, letterSpacing: -2.0, height: 1.0), sectionTitle: style(23, FontWeight.w800, letterSpacing: -0.6), screenTitle: style(19, FontWeight.w800, letterSpacing: -0.4), cardTitle: style(14.5, FontWeight.w700), bodyStrong: style(13, FontWeight.w600), // S9 (Tier 1 visual fidelity): the prototype's eyebrows are always // rgba(242,247,250,.42) (t4 lines 254, 299, 381, 450, 511, 660) — this // style used to carry no colour at all, so it rendered at whatever // full-opacity default the ambient text theme resolved to. eyebrowLabel: style( 11, FontWeight.w800, letterSpacing: 0.8, color: const Color(0xFFF2F7FA).withValues(alpha: 0.42), ), ); } @override PluriWaveTypography copyWith({ TextStyle? heroTime, TextStyle? sectionTitle, TextStyle? screenTitle, TextStyle? cardTitle, TextStyle? bodyStrong, TextStyle? eyebrowLabel, }) { return PluriWaveTypography( heroTime: heroTime ?? this.heroTime, sectionTitle: sectionTitle ?? this.sectionTitle, screenTitle: screenTitle ?? this.screenTitle, cardTitle: cardTitle ?? this.cardTitle, bodyStrong: bodyStrong ?? this.bodyStrong, eyebrowLabel: eyebrowLabel ?? this.eyebrowLabel, ); } @override PluriWaveTypography lerp( covariant ThemeExtension? other, double t, ) { if (other is! PluriWaveTypography) return this; return PluriWaveTypography( heroTime: TextStyle.lerp(heroTime, other.heroTime, t) ?? heroTime, sectionTitle: TextStyle.lerp(sectionTitle, other.sectionTitle, t) ?? sectionTitle, screenTitle: TextStyle.lerp(screenTitle, other.screenTitle, t) ?? screenTitle, cardTitle: TextStyle.lerp(cardTitle, other.cardTitle, t) ?? cardTitle, bodyStrong: TextStyle.lerp(bodyStrong, other.bodyStrong, t) ?? bodyStrong, eyebrowLabel: TextStyle.lerp(eyebrowLabel, other.eyebrowLabel, t) ?? eyebrowLabel, ); } }