Generated by /plan-design-review on 2026-03-24
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.
#fafaf8instead of pure white. Near-black#1a1a1ainstead of#000.
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) |
: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-accenton--color-surface(#ffffff): 2.5:1 — same; use only for borders, icons, large headings ≥24px bold
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 */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
┌────────────────────────────────────────────────────────────┐
│ 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" |
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.
/* 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" |
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)
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"
Minimal and purposeful. Three specified interactions:
-
RSVP button confirmation — brief scale pulse on click:
transform: scale(0.95) → scale(1.0), 150ms ease-out. Signals the click was registered. -
FOMO counter update — number cross-fades when capacity changes while page is open: opacity 1 → 0 → 1, 200ms. Announces itself without being distracting.
-
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).
- Contrast: 4.5:1 minimum for body text. 3:1 for large text (≥24px or ≥18px bold). Gold accent
#c8a96eis 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-labelupdates 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"
- FOMO counter:
- 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.
- 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)