feat(ui): warm-minimalism redesign — design tokens + 6 primitives + animated ∆

Replaces the Material-default look with a deliberate visual
language. Single accent (sky-cyan), Inter + JetBrains Mono via
google_fonts, dense rows over puffy cards, micro-interactions
under 250ms. Dark-first, light reaches parity.

New foundation under lib/theme/:
- tokens.dart   FaiColors / FaiSpace / FaiRadius / FaiMotion
- theme.dart    ColorScheme + typography + component themes for
                light and dark, plus FaiTheme.mono helper for
                technical strings (IDs, paths, capability refs).

Six primitives under lib/widgets/:
- FaiCard            flat card, optional accent stripe (top or
                     left). No shadows.
- FaiPill            small inline label with five tones
                     (neutral / accent / success / warning /
                     danger), optional mono and leading icon.
- FaiStatusDot       breathing dot, used as a "live" indicator.
- FaiDataRow         Linear-style dense row for the audit
                     stream — hover-elevation, mono leading,
                     coloured leading stripe.
- FaiEmptyState      gracious icon + title + hint + action,
                     replaces "(no data)" everywhere.
- FaiDeltaMark       the ∆ signature element. Three modes —
                     idle (still), live (gentle pulse), busy
                     (slow rotation). Drawn from primitives,
                     not a font glyph. Lives in the sidebar
                     header so the brand is always visible.

Page-level changes:
- main.dart        custom 220px sidebar replaces the Material
                   NavigationRail. ∆ on top, hub-connection
                   pill below it, hover-animated destinations,
                   page transitions are 200ms slide+fade.
- modules.dart     FaiCard rows with capability pills.
- audit.dart       FaiDataRow stream, segmented filter chips,
                   live status bar with hash-chain badge.
- approvals.dart   FaiCard with accent-top stripe, structured
                   action footer, toast on approve/reject,
                   themed reject-reason dialog.

google_fonts added as dep. flutter analyze clean. flutter test
2/2. flutter build macos --debug succeeds.

Bumps fai_studio 0.2.0 -> 0.3.0.

Signed-off-by: flemming-it <stefan.a.flemming@googlemail.com>
This commit is contained in:
flemming-it 2026-05-05 21:49:37 +02:00
parent 127c497f73
commit c94504247f
23 changed files with 1951 additions and 468 deletions

71
lib/theme/tokens.dart Normal file
View file

@ -0,0 +1,71 @@
// Design tokens for FI Studio.
//
// One file, semantic names, no magic numbers in the rest of the
// codebase. Both light and dark modes derive from these tokens.
import 'package:flutter/material.dart';
/// Colour palette. Single strong accent (FI Cyan) plus neutral
/// greys. Status colours stay restrained no candy palette.
class FaiColors {
FaiColors._();
// Brand accent. Slightly cooler / more saturated than the
// initial #1E3A8A this reads more as "deep tech, alive"
// than the previous corporate navy.
static const accent = Color(0xFF38BDF8); // sky-400 / cyan
static const accentDeep = Color(0xFF0EA5E9); // sky-500
static const accentMuted = Color(0xFF0369A1); // sky-700
// Semantic status. Used sparingly: tones, not flat fills.
static const success = Color(0xFF22C55E); // green-500
static const warning = Color(0xFFF59E0B); // amber-500
static const danger = Color(0xFFEF4444); // red-500
// Neutral scale. Dark-mode first.
static const black = Color(0xFF09090B); // canvas
static const surface = Color(0xFF18181B); // cards
static const surfaceHigh = Color(0xFF27272A); // elevated
static const border = Color(0xFF3F3F46); // 1px outlines
static const muted = Color(0xFF71717A); // de-emphasised text
static const text = Color(0xFFE4E4E7); // body
static const textStrong = Color(0xFFFAFAFA); // headings
// Light-mode neutrals. Calmer than pure white.
static const lightCanvas = Color(0xFFFAFAFA);
static const lightSurface = Color(0xFFFFFFFF);
static const lightSurfaceHigh = Color(0xFFF4F4F5);
static const lightBorder = Color(0xFFE4E4E7);
static const lightMuted = Color(0xFF71717A);
static const lightText = Color(0xFF18181B);
static const lightTextStrong = Color(0xFF09090B);
}
/// Spacing scale. Stick to multiples of 4. Keep the vocabulary
/// small six steps cover everything.
class FaiSpace {
FaiSpace._();
static const xs = 4.0;
static const sm = 8.0;
static const md = 12.0;
static const lg = 16.0;
static const xl = 24.0;
static const xxl = 32.0;
static const xxxl = 48.0;
}
/// Border radii. Two sizes only.
class FaiRadius {
FaiRadius._();
static const sm = 6.0;
static const md = 10.0;
}
/// Animation durations. Keep them under 300ms for power-user feel.
class FaiMotion {
FaiMotion._();
static const fast = Duration(milliseconds: 120);
static const base = Duration(milliseconds: 200);
static const slow = Duration(milliseconds: 320);
static const easing = Curves.easeOutCubic;
}