Files
breedr/docs/COMPACT_CARDS.md

8.5 KiB
Raw Permalink Blame History

Compact Info Card Design

Problem Statement

The original design used large square photo grids that consumed excessive screen space, making it difficult to scan through multiple dogs quickly. Photos were displayed at 1:1 aspect ratio taking up 50-100% of card width.

Solution: Horizontal Info Cards

Transformed to a compact horizontal card layout with small avatar photos and prominent metadata, optimized for information scanning and list navigation.


Design Specifications

Layout Structure

┌─────────────────────────────────────────────────────────────────┐
│  [Avatar]   Name ♂              Breed • Age • Color         →  │
│   80x80     Golden Retriever     #REG-12345                      │
└─────────────────────────────────────────────────────────────────┘

Card Components

1. Avatar Photo (80x80px)

  • Size: Fixed 80px × 80px
  • Shape: Rounded corners (var(--radius))
  • Border: 2px solid var(--border)
  • Background: var(--bg-primary) when no photo
  • Fallback: Dog icon at 32px, muted color
  • Object Fit: cover (crops to fill square)

2. Info Section (Flex: 1)

  • Name: 1.125rem, bold, truncate with ellipsis
  • Sex Icon: Colored ♂/♀ (blue for male, pink for female)
  • Metadata Row:
    • Breed name
    • Age (calculated, with calendar icon)
    • Color (if available)
    • Separated by bullets (•)
  • Registration Badge:
    • Monospace font
    • Hash icon prefix
    • Dark background pill
    • 1px border

3. Arrow Indicator

  • Icon: ArrowRight at 20px
  • Color: var(--text-muted)
  • Opacity: 0.5 default, increases on hover
  • Purpose: Visual affordance for clickability

Space Comparison

Before (Square Grid)

[===============]
[    Photo      ]
[    300x300    ]
[===============]
Name
Breed • Sex

Height: ~380px per card Width: 280-300px Photos per viewport: 2-3 (desktop)

After (Horizontal Card)

[Avatar] Name, Breed, Age, Badge →
 80x80

Height: ~100px per card Width: Full container width Cards per viewport: 6-8 (desktop)

Metrics

Metric Before After Improvement
Card height 380px 100px -74%
Photo area 90,000px² 6,400px² -93%
Scannable info 2-3 cards 6-8 cards +200%
Scroll distance 760px 200px -74%

Implementation Details

React Component Structure

<Link to={`/dogs/${dog.id}`} className="card">
  {/* Avatar */}
  <div className="avatar-80">
    {photo ? <img src={photo} /> : <Dog icon />}
  </div>
  
  {/* Info */}
  <div className="info-section">
    <h3>{name} <span>{sex icon}</span></h3>
    <div className="metadata">
      {breed}  {age}  {color}
    </div>
    <div className="badge">
      <Hash /> {registration}
    </div>
  </div>
  
  {/* Arrow */}
  <ArrowRight />
</Link>

CSS Styling

.card {
  display: flex;
  gap: 1rem;
  align-items: center;
  padding: 1rem;
  transition: all 0.2s;
}

.card:hover {
  border-color: var(--primary);
  transform: translateY(-2px);
  box-shadow: 0 8px 16px rgba(0,0,0,0.3);
}

.avatar-80 {
  width: 80px;
  height: 80px;
  border-radius: var(--radius);
  border: 2px solid var(--border);
  overflow: hidden;
}

.info-section {
  flex: 1;
  min-width: 0; /* Allow text truncation */
}

Age Calculation

Dynamic age display from birth date:

const calculateAge = (birthDate) => {
  const today = new Date()
  const birth = new Date(birthDate)
  let years = today.getFullYear() - birth.getFullYear()
  let months = today.getMonth() - birth.getMonth()
  
  if (months < 0) {
    years--
    months += 12
  }
  
  // Format: "2y 3mo" or "8mo" or "3y"
  if (years === 0) return `${months}mo`
  if (months === 0) return `${years}y`
  return `${years}y ${months}mo`
}

