diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md index 1a0e013..37af450 100644 --- a/IMPLEMENTATION_PLAN.md +++ b/IMPLEMENTATION_PLAN.md @@ -32,14 +32,14 @@ cd pathways_app && timeout 60 python -m reflex run 2>&1 | head -30 ## Phase 1: Foundation ### 1.1 Design Tokens Module -- [ ] Create `pathways_app/styles.py` with design token classes: +- [x] Create `pathways_app/styles.py` with design token classes: - `Colors` class with all palette colors as constants - `Typography` class with font sizes, weights - `Spacing` class with spacing scale - `Shadows` class with shadow values - `Radii` class with border radius values -- [ ] Create helper functions for common style patterns (e.g., `card_style()`, `button_primary_style()`) -- [ ] Verify imports work: `from pathways_app.styles import Colors, Spacing` +- [x] Create helper functions for common style patterns (e.g., `card_style()`, `button_primary_style()`) +- [x] Verify imports work: `from pathways_app.styles import Colors, Spacing` ### 1.2 App Skeleton - [ ] Create `pathways_app/app_v2.py` with basic Reflex app structure diff --git a/pathways_app/styles.py b/pathways_app/styles.py new file mode 100644 index 0000000..890c6d2 --- /dev/null +++ b/pathways_app/styles.py @@ -0,0 +1,408 @@ +""" +Design tokens and style helpers for HCD Analysis v2. + +All visual styling should use these tokens for consistency. +Import: from pathways_app.styles import Colors, Spacing, Typography, etc. +""" + + +class Colors: + """Color palette from DESIGN_SYSTEM.md""" + + # Primary Blues (NHS-inspired, modernized) + HERITAGE_BLUE = "#003087" # Deep headers, authoritative accents + PRIMARY = "#0066CC" # Main actions, links, focus states + VIBRANT = "#1E88E5" # Highlights, hover states, chart primary + SKY = "#4FC3F7" # Accents, progress bars, secondary elements + PALE = "#E3F2FD" # Subtle backgrounds, card tints + + # Neutrals (warm-tinted for clinical warmth) + SLATE_900 = "#1E293B" # Primary text + SLATE_700 = "#334155" # Secondary text + SLATE_500 = "#64748B" # Muted text, placeholders + SLATE_300 = "#CBD5E1" # Borders, dividers + SLATE_100 = "#F1F5F9" # Card backgrounds, hover states + WHITE = "#FFFFFF" # Page background + + # Semantic Colors + SUCCESS = "#059669" # Positive states, confirmations + WARNING = "#D97706" # Caution states, alerts + ERROR = "#DC2626" # Error states, destructive actions + INFO = "#0284C7" # Informational (matches primary family) + + # Chart Palette + CHART_SERIES = ["#003087", "#0066CC", "#1E88E5", "#4FC3F7", "#90CAF9"] + CHART_CATEGORICAL = ["#0066CC", "#059669", "#D97706", "#8B5CF6", "#EC4899"] + + +class Typography: + """Typography tokens from DESIGN_SYSTEM.md""" + + # Font families + FONT_FAMILY = "Inter, system-ui, -apple-system, sans-serif" + FONT_MONO = "JetBrains Mono, monospace" + + # Display: Page titles + DISPLAY_SIZE = "32px" + DISPLAY_WEIGHT = "700" + DISPLAY_TRACKING = "-0.02em" + DISPLAY_LINE_HEIGHT = "1.2" + + # Heading 1: Section headers + H1_SIZE = "24px" + H1_WEIGHT = "600" + H1_TRACKING = "-0.01em" + H1_LINE_HEIGHT = "1.3" + + # Heading 2: Card titles + H2_SIZE = "20px" + H2_WEIGHT = "600" + H2_TRACKING = "normal" + H2_LINE_HEIGHT = "1.4" + + # Heading 3: Subsections + H3_SIZE = "16px" + H3_WEIGHT = "600" + H3_TRACKING = "normal" + H3_LINE_HEIGHT = "1.4" + + # Body: Default text + BODY_SIZE = "14px" + BODY_WEIGHT = "400" + BODY_LINE_HEIGHT = "1.5" + + # Body Small: Secondary info + BODY_SMALL_SIZE = "13px" + BODY_SMALL_WEIGHT = "400" + BODY_SMALL_LINE_HEIGHT = "1.5" + + # Caption: Labels, metadata + CAPTION_SIZE = "12px" + CAPTION_WEIGHT = "500" + CAPTION_LINE_HEIGHT = "1.4" + + # Mono: Data values, codes + MONO_SIZE = "13px" + MONO_WEIGHT = "400" + MONO_LINE_HEIGHT = "1.5" + + +class Spacing: + """Spacing scale from DESIGN_SYSTEM.md""" + + XS = "4px" # Tight internal padding + SM = "8px" # Between related elements + MD = "12px" # Standard gaps + LG = "16px" # Section padding + XL = "24px" # Card padding + XXL = "32px" # Major section gaps + XXXL = "48px" # Page margins + + +class Radii: + """Border radius values from DESIGN_SYSTEM.md""" + + SM = "4px" # Small elements, inputs + MD = "8px" # Buttons, small cards + LG = "12px" # Cards, modals + XL = "16px" # Large containers + FULL = "9999px" # Pills, avatars + + +class Shadows: + """Shadow values from DESIGN_SYSTEM.md""" + + SM = "0 1px 2px rgba(0,0,0,0.05)" # Subtle elevation + MD = "0 1px 3px rgba(0,0,0,0.08)" # Cards at rest + LG = "0 4px 6px rgba(0,0,0,0.1)" # Cards on hover, dropdowns + XL = "0 10px 15px rgba(0,0,0,0.1)" # Modals, popovers + + +class Transitions: + """Transition values from DESIGN_SYSTEM.md""" + + COLOR = "150ms ease-out" + TRANSFORM = "200ms ease-out" + SHADOW = "200ms ease-out" + OPACITY = "200ms ease-in-out" + + +# ============================================================================== +# Helper functions for common style patterns +# ============================================================================== + +def card_style(hoverable: bool = False) -> dict: + """ + Card styling following DESIGN_SYSTEM.md specifications. + + - Background: White + - Border: 1px Slate 300 + - Border radius: lg (12px) + - Padding: xl (24px) + - Shadow: md at rest, lg on hover + """ + base_style = { + "background_color": Colors.WHITE, + "border": f"1px solid {Colors.SLATE_300}", + "border_radius": Radii.LG, + "padding": Spacing.XL, + "box_shadow": Shadows.MD, + } + + if hoverable: + base_style.update({ + "transition": f"box-shadow {Transitions.SHADOW}, transform {Transitions.TRANSFORM}", + "_hover": { + "box_shadow": Shadows.LG, + "transform": "translateY(-2px)", + } + }) + + return base_style + + +def button_primary_style() -> dict: + """ + Primary button styling following DESIGN_SYSTEM.md specifications. + + - Background: Primary Blue + - Text: White + - Border radius: md (8px) + - Padding: 10px 20px + - Hover: Vibrant Blue background, slight scale (1.02) + """ + return { + "background_color": Colors.PRIMARY, + "color": Colors.WHITE, + "border_radius": Radii.MD, + "padding": "10px 20px", + "font_weight": "500", + "font_size": Typography.BODY_SIZE, + "cursor": "pointer", + "border": "none", + "transition": f"background-color {Transitions.COLOR}, transform {Transitions.TRANSFORM}", + "_hover": { + "background_color": Colors.VIBRANT, + "transform": "scale(1.02)", + } + } + + +def button_secondary_style() -> dict: + """ + Secondary button styling following DESIGN_SYSTEM.md specifications. + + - Background: White + - Border: 1px Primary Blue + - Text: Primary Blue + - Hover: Pale Blue background + """ + return { + "background_color": Colors.WHITE, + "color": Colors.PRIMARY, + "border": f"1px solid {Colors.PRIMARY}", + "border_radius": Radii.MD, + "padding": "10px 20px", + "font_weight": "500", + "font_size": Typography.BODY_SIZE, + "cursor": "pointer", + "transition": f"background-color {Transitions.COLOR}", + "_hover": { + "background_color": Colors.PALE, + } + } + + +def button_ghost_style() -> dict: + """ + Ghost button styling following DESIGN_SYSTEM.md specifications. + + - Background: transparent + - Text: Primary Blue + - Hover: Pale Blue background + """ + return { + "background_color": "transparent", + "color": Colors.PRIMARY, + "border": "none", + "border_radius": Radii.MD, + "padding": "10px 20px", + "font_weight": "500", + "font_size": Typography.BODY_SIZE, + "cursor": "pointer", + "transition": f"background-color {Transitions.COLOR}", + "_hover": { + "background_color": Colors.PALE, + } + } + + +def input_style() -> dict: + """ + Form input styling following DESIGN_SYSTEM.md specifications. + + - Height: 40px + - Border: 1px Slate 300 + - Border radius: md (8px) + - Focus: 2px Primary Blue ring + - Placeholder: Slate 500 + """ + return { + "height": "40px", + "border": f"1px solid {Colors.SLATE_300}", + "border_radius": Radii.MD, + "padding": f"0 {Spacing.MD}", + "font_size": Typography.BODY_SIZE, + "font_family": Typography.FONT_FAMILY, + "color": Colors.SLATE_900, + "background_color": Colors.WHITE, + "transition": f"border-color {Transitions.COLOR}, box-shadow {Transitions.COLOR}", + "_placeholder": { + "color": Colors.SLATE_500, + }, + "_focus": { + "outline": "none", + "border_color": Colors.PRIMARY, + "box_shadow": f"0 0 0 2px {Colors.PALE}", + } + } + + +def kpi_card_style() -> dict: + """ + KPI card styling following DESIGN_SYSTEM.md specifications. + + - Large mono number: 32-48px, Slate 900 + - Label: Caption size, Slate 500 + - Background: White or Pale Blue tint + """ + return { + "background_color": Colors.WHITE, + "border": f"1px solid {Colors.SLATE_300}", + "border_radius": Radii.LG, + "padding": Spacing.XL, + "box_shadow": Shadows.SM, + "text_align": "center", + } + + +def kpi_value_style() -> dict: + """Style for the large number in a KPI card.""" + return { + "font_family": Typography.FONT_MONO, + "font_size": "32px", + "font_weight": "600", + "color": Colors.SLATE_900, + "line_height": "1.2", + } + + +def kpi_label_style() -> dict: + """Style for the label in a KPI card.""" + return { + "font_size": Typography.CAPTION_SIZE, + "font_weight": Typography.CAPTION_WEIGHT, + "color": Colors.SLATE_500, + "margin_top": Spacing.SM, + } + + +def text_display() -> dict: + """Display text style for page titles.""" + return { + "font_size": Typography.DISPLAY_SIZE, + "font_weight": Typography.DISPLAY_WEIGHT, + "letter_spacing": Typography.DISPLAY_TRACKING, + "line_height": Typography.DISPLAY_LINE_HEIGHT, + "color": Colors.SLATE_900, + "font_family": Typography.FONT_FAMILY, + } + + +def text_h1() -> dict: + """Heading 1 style for section headers.""" + return { + "font_size": Typography.H1_SIZE, + "font_weight": Typography.H1_WEIGHT, + "letter_spacing": Typography.H1_TRACKING, + "line_height": Typography.H1_LINE_HEIGHT, + "color": Colors.SLATE_900, + "font_family": Typography.FONT_FAMILY, + } + + +def text_h2() -> dict: + """Heading 2 style for card titles.""" + return { + "font_size": Typography.H2_SIZE, + "font_weight": Typography.H2_WEIGHT, + "letter_spacing": Typography.H2_TRACKING, + "line_height": Typography.H2_LINE_HEIGHT, + "color": Colors.SLATE_900, + "font_family": Typography.FONT_FAMILY, + } + + +def text_h3() -> dict: + """Heading 3 style for subsections.""" + return { + "font_size": Typography.H3_SIZE, + "font_weight": Typography.H3_WEIGHT, + "letter_spacing": Typography.H3_TRACKING, + "line_height": Typography.H3_LINE_HEIGHT, + "color": Colors.SLATE_900, + "font_family": Typography.FONT_FAMILY, + } + + +def text_body() -> dict: + """Default body text style.""" + return { + "font_size": Typography.BODY_SIZE, + "font_weight": Typography.BODY_WEIGHT, + "line_height": Typography.BODY_LINE_HEIGHT, + "color": Colors.SLATE_900, + "font_family": Typography.FONT_FAMILY, + } + + +def text_body_small() -> dict: + """Secondary/small body text style.""" + return { + "font_size": Typography.BODY_SMALL_SIZE, + "font_weight": Typography.BODY_SMALL_WEIGHT, + "line_height": Typography.BODY_SMALL_LINE_HEIGHT, + "color": Colors.SLATE_700, + "font_family": Typography.FONT_FAMILY, + } + + +def text_caption() -> dict: + """Caption style for labels and metadata.""" + return { + "font_size": Typography.CAPTION_SIZE, + "font_weight": Typography.CAPTION_WEIGHT, + "line_height": Typography.CAPTION_LINE_HEIGHT, + "color": Colors.SLATE_500, + "font_family": Typography.FONT_FAMILY, + } + + +def text_mono() -> dict: + """Monospace text style for data values and codes.""" + return { + "font_size": Typography.MONO_SIZE, + "font_weight": Typography.MONO_WEIGHT, + "line_height": Typography.MONO_LINE_HEIGHT, + "color": Colors.SLATE_900, + "font_family": Typography.FONT_MONO, + } + + +# ============================================================================== +# Layout constants +# ============================================================================== + +TOP_BAR_HEIGHT = "64px" +PAGE_MAX_WIDTH = "1600px" +PAGE_PADDING = Spacing.XXXL