Version: 2.1
Last Updated: 2026-01-08
Purpose: Ensure the Champion Portal feels warm, welcoming, and distinctly Belmont
Status: Living Document — Use this prominently for all visual refresh work
The Champion Portal is a community, not a corporate portal. Every design decision should reinforce that Champions are welcomed, valued, and connected.
❌ Avoid:
✅ Embrace:
This isn’t just a tool—it’s a place to belong. Design should:
Use Belmont elements as structure, not decoration:
The goal: Feel like a Belmont product without looking like a marketing brochure.
Per JOBS-TO-BE-DONE.md Job C10:
“When I put effort into being a Champion, I want to see that my contributions matter.”
Every feature should ask: How can we recognize the people who contribute?
These are non-negotiables for all new design work:
| # | Principle | What It Means |
|---|---|---|
| 1 | Warmth over sterility | Friendly colors, soft shadows, rounded corners, human language |
| 2 | Scannable layout | Clear visual hierarchy, obvious sections, breathing room |
| 3 | Visual differentiation | Each section has distinct character (icon, accent, tint) |
| 4 | Mobile-first polish | Touch-friendly, thumb-zone aware, responsive by default |
| 5 | Belmont identity with restraint | Brand colors anchor, but don’t overwhelm |
If a page feels cold or corporate, fix it in this order:
Use the Belmont digital palette for the portal UI (it’s explicitly intended for screens with better contrast).
| Color | Digital Hex | Tailwind Class | Usage |
|---|---|---|---|
| Belmont Blue | #001D54 |
bg-belmontblue, text-belmontblue |
Primary anchor — headers, nav, buttons, selected states, focus rings |
| Belmont Red | #B21029 |
bg-belmontred, text-belmontred |
Secondary emphasis — alerts, badges, destructive actions, rare highlights |
⚠️ Belmont Blue is the primary anchor color and should be the dominant brand signal.
Belmont Red is secondary and should show up as emphasis, not wallpaper.
💡 If the UI starts to feel “too blue,” fix it with warm neutrals and spacing — not by swapping Belmont Blue out.
| Color | Hex | Tailwind Class | Usage |
|---|---|---|---|
| Admissions Blue | #1D4289 |
bg-admissionsblue |
Alternative primary for variety |
| Fountain Blue | #2874AF |
bg-fountainblue |
Secondary buttons, hover states |
| Sky Blue | #6AB3E7 |
bg-skyblue |
Highlights, links, engagement indicators |
Use warm-leaning neutrals for surfaces so the portal feels hospitable:
| Purpose | Recommendation |
|---|---|
| Page backgrounds | Off-white / warm gray (avoid pure white everywhere) |
| Borders | Soft, low-contrast (not harsh lines) |
| Cards | Tinted surfaces or soft white with subtle shadow |
| Text | Dark gray (#3D3D3D), not pure black |
Avoid:
Assign each major area a single accent that shows up as:
Keep accents subtle so Belmont Blue remains “home base.”
<!-- Primary button (Belmont Blue) -->
<button class="bg-belmontblue hover:bg-fountainblue text-white px-4 py-2 rounded-lg">
Say Hello
</button>
<!-- Secondary button -->
<button class="bg-white border border-belmontblue text-belmontblue hover:bg-gray-50 px-4 py-2 rounded-lg">
Learn More
</button>
<!-- Destructive action (Belmont Red, sparingly) -->
<button class="bg-belmontred hover:bg-red-800 text-white px-4 py-2 rounded-lg">
Delete Account
</button>
<!-- Card with warmth -->
<div class="bg-white border border-gray-200 rounded-xl p-6 shadow-sm hover:shadow-md transition">
<!-- Content -->
</div>
Belmont’s primary web typefaces:
If licenses unavailable, Belmont approves these substitutes:
Currently configured in application.tailwind.css:
| Purpose | Font | Tailwind Class |
|---|---|---|
| Body text | Inter | font-sans (default) |
| Headings | Montserrat | font-sansalt |
| Rule | Why |
|---|---|
| Bigger default body text | Avoid tiny “admin UI” sizing — aim for 16px+ |
| Softer hierarchy | Fewer ALL CAPS labels, fewer ultra-bold headings |
| Comfortable line-height | For paragraph copy, empty states, instructions |
| Use serif sparingly | For quotes, “story” moments, short callouts — not dense UI text |
<!-- Page titles -->
<h1 class="font-sansalt text-3xl font-bold text-belmontblue">
Welcome, Sarah
</h1>
<!-- Section headers -->
<h2 class="font-sansalt text-xl font-semibold text-gray-800">
Champions in Nashville
</h2>
<!-- Body text (comfortable) -->
<p class="text-base text-gray-700 leading-relaxed">
Connect with fellow Bruins in your city.
</p>
<!-- Helper text -->
<p class="text-sm text-gray-500 leading-relaxed">
This helps other Champions find you.
</p>
Use a single icon set across the portal (consistent stroke, corner radius, visual weight).
Recommended: Heroicons — outline style
| Context | Icon | Usage |
|---|---|---|
| Contact/Message | envelope |
Contact button, messaging |
| Profile | user-circle |
Profile views, account |
| Directory | users |
Champion directory |
| Events | calendar |
Event listings |
| Location | map-pin |
City/location indicators |
| Search | magnifying-glass |
Directory search |
| Success | check-circle |
Confirmations |
| Activity | sparkles |
Recent activity, engagement |
| Settings | cog-6-tooth |
Account settings |
| Help | question-mark-circle |
FAQ, help pages |
<!-- Icon with text (button) -->
<button class="inline-flex items-center gap-2 bg-belmontblue text-white px-4 py-2 rounded-lg">
<%= heroicon "envelope", variant: :outline, options: { class: "w-5 h-5" } %>
Say Hello
</button>
<!-- Icon-only (with accessibility) -->
<button aria-label="View profile" class="p-2 hover:bg-gray-100 rounded-lg">
<%= heroicon "user-circle", variant: :outline, options: { class: "w-6 h-6 text-belmontblue" } %>
</button>
Avoid:
Sterile UIs often fail because everything is evenly tight. Instead:
| Pattern | Why |
|---|---|
| Generous vertical spacing between sections | Creates clear visual breaks |
| Tight spacing within components | Forms, tables stay scannable |
| 2–3 content zones per page | Not 7–10 little boxes |
Belmont’s homepage leans on:
In the portal, translate that into:
| Element | Warm Approach |
|---|---|
| Cards | Noticeably rounded corners (not barely rounded) |
| Shadows | Soft, subtle shadows with low opacity |
| Dividers | Lighter borders, fewer hard lines |
| Backgrounds | Tinted surfaces instead of stark white |
┌─────────────────────────────────────────────────────────────────────┐
│ [PAGE HEADER] │
│ Title + 1–2 line description + optional action button │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ [SECTION 1] ────────────────────────────────────────────── │
│ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │
│ │ Card │ │ Card │ │ Card │ │
│ └────────────────┘ └────────────────┘ └────────────────┘ │
│ │
│ [SECTION 2] ────────────────────────────────────────────── │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ Larger content block or list │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
Cards should feel like “inviting surfaces,” not “dashboard tiles.”
Card anatomy:
<!-- Standard card -->
<div class="bg-white rounded-xl border border-gray-200 p-6 shadow-sm hover:shadow-md transition">
<div class="flex items-start gap-3">
<%= heroicon "users", variant: :outline, options: { class: "w-6 h-6 text-belmontblue flex-shrink-0" } %>
<div>
<h3 class="font-semibold text-gray-900">Champions Near You</h3>
<p class="text-sm text-gray-600 mt-1">Connect with 23 Champions in Nashville</p>
</div>
</div>
</div>
<!-- Card with accent border -->
<div class="bg-white rounded-xl border-l-4 border-l-skyblue border border-gray-200 p-6">
<!-- Content -->
</div>
| Type | Color | Usage |
|---|---|---|
| Primary | Belmont Blue | Main actions (“Say Hello,” “Submit Event”) |
| Secondary | White + Blue border | Alternative actions (“Learn More,” “Cancel”) |
| Destructive | Belmont Red | Delete, remove — use sparingly |
| Ghost | Transparent | Subtle actions (“Edit,” “View All”) |
Friendly button rules:
<!-- Primary -->
<button class="bg-belmontblue hover:bg-fountainblue text-white px-5 py-2.5 rounded-lg font-medium">
Say Hello
</button>
<!-- Secondary -->
<button class="bg-white border border-gray-300 text-gray-700 hover:bg-gray-50 px-5 py-2.5 rounded-lg font-medium">
Cancel
</button>
<!-- Destructive (rare) -->
<button class="bg-belmontred hover:bg-red-800 text-white px-5 py-2.5 rounded-lg font-medium">
Delete
</button>
Make forms feel like guidance, not compliance:
| Pattern | Example |
|---|---|
| Always include helper text | “This helps other Champions find you” |
| Human validation | “That email doesn’t look right” vs “Invalid format” |
| Grouped fields | Labeled sections with whitespace |
| Progressive disclosure | Advanced fields behind “Add details” toggle |
<!-- Form field with helper text -->
<div class="space-y-1">
<label class="block text-sm font-medium text-gray-700">City</label>
<input type="text" class="w-full rounded-lg border-gray-300 focus:border-belmontblue focus:ring-belmontblue" />
<p class="text-sm text-gray-500">Enter your current city so Champions can find you.</p>
</div>
Tables are inherently “cold” — soften them:
| Pattern | Implementation |
|---|---|
| Zebra striping | Very subtle tint (not harsh alternation) |
| Row padding | More generous than default |
| Empty state | Suggests what to do next |
| Mobile | Switch to card rows (name + key fields + actions) |
Every empty state should include:
Tone examples:
| ❌ Bad | ✅ Good |
|---|---|
| “No Champions found.” | “No Champions in Portland yet.” |
| “No messages.” | “Your inbox is empty — start a conversation!” |
| “No events.” | “No events scheduled. Want to host one?” |
<!-- Empty state example -->
<div class="text-center py-12">
<%= heroicon "users", variant: :outline, options: { class: "w-12 h-12 text-gray-400 mx-auto" } %>
<h3 class="mt-4 text-lg font-medium text-gray-900">No Champions in Portland yet</h3>
<p class="mt-2 text-gray-500">Know an alum there? Invite them to join!</p>
<button class="mt-4 bg-belmontblue text-white px-4 py-2 rounded-lg">
Invite a Champion
</button>
</div>
| State | Approach |
|---|---|
| Loading | Skeleton screens for lists/cards (reduces stress) |
| Success | Short confirmation + what happens next |
| Errors | One clear fix, not a wall of red |
Implemented in Phase 1.9.4 — The dashboard is the “home” of the Champion Portal.
The dashboard should feel like returning to a welcoming community space, not logging into a corporate tool.
| Admin Panel Feel ❌ | Community Home Feel ✅ |
|---|---|
| Generic “Dashboard” title | “Good morning, Jane! 👋” |
| Dense metrics grid | Scannable card hierarchy |
| All sections equal weight | Clear visual priority |
| Static, unchanging | Fresh content encourages return |
| Transactional language | Warm, belonging language |
Personalized greetings based on local time create warmth:
| Time Range | Greeting |
|---|---|
| 5:00 AM – 11:59 AM | “Good morning, [Name]! 👋” |
| 12:00 PM – 4:59 PM | “Good afternoon, [Name]! 👋” |
| 5:00 PM – 4:59 AM | “Good evening, [Name]! 👋” |
Implementation: See Cp::DashboardHelper#time_of_day_greeting
<h1 class="font-sansalt text-2xl sm:text-3xl font-bold text-belmontblue">
<%= time_of_day_greeting(current_cp_champion.display_first_name) %>
</h1>
Champions in their first week get an enhanced welcome hero:
<% if current_cp_champion.created_at > 7.days.ago %>
<!-- Full welcome banner with gradient -->
<% else %>
<!-- Simpler returning champion greeting -->
<% end %>
Dashboard widgets have clear priority order. Higher priority = larger, more prominent.
| Priority | Widget | Visual Treatment |
|---|---|---|
| 1. Hero | Welcome greeting | Full-width, gradient for new users |
| 2. Next Steps | Profile/verification prompts | Prominent card, consolidated |
| 3. Community | District preview | Champion photos, location context |
| 4. Messages | Unread count + previews | Sidebar position, badge for unread |
| 5. Profile | Compact summary | Sidebar, completion progress |
| 6. News | Community content | Grid cards (Phase 1.10) |
| 7. Quick Actions | Edit, search, invite, help | Demoted to subtle row |
Multiple prompts (profile completion, verification pending, location missing) should be consolidated into one card, not scattered across the page.
Card Structure:
Item Types: | Icon | Type | Description | |——|——|————-| | 👤 (profile) | Profile completion | “Complete your profile (85%)” | | ⏳ (clock) | Verification pending | “You’ll get directory access soon” | | 📍 (location) | Missing location | “Add ZIP code to find nearby Champions” |
The Community Snapshot card adapts to the champion’s state:
| State | Display |
|---|---|
| No ZIP code | Prompt to add location |
| Not verified | “Directory unlocks after verification” + count |
| First in district | Celebration + invite CTA + regional fallback |
| Has neighbors | Photo preview grid + “View all →” |
Design now, implement in Phase 3.3. The dashboard needs fresh content for return visits.
| Post Type | Icon | Example |
|---|---|---|
| Story | 📰 | Alumni Spotlight: Sarah Chen |
| Photo | 📷 | Nashville Meetup Recap |
| Announcement | 📢 | New Feature: Direct Messaging |
Environment-aware display:
<% if show_demo_content? %>
<!-- Sample news cards -->
<% else %>
<!-- Coming soon placeholder -->
<% end %>
Quick actions are utility, not primary navigation. Display as subtle row at bottom:
<div class="border-t border-gray-200 pt-6">
<p class="text-xs font-medium text-gray-400 uppercase tracking-wider mb-3">Quick Actions</p>
<div class="flex flex-wrap gap-2">
<!-- Ghost-style buttons: Edit Profile, Find Alumni, Invite, Help, Settings -->
</div>
</div>
Mobile (< 1024px): Single column, full-width cards stacked vertically.
Desktop (≥ 1024px): 3-column grid:
Desktop Layout:
┌─────────────────────────────────────────────────────────────────┐
│ Hero: Welcome greeting (full width) │
├───────────────────────────────────────────┬─────────────────────┤
│ Next Steps (2/3) │ Messages (1/3) │
├───────────────────────────────────────────┼─────────────────────┤
│ Community Snapshot (2/3) │ Your Profile (1/3) │
├───────────────────────────────────────────┴─────────────────────┤
│ News/Posts (full width, 3-card grid) │
├─────────────────────────────────────────────────────────────────┤
│ Quick Actions (subtle row, full width) │
└─────────────────────────────────────────────────────────────────┘
Show who’s active and engaged:
<!-- Active indicator on profile card -->
<div class="relative">
<img src="..." class="w-12 h-12 rounded-full" />
<span class="absolute bottom-0 right-0 w-3 h-3 bg-green-500 border-2 border-white rounded-full"
title="Active this week"></span>
</div>
| Badge | Trigger |
|---|---|
| 🎉 Event Host | Hosted at least 1 event |
| 📖 Storyteller | Shared a story |
| 🤝 Connector | Sent 5+ messages |
| ⭐ Active | Active every week for a month |
New Champions should feel welcomed:
<!-- Dashboard welcome banner -->
<div class="bg-gradient-to-r from-belmontblue to-admissionsblue text-white rounded-xl p-6">
<h2 class="text-2xl font-bold">Welcome to the Champion Portal, Sarah! 🎉</h2>
<p class="mt-2 text-blue-100">You're now connected to 127 Champions across 23 cities.</p>
<a href="..." class="mt-4 inline-block bg-white text-belmontblue px-4 py-2 rounded-lg font-semibold hover:bg-gray-100">
Find Champions Near You
</a>
</div>
For milestone moments (first event submitted, account verified), consider subtle confetti or animation.
See also: Phase 1.17: Mobile Polish for implementation details
| Breakpoint | Size | Design |
|---|---|---|
| Mobile | < 640px | Single column, stacked cards |
| Tablet | 640–1024px | Two columns where appropriate |
| Desktop | > 1024px | Full layout, sidebars |
The Champion Portal uses a fixed bottom navigation bar on mobile. All pages must account for this by adding bottom padding to the main content area:
<%# In layout or main content wrapper %>
<main class="pb-20 sm:pb-0">
<%# Content won't be hidden behind bottom nav %>
</main>
Standardize card styling across all views:
| Property | Mobile | Desktop |
|---|---|---|
| Horizontal margin | mx-4 (16px) |
mx-0 (in grid) |
| Internal padding | p-4 (16px) |
p-6 (24px) |
| Section spacing | space-y-4 |
space-y-6 |
<%# Standard card pattern %>
<div class="mx-4 sm:mx-0">
<div class="bg-white rounded-xl shadow-sm p-4 sm:p-6">
<%# Card content %>
</div>
</div>
On mobile, filter-heavy views (Directory, Events) should collapse filters by default:
┌─────────────────────────────────┐
│ Filters (2 applied) [▼] │
└─────────────────────────────────┘
Hero sections should inform, not dominate. On mobile:
| Guideline | Rule |
|---|---|
| Max viewport height | ~35% of initial viewport |
| Hero padding | py-4 sm:py-8 (reduced on mobile) |
| Action buttons | Move outside hero on mobile |
| Photo size | w-20 h-20 mobile, w-24 h-24 desktop |
Use consistent back navigation across all detail views:
<%# app/views/shared/_back_link.html.erb %>
<%= link_to path, class: "inline-flex items-center gap-1 text-gray-600 hover:text-belmontblue text-sm mb-4" do %>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
</svg>
Back to <%= section_name %>
<% end %>
Rules:
<)text-gray-600 with hover:text-belmontblueHandle long text gracefully:
| Content Type | Strategy | Tailwind Class |
|---|---|---|
| Names (single line) | Truncate with ellipsis | truncate |
| Descriptions | 2-line clamp | line-clamp-2 |
| Community names | Allow 2-line wrap | line-clamp-2 |
| Requirement | Standard |
|---|---|
| Color contrast | WCAG AA minimum (4.5:1 for text) |
| Focus states | Visible keyboard focus indicators |
| Alt text | All images must have descriptive alt text |
| ARIA labels | Interactive elements need accessible names |
| Skip links | Navigation skip for keyboard users |
Note: The Belmont digital brand colors are designed with screen contrast in mind.
These are “defaults” to keep us consistent:
| Element | Default |
|---|---|
| Border radius | rounded-lg for inputs, rounded-xl for cards |
| Card shadow | shadow-sm default, shadow-md on hover |
| Borders | Low-contrast (border-gray-200), avoid heavy outlines |
| Page background | Warm neutral (bg-gray-50 or custom warm) |
| Card background | bg-white with soft shadow |
Fix in this order:
Key Principle: Image variant dimensions must match CSS display dimensions to avoid fuzzy images.
When using variant(resize_to_fill: [width, height]), the pixel values should match the actual rendered size:
| Tailwind Class | Pixels | Variant Size |
|---|---|---|
w-14 h-14 |
56×56 | resize_to_fill: [56, 56] |
w-20 h-20 |
80×80 | resize_to_fill: [80, 80] |
w-24 h-24 |
96×96 | resize_to_fill: [96, 96] |
w-28 h-28 |
112×112 | resize_to_fill: [112, 112] |
w-32 h-32 |
128×128 | resize_to_fill: [128, 128] |
w-40 h-40 |
160×160 | resize_to_fill: [160, 160] |
w-48 h-48 |
192×192 | resize_to_fill: [192, 192] |
Rectangular images: For non-square displays, use rectangular variants:
w-32 h-24 (128×96) = resize_to_fill: [128, 96] (4:3 ratio)w-48 aspect-[8/5] (192×120) = resize_to_fill: [192, 120] (8:5 ratio)aspect-video (16:9) = resize_to_fill: [800, 450] for full-width heroesCommon mistakes:
| ❌ Wrong | ✅ Correct | Issue |
|———-|———–|——-|
| resize_to_fill: [128, 128] for w-28 | resize_to_fill: [112, 112] | Oversized → wasted bandwidth |
| resize_to_fill: [112, 112] for rectangular display | resize_to_fill: [128, 96] | Square for rectangle → fuzzy upscaling |
| resize_to_fill: [800, 500] for aspect-video | resize_to_fill: [800, 450] | 8:5 ratio for 16:9 container → cropping |
/* Configured in tailwind.config.js */
belmontblue: '#001D54' /* Primary anchor */
belmontred: '#B21029' /* Secondary emphasis */
admissionsblue: '#1D4289' /* Alt primary */
fountainblue: '#2874AF' /* Hover states */
skyblue: '#6AB3E7' /* Highlights */
towerred: '#862633' /* Legacy - prefer belmontred */
When building pages, always include explicit image placeholders with requests:
<!-- Example placeholder in ERB -->
<div class="relative bg-gray-200 rounded-lg overflow-hidden" style="aspect-ratio: 16/9;">
<div class="absolute inset-0 flex items-center justify-center text-gray-500">
<div class="text-center p-4">
<svg class="w-12 h-12 mx-auto mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
<p class="font-medium">IMAGE NEEDED</p>
<p class="text-sm">Hero: Campus bell tower at sunset</p>
<p class="text-xs">1920x600px, landscape, space for text overlay on left</p>
</div>
</div>
</div>
Image Request: Champion Portal Hero Image
Purpose: Landing page hero for alumnichampions.com
Dimensions: 1920×600px (will be cropped responsively)
Subject: Belmont campus with alumni, ideally near bell tower or lawn
Mood: Warm, welcoming, aspirational
Text overlay: Yes — need space on left side for headline
Notes: Avoid dated clothing or event-specific signage
| Document | Purpose |
|---|---|
| LANGUAGE_STYLE_GUIDE.md | Voice & tone for all copy — Required for any user-facing text |
| ../JOBS-TO-BE-DONE.md | User motivations (esp. Job C9: Feel Like I Belong) |
| ../STAKEHOLDER-OVERVIEW.md | High-level features and strategic context |
| ../phases/phase-1/1.9-pre-beta-polish.md | Visual refresh implementation |
| ../phases/phase-4/README.md | Mobile polish planning |
tailwind.config.js |
Color configuration |
| Belmont Brand Guidelines | Official brand guidelines |
This document should evolve as we build. Add patterns that work, remove patterns that don’t.