import 'package:flutter/material.dart'; /// ───────────────────────────────────────────────────────────────────────── /// PREMIUM LOGISTICS NARRATIVE — the design system the rebuilt screens follow /// /// A supplied spec, transcribed rather than interpreted: the values below are /// the ones in the brief. It is a different recipe from [MilerSurface]'s, and /// the difference is the whole point, so it is worth stating plainly: /// /// ``` /// MilerSurface dark canvas, white cards, NO border and NO shadow /// — separation bought with a 1.29:1 tonal step /// Narrative light canvas, white cards, 1px border AND a soft shadow /// — separation bought with an edge and a lift /// ``` /// /// Both work; what does not work is half of each. So a screen belongs to one /// system or the other, and the ones rebuilt against this brief take this one /// whole — canvas, card, border, shadow, radius and rhythm together. /// /// ── No glass ── /// /// The brief's own Level 2 is a frosted, 70%-white overlay. It is deliberately /// **not** transcribed here: translucency was cut from these screens on the /// instruction that came with the spec. A blurred surface is also the single /// most reliable way to drop a mid-range Android below 60fps, which is the /// phone this app ships to — so nothing here is see-through, and depth comes /// from tone, edge and shadow instead. /// ───────────────────────────────────────────────────────────────────────── class Narrative { Narrative._(); // ── Surfaces ────────────────────────────────────────────────────────── /// **Level 0.** The page. A soft, cool grey — light enough that a white card /// on it needs its edge and its shadow to read, which is exactly what this /// system gives them. static const Color canvas = Color(0xFFF8F9FA); /// **Level 1.** A card: pure white, over [cardBorder] and [cardShadow]. static const Color card = Color(0xFFFFFFFF); /// The hairline around a card. 1.13:1 against white on its own — it is not /// asked to separate anything by itself, it is asked to *finish* an edge the /// shadow has already softened. static const Color cardBorder = Color(0xFFE5E7EB); /// A tonal block *inside* a card — an address, a fact tile, a task row. static const Color inset = Color(0xFFF3F4F5); /// One step down from [inset], for a control's resting state. static const Color insetDeep = Color(0xFFEDEEEF); // ── Ink ─────────────────────────────────────────────────────────────── /// Headings and any figure the screen is about. 15.9:1 on [card]. static const Color ink = Color(0xFF191C1D); /// Supporting copy, captions, the quiet half of a pair. 8.0:1 on [card]. static const Color inkSoft = Color(0xFF584141); /// Labels in data-dense places — timestamps, counts, eyebrows. 4.9:1. static const Color inkMuted = Color(0xFF6B6B6B); // ── Accents ─────────────────────────────────────────────────────────── /// **Deep burgundy.** The brand, spent sparingly: primary actions, the /// selected state, and the one figure a screen is answering with. /// /// The brief names #800020 in prose and #570013 in its token table. The /// prose value is the one used, because it is the one the brief calls "the /// palette is centred around" — and it is the value the mock-ups were drawn /// with. static const Color burgundy = Color(0xFF800020); /// The burgundy at the weight a tint reads on white without becoming a /// surface of its own — the brief's "10% opacity burgundy" ghost fill. static const Color burgundyWash = Color(0x1A800020); /// **Refined emerald.** Completion, and only completion. static const Color emerald = Color(0xFF00472C); /// The emerald as a chip fill, with [emerald] as its text — the brief's /// "light tint of the status colour, darker text of the same hue". static const Color emeraldWash = Color(0x1A00472C); /// Amber, for the one row that still owes somebody something. static const Color amber = Color(0xFF8E5A00); static const Color amberWash = Color(0x1A8E5A00); // ── Shape ───────────────────────────────────────────────────────────── // // "Architectural curvature": large, intentional radii, nested so a child is // always visibly tighter than the surface holding it. /// A card, a sheet, a panel — anything that is a surface in its own right. static const double radiusCard = 28; /// A block inside a card: a tonal well, a task row, a fact tile. static const double radiusInner = 18; /// A chip, an icon container, a small tile. static const double radiusChip = 14; /// Fully rounded — pills, segmented tracks, avatars. static const double radiusPill = 999; // ── Elevation ───────────────────────────────────────────────────────── /// A card's lift: wide, diffuse and almost invisible. Offset 0,4 · blur 20 · /// 4% black. It is not there to be seen; it is there so the edge above it /// does not have to carry the separation alone. static const List cardShadow = [ BoxShadow(color: Color(0x0A000000), blurRadius: 20, offset: Offset(0, 4)), ]; /// A surface that floats over the page rather than sitting in it. static const List floatShadow = [ BoxShadow(color: Color(0x14000000), blurRadius: 28, offset: Offset(0, 8)), ]; /// The glow under a primary action — the brand's own colour, so the button /// reads as lit rather than as merely raised. static const List burgundyGlow = [ BoxShadow(color: Color(0x33800020), blurRadius: 20, offset: Offset(0, 8)), ]; // ── Rhythm ──────────────────────────────────────────────────────────── // // Base-8. Two sections are 32 apart, two blocks 16, two lines 8. /// The margin the page keeps off the screen's edge, so nothing interactive /// touches it and the cards read as floating. /// /// ── 24 → 14 ── /// /// The brief's figure is 24, and it is written for a page of prose. These /// pages are lists of stops read at arm's length: at 24 of margin plus 20 of /// card padding, a customer's name started 44pt in on a 390pt phone and the /// screen held two fewer rows for it. The reference mock-ups the brief came /// with are drawn at about half its own number, which is the answer — the /// margin exists so the cards read as floating, and 14 does that. /// /// ── 14 → 12 ── /// /// One more point off each side, measured on device rather than reasoned: /// with a 1px border and a 20-blur shadow the card's *visual* edge already /// sits a point or two inside its box, so 14 of margin reads as 16 and the /// page looked like it had a frame. 12 is where the gap stops being noticed /// and starts just being air. static const double gutter = 12; /// The padding inside a card. The brief says 20; 16 is what leaves the /// content room once [gutter] is added to it on a phone. static const double cardPad = 16; /// Between two logical sections. static const double gapSection = 32; /// Between two blocks in a section. static const double gapBlock = 16; /// Between two lines of one block. static const double gapLine = 8; /// Decoration for a Level-1 card, so no screen has to re-declare the three /// values that make one. static BoxDecoration cardBox({double? radius}) => BoxDecoration( color: card, borderRadius: BorderRadius.circular(radius ?? radiusCard), border: Border.all(color: cardBorder, width: 1), boxShadow: cardShadow, ); }