Interactive States

Default

  • Border: var(--border)
  • Shadow: var(--shadow-sm)
  • Transform: none

Hover

  • Border: var(--primary)
  • Shadow: 0 8px 16px rgba(0,0,0,0.3)
  • Transform: translateY(-2px)
  • Arrow opacity: 1.0
  • Transition: 0.2s cubic-bezier

Active/Click

  • Navigate to detail page
  • Maintains selection state in history

Responsive Behavior

Desktop (>768px)

  • Full horizontal layout
  • All metadata visible
  • Hover effects enabled

Tablet (768px - 1024px)

  • Slightly smaller avatar (70px)
  • Abbreviated metadata
  • Touch-friendly spacing

Mobile (<768px)

  • Avatar: 60px
  • Name on top line
  • Metadata stacks below
  • Registration badge wraps
  • Larger tap targets

Accessibility

Keyboard Navigation

  • Cards are focusable links
  • Tab order follows visual order
  • Enter/Space to activate
  • Focus ring with primary color

Screen Readers

  • Semantic HTML (Link + heading structure)
  • Alt text on avatar images
  • Icon meanings in aria-labels
  • Registration formatted as code

Color Contrast

  • Name: High contrast (var(--text-primary))
  • Metadata: Medium contrast (var(--text-secondary))
  • Icons: Sufficient contrast ratios
  • Sex icons: Color + symbol (not color-only)

Benefits

User Experience

  1. Faster Scanning - See 3x more dogs without scrolling
  2. Quick Comparison - All key info visible at once
  3. Less Cognitive Load - Consistent layout, predictable
  4. Better Navigation - Clear visual hierarchy

Performance

  1. Smaller Images - Avatar size reduces bandwidth
  2. Lazy Loading - Efficient with IntersectionObserver
  3. Less Rendering - Simpler DOM structure
  4. Faster Scrolling - Fewer pixels to paint

Mobile

  1. Touch Targets - Full card width clickable
  2. Vertical Real Estate - More content on screen
  3. Thumb-Friendly - No precise tapping required
  4. Data Efficient - Smaller photo downloads

Usage Context

Dashboard

  • Shows 6-8 recent dogs
  • "View All" button to Dogs page
  • Provides quick overview

Dogs List

  • Full searchable/filterable catalog
  • Horizontal scroll on mobile
  • Infinite scroll potential
  • Batch operations possible

NOT Used For

  • Dog detail page (uses full photo gallery)
  • Pedigree tree (uses compact nodes)
  • Print layouts (uses different format)

Future Enhancements

Planned

  • Checkbox selection mode (bulk actions)
  • Drag-to-reorder in custom lists
  • Quick actions menu (edit, delete)
  • Photo upload from card
  • Inline editing of name/breed

Considered

  • Multi-select with Shift+Click
  • Card density options (compact/comfortable/spacious)
  • Alternative views (grid toggle)
  • Column sorting (name, age, breed)
  • Grouping (by breed, age range)

Examples

Example 1: Male with Photo

┌───────────────────────────────────────────────────────────┐
│  [Photo]   Max ♂                                         →  │
│           Golden Retriever • 2y 3mo • Golden                │
│           #AKC-SR123456                                    │
└───────────────────────────────────────────────────────────┘

Example 2: Female No Photo

┌───────────────────────────────────────────────────────────┐
│  [🐶]    Bella ♀                                        →  │
│   icon    Labrador Retriever • 8mo • Black                  │
└───────────────────────────────────────────────────────────┘

Example 3: Puppy No Registration

┌───────────────────────────────────────────────────────────┐
│  [Photo]   Rocky ♂                                         →  │
│           German Shepherd • 3mo                             │
└───────────────────────────────────────────────────────────┘

Last Updated: March 8, 2026