The prototype's eyebrows are always rgba(242,247,250,.42) (t4 lines 254, 299, 381, 450, 511, 660). eyebrowLabel set size/weight/letter- spacing only, no colour, so every call site (settings group headers, vacation section titles, the ringing screen's snooze label) rendered at whatever full-opacity default the ambient text theme resolved to. One factory-level change fixes every current and future consumer of PluriWaveTypography.eyebrowLabel — no call site needs editing. S9, Tier 1 visual-fidelity pass (audit id 2521).
123 lines
4.3 KiB
Dart
123 lines
4.3 KiB
Dart
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<PluriWaveTypography> {
|
|
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<PluriWaveTypography>? 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,
|
|
);
|
|
}
|
|
}
|