From 8431a8dc13a16a76883bd235217435587c63c76c Mon Sep 17 00:00:00 2001 From: Suherdy Yacob Date: Fri, 27 Mar 2026 11:30:44 +0700 Subject: [PATCH] feat: Implement "Heritage Gallery Editorial" design system with updated app theme and new design documentation. --- DESIGN.md | 93 +++++++++++++++++++++++++ lib/screens/branches_screen.dart | 8 +-- lib/screens/login_screen.dart | 17 +++-- lib/screens/loyalty_dashboard.dart | 17 ++--- lib/theme/app_theme.dart | 105 ++++++++++++++++++----------- 5 files changed, 176 insertions(+), 64 deletions(-) create mode 100644 DESIGN.md diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..626b740 --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,93 @@ +# Design System Strategy: Heritage Gallery Editorial + +## 1. Overview & Creative North Star +The Creative North Star for this design system is **"The Modern Curator."** We are not building a standard utility app; we are designing a digital gallery that honors a rich culinary heritage through a modern, high-end editorial lens. + +To achieve this, we move beyond the rigid "bootstrap" look. We break the template by embracing **Intentional Asymmetry**—where whitespace is as much a design element as the content itself. Overlapping elements, such as images bleeding off the edge or overlapping surface containers, create a sense of physical depth. By utilizing the **Plus Jakarta Sans** typeface at extreme scale contrasts, we establish an authoritative yet welcoming voice that feels premium, bespoke, and intentional. + +--- + +## 2. Colors +Our palette is rooted in the warmth of tradition but executed with modern precision. + +### The Palette +- **Primary (`#E1251B`)**: A bold, vibrant red used for high-impact CTAs and core brand moments. +- **Secondary (`#FFBF3C`)**: A warm gold, used to draw attention to interactive accents and "Mapan" highlights. +- **Tertiary (`#CA8342`)**: An earthy, wood-tone brown used for sophisticated detailing and organic warmth. +- **Background (`#EADFD2`)**: A soft, parchment-like neutral that provides the "gallery wall" for our content. + +### The "No-Line" Rule +**Explicit Instruction:** Do not use 1px solid borders to section off content. Traditional lines are "noise" that clutter the heritage aesthetic. Boundaries must be defined solely through background color shifts. For example, a `surface-container-low` section should sit directly on a `surface` background to define its territory. + +### Surface Hierarchy & Nesting +Treat the UI as a series of physical layers—stacked sheets of fine paper. +- Use the **Surface Tiers** (`surface-container-lowest` to `highest`) to create depth. +- **Nesting:** An inner card should be `surface-container-lowest` (pure white) when placed on a `surface-container-low` section. This creates a soft, natural "lift" without the need for artificial borders. + +### The "Glass & Gradient" Rule +To add "soul" to the interface: +- **Glassmorphism:** Use semi-transparent versions of `surface` colors with a `backdrop-blur` for floating navigation bars or modal overlays. +- **Signature Gradients:** Use subtle transitions from `primary` (`#BB0004`) to `primary-container` (`#E1251B`) on large buttons or Hero backgrounds. This prevents the "flat-web" look and adds professional polish. + +--- + +## 3. Typography +We use **Plus Jakarta Sans** to balance contemporary geometric shapes with warm, humanistic curves. + +- **Display Scale (`display-lg` to `sm`)**: Used for "Hero" moments and editorial storytelling. Use extreme negative letter-spacing (-0.02em) to create a high-fashion, compact look. +- **Headline & Title Scale**: These are your signposts. Use `headline-lg` for section headers with generous top-padding to let the "curation" breathe. +- **Body & Labels**: `body-lg` (1rem) is our standard for readability. Use `label-md` for metadata, ensuring high contrast using the `on-surface-variant` token. + +The hierarchy is designed to be **Editorial First**: large headings should feel like magazine titles, while body text provides the "curator's notes" in a clean, legible block. + +--- + +## 4. Elevation & Depth +In this design system, depth is felt, not seen. We favor **Tonal Layering** over structural shadows. + +### The Layering Principle +Depth is achieved by "stacking" the surface-container tiers. For instance, a floating action card should use `surface-container-lowest` to pop against a `surface-dim` background. + +### Ambient Shadows +When a physical "lift" is required (e.g., a floating bottom sheet): +- **Diffusion:** Shadows must be extra-diffused (Blur: 20px-40px). +- **Opacity:** Keep opacity between 4% and 8%. +- **Tinting:** Never use pure black. Tint the shadow with `on-surface` (`#201B13`) to mimic natural, ambient light reflecting off the heritage wood tones. + +### The "Ghost Border" Fallback +If accessibility requires a container edge, use a **Ghost Border**: the `outline-variant` token at 15% opacity. Standard 100% opaque borders are strictly forbidden. + +--- + +## 5. Components + +### Buttons +- **Primary**: `primary` background with `on-primary` text. Apply a `xl` (0.75rem) roundedness for a modern, tactile feel. +- **Secondary**: `secondary-container` (`#FEBE3B`) with `on-secondary-container`. This acts as a "warm" alternative for secondary actions. +- **Glass Variant**: For buttons sitting on imagery, use a semi-transparent `surface` with a heavy blur. + +### Cards & Lists +- **No Dividers:** Forbid the use of horizontal rules. Separate list items using the **Spacing Scale** (e.g., `spacing-4`) or by alternating between `surface` and `surface-container-low`. +- **Imagery**: Cards should feature high-quality photography that bleeds to the top and sides, emphasizing the "Gallery" aesthetic. + +### Chips & Inputs +- **Chips**: Use `surface-container-high` for unselected states and `tertiary` for selected states. +- **Input Fields**: Use a "minimalist-underlined" or "soft-filled" style. Forbid the heavy "boxed" input. The background should be `surface-container-lowest`. + +### Signature Component: The "Heritage Overlay" +A specific component for this system: An image container with a `tertiary-container` (`#A56526`) accent tab in the corner, holding a `label-sm` tag. This mimics the labeling of artifacts in a gallery. + +--- + +## 6. Do's and Don'ts + +### Do +- **Do** use intentional white space. If you think a section needs more room, double the spacing. +- **Do** overlap elements. Let a product image break the container of a card to create 3D interest. +- **Do** use the `tertiary` wood-tones for subtle accents like icons or small labels to tie into the "Heritage" material. + +### Don't +- **Don't** use 1px solid black or grey borders. This immediately destroys the "Modern Gallery" feel. +- **Don't** use standard drop shadows. If it looks like a "box shadow," it’s too heavy. +- **Don't** crowd the layout. If the user feels overwhelmed, the "Curator" has failed. +- **Don't** use pure black (`#000000`) for text. Use `on-surface` (`#201B13`) to keep the typography feeling organic and soft. \ No newline at end of file diff --git a/lib/screens/branches_screen.dart b/lib/screens/branches_screen.dart index 4106010..9b2a0d1 100644 --- a/lib/screens/branches_screen.dart +++ b/lib/screens/branches_screen.dart @@ -86,13 +86,7 @@ class _BranchesScreenState extends State { decoration: BoxDecoration( color: AppTheme.surfaceContainerLow, borderRadius: BorderRadius.circular(16), - boxShadow: [ - BoxShadow( - color: AppTheme.onSurface.withOpacity(0.04), - blurRadius: 16, - offset: const Offset(0, 4), - ) - ] + // Spec rules: "Don't use standard drop shadows" ), child: ListTile( contentPadding: const EdgeInsets.all(16), diff --git a/lib/screens/login_screen.dart b/lib/screens/login_screen.dart index ffdff9e..1c3a165 100644 --- a/lib/screens/login_screen.dart +++ b/lib/screens/login_screen.dart @@ -74,23 +74,30 @@ class _LoginScreenState extends State { return Scaffold( body: SafeArea( child: SingleChildScrollView( - padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 48.0), + padding: const EdgeInsets.only( + left: 32.0, + right: 32.0, + top: 80.0, + bottom: 48.0, + ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const SizedBox(height: 64), Text( 'Mie Mapan\nMembership', // Editorial high-end entry - style: Theme.of( - context, - ).textTheme.displayMedium?.copyWith(color: AppTheme.primary), + style: Theme.of(context).textTheme.displayLarge?.copyWith( + color: AppTheme.primary, + letterSpacing: -1.0, // High-fashion compact look + fontSize: 48, + ), ), const SizedBox(height: 12), Text( 'Sign in to access your culinary loyalty tier and discover exclusive offers.', style: Theme.of(context).textTheme.bodyLarge, ), - const SizedBox(height: 56), + const SizedBox(height: 80), TextField( controller: _usernameController, decoration: const InputDecoration( diff --git a/lib/screens/loyalty_dashboard.dart b/lib/screens/loyalty_dashboard.dart index 44aef99..9961e51 100644 --- a/lib/screens/loyalty_dashboard.dart +++ b/lib/screens/loyalty_dashboard.dart @@ -66,23 +66,16 @@ class _LoyaltyDashboardState extends State { ), ) : ListView.builder( - padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 32.0), + padding: const EdgeInsets.symmetric(horizontal: 32.0, vertical: 40.0), itemCount: _loyaltyCards.length, itemBuilder: (context, index) { final card = _loyaltyCards[index]; return Container( - margin: const EdgeInsets.only(bottom: 32), - padding: const EdgeInsets.all(24), + margin: const EdgeInsets.only(bottom: 40), + padding: const EdgeInsets.all(32), decoration: BoxDecoration( - color: AppTheme.surfaceContainerLow, + color: AppTheme.surfaceContainerHighest, // Soft Lift without shadow borderRadius: BorderRadius.circular(24), - boxShadow: [ - BoxShadow( - color: AppTheme.onSurface.withOpacity(0.06), - blurRadius: 24, - offset: const Offset(0, 8), - ) - ] ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -102,7 +95,7 @@ class _LoyaltyDashboardState extends State { padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( color: AppTheme.secondaryContainer, - borderRadius: BorderRadius.circular(24), + borderRadius: BorderRadius.circular(1000), // full explicit roundedness ), child: Text( 'Gold Member', diff --git a/lib/theme/app_theme.dart b/lib/theme/app_theme.dart index 3225da3..7b06a44 100644 --- a/lib/theme/app_theme.dart +++ b/lib/theme/app_theme.dart @@ -2,22 +2,23 @@ import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; class AppTheme { - // Mapan Core Tokens - static const Color primary = Color(0xFFB20000); - static const Color primaryContainer = Color(0xFFE00101); - static const Color secondary = Color(0xFF825500); - static const Color secondaryContainer = Color(0xFFFEB23D); - static const Color onSecondaryContainer = Color(0xFF6E4700); - + // Mapan Core Tokens (Heritage Gallery) + static const Color primary = Color(0xFFE1251B); + static const Color primaryContainer = Color(0xFFBB0004); + static const Color secondary = Color(0xFFCA8342); + static const Color secondaryContainer = Color(0xFFFFBF3C); + static const Color onSecondaryContainer = Color(0xFFEADFD2); + // Surface Hierarchy - static const Color surface = Color(0xFFFCF9F8); - static const Color surfaceContainer = Color(0xFFF0EDED); - static const Color surfaceContainerLow = Color(0xFFF6F3F2); - static const Color surfaceContainerHighest = Color(0xFFE5E2E1); - + static const Color surface = Color(0xFFFFF8F3); + static const Color surfaceContainer = Color(0xFFF7ECDF); + static const Color surfaceContainerLow = Color(0xFFFDF2E5); + static const Color surfaceContainerLowest = Colors.white; + static const Color surfaceContainerHighest = Color(0xFFECE1D4); + // Text & On-Colors - static const Color onSurface = Color(0xFF1C1B1B); - static const Color onSurfaceVariant = Color(0xFF5E3F3A); + static const Color onSurface = Color(0xFF201B13); + static const Color onSurfaceVariant = Color(0xFF5D3F3B); static const Color onPrimary = Colors.white; /// The Signature 135-degree CTA Gradient for main buttons. @@ -46,21 +47,45 @@ class AppTheme { error: Color(0xFFBA1A1A), ), textTheme: baseTheme.textTheme.copyWith( - displayLarge: GoogleFonts.plusJakartaSans(color: onSurface, fontWeight: FontWeight.bold, letterSpacing: -0.5), - displayMedium: GoogleFonts.plusJakartaSans(color: onSurface, fontWeight: FontWeight.bold), - displaySmall: GoogleFonts.plusJakartaSans(color: onSurface, fontWeight: FontWeight.bold), - headlineMedium: GoogleFonts.plusJakartaSans(color: onSurface, fontWeight: FontWeight.bold), - titleLarge: GoogleFonts.beVietnamPro(color: onSurface, fontWeight: FontWeight.w600), - titleMedium: GoogleFonts.beVietnamPro(color: onSurface, fontWeight: FontWeight.w600), - titleSmall: GoogleFonts.beVietnamPro(color: onSurface, fontWeight: FontWeight.w500), - bodyLarge: GoogleFonts.beVietnamPro(color: onSurfaceVariant), - bodyMedium: GoogleFonts.beVietnamPro(color: onSurfaceVariant), - bodySmall: GoogleFonts.beVietnamPro(color: onSurfaceVariant), - labelLarge: GoogleFonts.beVietnamPro(color: onSurfaceVariant), + displayLarge: GoogleFonts.plusJakartaSans( + color: onSurface, + fontWeight: FontWeight.bold, + letterSpacing: -0.5, + ), + displayMedium: GoogleFonts.plusJakartaSans( + color: onSurface, + fontWeight: FontWeight.bold, + ), + displaySmall: GoogleFonts.plusJakartaSans( + color: onSurface, + fontWeight: FontWeight.bold, + ), + headlineMedium: GoogleFonts.plusJakartaSans( + color: onSurface, + fontWeight: FontWeight.bold, + ), + titleLarge: GoogleFonts.plusJakartaSans( + color: onSurface, + fontWeight: FontWeight.w600, + ), + titleMedium: GoogleFonts.plusJakartaSans( + color: onSurface, + fontWeight: FontWeight.w600, + ), + titleSmall: GoogleFonts.plusJakartaSans( + color: onSurface, + fontWeight: FontWeight.w500, + ), + bodyLarge: GoogleFonts.plusJakartaSans(color: onSurfaceVariant), + bodyMedium: GoogleFonts.plusJakartaSans(color: onSurfaceVariant), + bodySmall: GoogleFonts.plusJakartaSans(color: onSurfaceVariant), + labelLarge: GoogleFonts.plusJakartaSans(color: onSurfaceVariant), ), elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), foregroundColor: onPrimary, backgroundColor: primaryContainer, // Fallback if no gradient is used elevation: 0, @@ -74,19 +99,19 @@ class AppTheme { ), inputDecorationTheme: InputDecorationTheme( filled: true, - fillColor: surfaceContainerHighest, - // Using "Ghost Border" logic at 15% opacity - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: const BorderSide(color: Color(0x26946E68)), + fillColor: surfaceContainerLowest, // Spec: soft filled background + contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), + border: const UnderlineInputBorder( + borderSide: BorderSide.none, // Minimum/No outline by default + borderRadius: BorderRadius.only(topLeft: Radius.circular(8), topRight: Radius.circular(8)), ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: const BorderSide(color: Color(0x26946E68)), + enabledBorder: const UnderlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.only(topLeft: Radius.circular(8), topRight: Radius.circular(8)), ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide(color: primary.withOpacity(0.4), width: 2), + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide(color: primary.withOpacity(0.5), width: 2), + borderRadius: const BorderRadius.only(topLeft: Radius.circular(8), topRight: Radius.circular(8)), ), labelStyle: const TextStyle(color: onSurfaceVariant), ), @@ -96,9 +121,9 @@ class AppTheme { elevation: 0, surfaceTintColor: Colors.transparent, titleTextStyle: GoogleFonts.plusJakartaSans( - color: onSurface, - fontSize: 20, - fontWeight: FontWeight.bold + color: onSurface, + fontSize: 20, + fontWeight: FontWeight.bold, ), ), );