Files
pluriwave/lib/tema/pluriwave_typography.dart
T
FreeTLab 01615f9751 fix(escuchar,reproductor): add the section-heading token and screen 1-3 polish
Audit S7 plus the open items on screens 1-3. Escuchar gains the
prototype's own 52px "now listening" eyebrow header (t4:53), which is
structurally different from the 56px title row every other root uses
(t4:325) -- the earlier wiring test asserted PluriRootHeader on all five
roots, an over-broad premise now corrected to guard what actually holds:
no AppBar, and the sleep-timer action still reachable.
2026-07-30 14:48:06 +02:00

141 lines
5.4 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 was closed at
/// six styles; S7 (visual fidelity pass) amended ADR-1 to add a seventh —
/// [inPageSectionHeading] — rather than have four screens each keep
/// copy-pasting their own `titleMedium.copyWith(fontWeight: w900)` override,
/// the exact duplication this extension exists to eliminate.
///
/// [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,
required this.inPageSectionHeading,
});
/// 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;
/// 15 / w800 / ls -0.2. S7 (visual fidelity, t4 lines 80, 153, 164, 173):
/// an in-page section heading living directly on a screen's content —
/// "Tus emisoras", "Cerca de ti", "Explorar por", "Populares ahora".
/// Distinct from both [sectionTitle] (23, a ROOT screen's own title) and
/// [screenTitle] (19, a pushed screen's AppBar title) — this is smaller
/// than either, for a heading living one level below the screen itself.
final TextStyle inPageSectionHeading;
/// 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),
),
inPageSectionHeading: style(15, FontWeight.w800, letterSpacing: -0.2),
);
}
@override
PluriWaveTypography copyWith({
TextStyle? heroTime,
TextStyle? sectionTitle,
TextStyle? screenTitle,
TextStyle? cardTitle,
TextStyle? bodyStrong,
TextStyle? eyebrowLabel,
TextStyle? inPageSectionHeading,
}) {
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,
inPageSectionHeading: inPageSectionHeading ?? this.inPageSectionHeading,
);
}
@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,
inPageSectionHeading:
TextStyle.lerp(inPageSectionHeading, other.inPageSectionHeading, t) ??
inPageSectionHeading,
);
}
}