feat: Implement "Heritage Gallery Editorial" design system with updated app theme and new design documentation.
This commit is contained in:
parent
544439d571
commit
8431a8dc13
93
DESIGN.md
Normal file
93
DESIGN.md
Normal file
@ -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.
|
||||||
@ -86,13 +86,7 @@ class _BranchesScreenState extends State<BranchesScreen> {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppTheme.surfaceContainerLow,
|
color: AppTheme.surfaceContainerLow,
|
||||||
borderRadius: BorderRadius.circular(16),
|
borderRadius: BorderRadius.circular(16),
|
||||||
boxShadow: [
|
// Spec rules: "Don't use standard drop shadows"
|
||||||
BoxShadow(
|
|
||||||
color: AppTheme.onSurface.withOpacity(0.04),
|
|
||||||
blurRadius: 16,
|
|
||||||
offset: const Offset(0, 4),
|
|
||||||
)
|
|
||||||
]
|
|
||||||
),
|
),
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
contentPadding: const EdgeInsets.all(16),
|
contentPadding: const EdgeInsets.all(16),
|
||||||
|
|||||||
@ -74,23 +74,30 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: SingleChildScrollView(
|
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(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(height: 64),
|
const SizedBox(height: 64),
|
||||||
Text(
|
Text(
|
||||||
'Mie Mapan\nMembership', // Editorial high-end entry
|
'Mie Mapan\nMembership', // Editorial high-end entry
|
||||||
style: Theme.of(
|
style: Theme.of(context).textTheme.displayLarge?.copyWith(
|
||||||
context,
|
color: AppTheme.primary,
|
||||||
).textTheme.displayMedium?.copyWith(color: AppTheme.primary),
|
letterSpacing: -1.0, // High-fashion compact look
|
||||||
|
fontSize: 48,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
Text(
|
Text(
|
||||||
'Sign in to access your culinary loyalty tier and discover exclusive offers.',
|
'Sign in to access your culinary loyalty tier and discover exclusive offers.',
|
||||||
style: Theme.of(context).textTheme.bodyLarge,
|
style: Theme.of(context).textTheme.bodyLarge,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 56),
|
const SizedBox(height: 80),
|
||||||
TextField(
|
TextField(
|
||||||
controller: _usernameController,
|
controller: _usernameController,
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
|
|||||||
@ -66,23 +66,16 @@ class _LoyaltyDashboardState extends State<LoyaltyDashboard> {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
: ListView.builder(
|
: 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,
|
itemCount: _loyaltyCards.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final card = _loyaltyCards[index];
|
final card = _loyaltyCards[index];
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.only(bottom: 32),
|
margin: const EdgeInsets.only(bottom: 40),
|
||||||
padding: const EdgeInsets.all(24),
|
padding: const EdgeInsets.all(32),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppTheme.surfaceContainerLow,
|
color: AppTheme.surfaceContainerHighest, // Soft Lift without shadow
|
||||||
borderRadius: BorderRadius.circular(24),
|
borderRadius: BorderRadius.circular(24),
|
||||||
boxShadow: [
|
|
||||||
BoxShadow(
|
|
||||||
color: AppTheme.onSurface.withOpacity(0.06),
|
|
||||||
blurRadius: 24,
|
|
||||||
offset: const Offset(0, 8),
|
|
||||||
)
|
|
||||||
]
|
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@ -102,7 +95,7 @@ class _LoyaltyDashboardState extends State<LoyaltyDashboard> {
|
|||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: AppTheme.secondaryContainer,
|
color: AppTheme.secondaryContainer,
|
||||||
borderRadius: BorderRadius.circular(24),
|
borderRadius: BorderRadius.circular(1000), // full explicit roundedness
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Gold Member',
|
'Gold Member',
|
||||||
|
|||||||
@ -2,22 +2,23 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
|
||||||
class AppTheme {
|
class AppTheme {
|
||||||
// Mapan Core Tokens
|
// Mapan Core Tokens (Heritage Gallery)
|
||||||
static const Color primary = Color(0xFFB20000);
|
static const Color primary = Color(0xFFE1251B);
|
||||||
static const Color primaryContainer = Color(0xFFE00101);
|
static const Color primaryContainer = Color(0xFFBB0004);
|
||||||
static const Color secondary = Color(0xFF825500);
|
static const Color secondary = Color(0xFFCA8342);
|
||||||
static const Color secondaryContainer = Color(0xFFFEB23D);
|
static const Color secondaryContainer = Color(0xFFFFBF3C);
|
||||||
static const Color onSecondaryContainer = Color(0xFF6E4700);
|
static const Color onSecondaryContainer = Color(0xFFEADFD2);
|
||||||
|
|
||||||
// Surface Hierarchy
|
// Surface Hierarchy
|
||||||
static const Color surface = Color(0xFFFCF9F8);
|
static const Color surface = Color(0xFFFFF8F3);
|
||||||
static const Color surfaceContainer = Color(0xFFF0EDED);
|
static const Color surfaceContainer = Color(0xFFF7ECDF);
|
||||||
static const Color surfaceContainerLow = Color(0xFFF6F3F2);
|
static const Color surfaceContainerLow = Color(0xFFFDF2E5);
|
||||||
static const Color surfaceContainerHighest = Color(0xFFE5E2E1);
|
static const Color surfaceContainerLowest = Colors.white;
|
||||||
|
static const Color surfaceContainerHighest = Color(0xFFECE1D4);
|
||||||
|
|
||||||
// Text & On-Colors
|
// Text & On-Colors
|
||||||
static const Color onSurface = Color(0xFF1C1B1B);
|
static const Color onSurface = Color(0xFF201B13);
|
||||||
static const Color onSurfaceVariant = Color(0xFF5E3F3A);
|
static const Color onSurfaceVariant = Color(0xFF5D3F3B);
|
||||||
static const Color onPrimary = Colors.white;
|
static const Color onPrimary = Colors.white;
|
||||||
|
|
||||||
/// The Signature 135-degree CTA Gradient for main buttons.
|
/// The Signature 135-degree CTA Gradient for main buttons.
|
||||||
@ -46,21 +47,45 @@ class AppTheme {
|
|||||||
error: Color(0xFFBA1A1A),
|
error: Color(0xFFBA1A1A),
|
||||||
),
|
),
|
||||||
textTheme: baseTheme.textTheme.copyWith(
|
textTheme: baseTheme.textTheme.copyWith(
|
||||||
displayLarge: GoogleFonts.plusJakartaSans(color: onSurface, fontWeight: FontWeight.bold, letterSpacing: -0.5),
|
displayLarge: GoogleFonts.plusJakartaSans(
|
||||||
displayMedium: GoogleFonts.plusJakartaSans(color: onSurface, fontWeight: FontWeight.bold),
|
color: onSurface,
|
||||||
displaySmall: GoogleFonts.plusJakartaSans(color: onSurface, fontWeight: FontWeight.bold),
|
fontWeight: FontWeight.bold,
|
||||||
headlineMedium: GoogleFonts.plusJakartaSans(color: onSurface, fontWeight: FontWeight.bold),
|
letterSpacing: -0.5,
|
||||||
titleLarge: GoogleFonts.beVietnamPro(color: onSurface, fontWeight: FontWeight.w600),
|
),
|
||||||
titleMedium: GoogleFonts.beVietnamPro(color: onSurface, fontWeight: FontWeight.w600),
|
displayMedium: GoogleFonts.plusJakartaSans(
|
||||||
titleSmall: GoogleFonts.beVietnamPro(color: onSurface, fontWeight: FontWeight.w500),
|
color: onSurface,
|
||||||
bodyLarge: GoogleFonts.beVietnamPro(color: onSurfaceVariant),
|
fontWeight: FontWeight.bold,
|
||||||
bodyMedium: GoogleFonts.beVietnamPro(color: onSurfaceVariant),
|
),
|
||||||
bodySmall: GoogleFonts.beVietnamPro(color: onSurfaceVariant),
|
displaySmall: GoogleFonts.plusJakartaSans(
|
||||||
labelLarge: GoogleFonts.beVietnamPro(color: onSurfaceVariant),
|
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(
|
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
),
|
||||||
foregroundColor: onPrimary,
|
foregroundColor: onPrimary,
|
||||||
backgroundColor: primaryContainer, // Fallback if no gradient is used
|
backgroundColor: primaryContainer, // Fallback if no gradient is used
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
@ -74,19 +99,19 @@ class AppTheme {
|
|||||||
),
|
),
|
||||||
inputDecorationTheme: InputDecorationTheme(
|
inputDecorationTheme: InputDecorationTheme(
|
||||||
filled: true,
|
filled: true,
|
||||||
fillColor: surfaceContainerHighest,
|
fillColor: surfaceContainerLowest, // Spec: soft filled background
|
||||||
// Using "Ghost Border" logic at 15% opacity
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||||
border: OutlineInputBorder(
|
border: const UnderlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderSide: BorderSide.none, // Minimum/No outline by default
|
||||||
borderSide: const BorderSide(color: Color(0x26946E68)),
|
borderRadius: BorderRadius.only(topLeft: Radius.circular(8), topRight: Radius.circular(8)),
|
||||||
),
|
),
|
||||||
enabledBorder: OutlineInputBorder(
|
enabledBorder: const UnderlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderSide: BorderSide.none,
|
||||||
borderSide: const BorderSide(color: Color(0x26946E68)),
|
borderRadius: BorderRadius.only(topLeft: Radius.circular(8), topRight: Radius.circular(8)),
|
||||||
),
|
),
|
||||||
focusedBorder: OutlineInputBorder(
|
focusedBorder: UnderlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderSide: BorderSide(color: primary.withOpacity(0.5), width: 2),
|
||||||
borderSide: BorderSide(color: primary.withOpacity(0.4), width: 2),
|
borderRadius: const BorderRadius.only(topLeft: Radius.circular(8), topRight: Radius.circular(8)),
|
||||||
),
|
),
|
||||||
labelStyle: const TextStyle(color: onSurfaceVariant),
|
labelStyle: const TextStyle(color: onSurfaceVariant),
|
||||||
),
|
),
|
||||||
@ -96,9 +121,9 @@ class AppTheme {
|
|||||||
elevation: 0,
|
elevation: 0,
|
||||||
surfaceTintColor: Colors.transparent,
|
surfaceTintColor: Colors.transparent,
|
||||||
titleTextStyle: GoogleFonts.plusJakartaSans(
|
titleTextStyle: GoogleFonts.plusJakartaSans(
|
||||||
color: onSurface,
|
color: onSurface,
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: FontWeight.bold
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user