Skip to content

Latest commit

 

History

History
253 lines (200 loc) · 10.5 KB

File metadata and controls

253 lines (200 loc) · 10.5 KB

Design System — baashb Platform

Generated by /plan-design-review on 2026-03-24

Aesthetic Direction

Editorial minimal. The platform serves invitation-only social clubs — rowing alumni, book clubs, professional associations. The design should feel like a well-maintained membership directory, not a SaaS product.

Core principles:

  • Typography first. Strong typographic hierarchy is the primary design tool.
  • No decoration. No shadows, no gradients, no rounded bubble cards, no icon-in-circle patterns.
  • Thin borders, not cards. Event rows separated by 1px var(--color-border) lines, not drop-shadow cards.
  • One accent color. Used sparingly: active states, FOMO indicators, primary CTAs only.
  • Warm, not clinical. #fafaf8 instead of pure white. Near-black #1a1a1a instead of #000.

Typography

Font: Inter (Google Fonts, free, open source).

Note: Söhne (Klim Type Foundry) is the aspirational heading font for a future upgrade — it has slightly more warmth and editorial presence at display sizes. See TODOS.md. The CSS is structured so migration requires changing one custom property.

/* Base font stack */
--font-heading: 'Inter', system-ui, sans-serif;
--font-body:    'Inter', system-ui, sans-serif;

/* Future Söhne upgrade — swap this one line */
/* --font-heading: 'Söhne', 'Inter', system-ui, sans-serif; */

Scale:

Token Size Line height Weight Use
--text-4xl 32px 1.2 700 Page H1, club name
--text-3xl 24px 1.3 600 Section H2, event name
--text-2xl 20px 1.35 600 Card heading, form section
--text-xl 18px 1.4 500 H3, sidebar heading
--text-base 16px 1.6 400 Body text, event description
--text-sm 14px 1.5 400 Secondary info, captions
--text-xs 12px 1.4 500 Labels, status badges
--text-btn 14px 1 500 Button labels (letter-spacing: 0.01em)

Color System

:root {
  /* Base */
  --color-bg:            #fafaf8;  /* Warm off-white — never pure white */
  --color-surface:       #ffffff;  /* Modal / overlay backgrounds */
  --color-text:          #1a1a1a;  /* Near-black body text */
  --color-text-muted:    #6b7280;  /* Secondary text, captions, placeholders */
  --color-border:        #e5e5e0;  /* Thin separators, dividers */
  --color-border-focus:  #1a1a1a;  /* Focus ring on inputs */

  /* Accent (club-overridable) */
  --color-accent:        #c8a96e;  /* BAASH-B gold — clubs override via primary_color */
  --color-accent-hover:  #b8965a;  /* Darker on hover */
  --color-accent-text:   #1a1a1a;  /* Text on accent background */

  /* Semantic */
  --color-success:       #16a34a;  /* Confirmed RSVP, SSL active, keys connected */
  --color-success-bg:    #f0fdf4;  /* Success state background */
  --color-warning:       #f59e0b;  /* FOMO amber (≤20% spots remaining) */
  --color-warning-bg:    #fffbeb;  /* Warning state background */
  --color-danger:        #c0392b;  /* FOMO red (1 spot), errors, failed states */
  --color-danger-bg:     #fef2f2;  /* Error state background */
  --color-muted:         #9ca3af;  /* Disabled states, past events, read-only */
}

Multi-tenant override — injected in layout <head>:

<style>
  :root {
    --color-accent:       <%= current_club.primary_color || '#c8a96e' %>;
    --color-accent-hover: <%= darken(current_club.primary_color, 10) || '#b8965a' %>;
    --font-heading:       '<%= current_club.font_choice || 'Inter' %>', system-ui, sans-serif;
  }
</style>

Contrast notes:

  • --color-text (#1a1a1a) on --color-bg (#fafaf8): 18.8:1 ✅ (WCAG AAA)
  • --color-accent (#c8a96e) on --color-bg (#fafaf8): 2.6:1 — decorative use only, never for body text
  • --color-accent on --color-surface (#ffffff): 2.5:1 — same; use only for borders, icons, large headings ≥24px bold

Spacing

Base unit: 8px. All spacing is multiples of 8.

--space-1:   4px;   /* Tight inline gaps (icon-to-label) */
--space-2:   8px;   /* Compact lists, form field internal padding */
--space-3:   12px;  /* Tight card padding */
--space-4:   16px;  /* Standard card padding, form field gaps */
--space-5:   20px;  /* List item vertical padding */
--space-6:   24px;  /* Section padding, page gutters */
--space-8:   32px;  /* Between major components */
--space-10:  40px;  /* Between page sections */
--space-16:  64px;  /* Page-level vertical rhythm */

Layout

Max content width: 1100px, centered. Horizontal padding: 24px (desktop), 16px (mobile).

Breakpoints:

--bp-sm:  640px;   /* Mobile / Tablet threshold */
--bp-md:  768px;   /* Tablet */
--bp-lg:  1024px;  /* Desktop */
--bp-xl:  1280px;  /* Wide desktop */

Event list:

  • Mobile (<640px): Single-column, full-width rows
  • Tablet (640–1024px): Single-column, max 720px
  • Desktop (>1024px): Single-column, max 800px (editorial, not grid)

Event detail split:

  • Desktop: 60% main / 40% sidebar — sidebar position: sticky top 24px
  • Mobile: Stacked — sidebar content moves below main content; RSVP button becomes full-width sticky at viewport bottom

Organizer dashboard:

  • Horizontal stat row (inline text with · separators — NOT a grid of metric cards)
  • Tabbed content: Events / Members / Archive

Components

Event Row / Card

┌────────────────────────────────────────────────────────────┐
│  Spring Rowing Dinner                    3 spots remaining  │
│  Saturday Apr 12 · The Boathouse · $45                      │
│                                            [ Pay $45 ]      │
└────────────────────────────────────────────────────────────┘
  border-bottom: 1px var(--color-border)
  padding: 16px 0 (flush to container)
  No card shadow. No card background.

RSVP Button states:

State Style Label
Default (paid) bg: var(--color-accent), white text, radius: 2px "Pay $45"
Default (free) Same "RSVP"
Processing bg: var(--color-muted), no pointer, spinner "Processing…"
Confirmed outline, color: var(--color-success), checkmark "✓ Attending · Cancel"
Past event muted, no pointer "Attended"
Sold out text link "Contact organizers"

Organizer Stat Row

42 members · 3 upcoming events · $1,240 this year · 2 pending approvals
─────────────────────────────────────────────────────────────────────
font-size: var(--text-sm)
color: var(--color-text-muted)
separator: · (U+00B7 middot)
NOT a grid of metric cards.

FOMO Capacity Badge

/* Applied inline to the capacity indicator text */
.fomo-neutral  { color: var(--color-text-muted); }
.fomo-warning  { color: var(--color-warning); font-weight: 600; }
.fomo-danger   { color: var(--color-danger);  font-weight: 700; }
.fomo-soldout  { color: var(--color-muted); }
Threshold Class Text
>50% spots left — (hidden)
≤50% .fomo-neutral "12 spots remaining"
≤20% .fomo-warning "3 spots remaining"
1 spot .fomo-danger "1 spot remaining"
0 spots .fomo-soldout "Sold out"

Modal

max-width: 440px
No backdrop blur (editorial, not glass)
border: 1px var(--color-border)
padding: var(--space-6)
Single primary CTA + cancel link below (not two equal buttons)

Toast Notification

Position: top-right, fixed
Width: 320px
Slide in from right: translateX(340px) → translateX(0), 200ms ease-out
Auto-dismiss: 4s
No dismiss animation (just disappears)
role="status" aria-live="polite"

Motion

Minimal and purposeful. Three specified interactions:

  1. RSVP button confirmation — brief scale pulse on click: transform: scale(0.95) → scale(1.0), 150ms ease-out. Signals the click was registered.

  2. FOMO counter update — number cross-fades when capacity changes while page is open: opacity 1 → 0 → 1, 200ms. Announces itself without being distracting.

  3. Toast notification entrance — slides in from top-right: translateX(340px) → translateX(0), 200ms ease-out. Polled every 3s disappears without animation.

No scroll-linked animations. No page entrance animations. No hover scale/blur effects. No CSS transitions on color changes (use opacity transitions only where specified).


Accessibility

  • Contrast: 4.5:1 minimum for body text. 3:1 for large text (≥24px or ≥18px bold). Gold accent #c8a96e is decorative only — never used as text color on any background.
  • Touch targets: 44×44px minimum for all interactive elements. Increase tap area with padding if visual size is smaller.
  • Focus management: All modals trap focus (focus-trap-wasm or equivalent). On modal close, focus returns to the trigger element.
  • Keyboard navigation: All flows completable without mouse. RSVP flow (event list → event detail → pay button → Stripe) fully keyboard-navigable.
  • ARIA:
    • FOMO counter: aria-live="polite" region (announced when count changes while page is open)
    • RSVP button: aria-label updates on state change (e.g. aria-label="RSVP to Spring Rowing Dinner"aria-label="Cancel RSVP for Spring Rowing Dinner")
    • Toast notifications: role="status" aria-live="polite"
    • Processing spinner: aria-label="Processing payment" aria-busy="true"
  • Skip link: <a class="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4" href="#main-content">Skip to main content</a>
  • Reduced motion: Respect prefers-reduced-motion: reduce — disable all three specified animations for users who opt out.

What is NOT in this design system

  • Dark mode (deferred — no club has requested it)
  • Right-to-left layout (BAASH-B is English-only)
  • Print stylesheet (deferred)
  • Animated page transitions (out of scope — editorial, not app-like)
  • Icon library (use inline SVG for the handful of icons needed: checkmark, spinner, download arrow, external link)
  • Component library / Storybook (v1 uses plain Rails views + Stimulus; extract to components in v2 if needed)