# Media Display Fixes - Architectural Design

## Status
Active

## Problem Statement
Three issues with current media display implementation:
1. **Images too large** - No CSS constraints on image dimensions
2. **Captions visible** - Currently showing figcaption, should use alt text only
3. **Images missing on homepage** - Media not fetched or displayed in index.html

## Root Cause Analysis

### Issue 1: Images Too Large
The current CSS (`/static/css/style.css`) has NO styles for:
- `.note-media` container
- `.media-item` figure elements
- `.u-photo` images
- Responsive image constraints

Images display at their native dimensions, which can break layouts.

### Issue 2: Captions Visible
Template (`note.html` lines 25-27) explicitly renders figcaption:
```html
{% if item.caption %}
<figcaption>{{ item.caption }}</figcaption>
{% endif %}
```
This violates the social media pattern where captions are for accessibility (alt text) only.

### Issue 3: Missing Homepage Media
The index route (`public.py` line 231) doesn't fetch media:
```python
notes = list_notes(published_only=True, limit=20)
```
Compare to the note route (lines 263-267) which DOES fetch media.

## Architectural Solution

### Design Principles
1. **Consistency**: Same media display logic on all pages
2. **Responsive**: Images adapt to viewport and container
3. **Accessible**: Alt text for screen readers, no visible captions
4. **Performance**: Lazy loading for below-fold images
5. **Standards**: Proper Microformats2 markup maintained

### Component Architecture

#### 1. CSS Media Display System
Create responsive, constrained image display with grid layouts:

```css
/* Media container styles */
.note-media {
  margin-bottom: var(--spacing-md);
  width: 100%;
}

/* Single image - full width */
.note-media:has(.media-item:only-child) {
  max-width: 100%;
}

.note-media:has(.media-item:only-child) .media-item {
  width: 100%;
}

/* Two images - side by side */
.note-media:has(.media-item:nth-child(2):last-child) {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--spacing-sm);
}

/* Three or four images - grid */
.note-media:has(.media-item:nth-child(3)),
.note-media:has(.media-item:nth-child(4)) {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--spacing-sm);
}

/* Media item wrapper */
.media-item {
  margin: 0;
  padding: 0;
  background: var(--color-bg-alt);
  border-radius: var(--border-radius);
  overflow: hidden;
  aspect-ratio: 1 / 1; /* Instagram-style square crop */
  display: flex;
  align-items: center;
  justify-content: center;
}

/* Image constraints */
.media-item img,
.u-photo {
  width: 100%;
  height: 100%;
  object-fit: cover; /* Crop to fill container */
  display: block;
}

/* For single images, allow natural aspect ratio */
.note-media:has(.media-item:only-child) .media-item {
  aspect-ratio: auto;
  max-height: 500px; /* Prevent extremely tall images */
}

.note-media:has(.media-item:only-child) .media-item img {
  object-fit: contain; /* Show full image for singles */
  width: 100%;
  height: auto;
  max-height: 500px;
}

/* Remove figcaption from display */
.media-item figcaption {
  display: none; /* Captions are for alt text only */
}

/* Mobile responsive adjustments */
@media (max-width: 767px) {
  /* Stack images vertically on small screens */
  .note-media:has(.media-item:nth-child(2):last-child) {
    grid-template-columns: 1fr;
  }

  .media-item {
    aspect-ratio: 16 / 9; /* Wider aspect on mobile */
  }
}
```

#### 2. Template Refactoring

Create a reusable macro for media display to ensure consistency:

**New template partial: `templates/partials/media.html`**
```jinja2
{# Reusable media display macro #}
{% macro display_media(media_items) %}
  {% if media_items %}
  <div class="note-media">
    {% for item in media_items %}
    <figure class="media-item">
      <img src="{{ url_for('public.media_file', path=item.path) }}"
           alt="{{ item.caption or 'Image' }}"
           class="u-photo"
           loading="lazy">
      {# No figcaption - caption is for alt text only #}
    </figure>
    {% endfor %}
  </div>
  {% endif %}
{% endmacro %}
```

**Updated `note.html`** (lines 16-31):
```jinja2
{# Import media macro #}
{% from "partials/media.html" import display_media %}

{# Media display at TOP (v1.2.0 Phase 3, per ADR-057) #}
{{ display_media(note.media) }}
```

**Updated `index.html`** (after line 26, before e-content):
```jinja2
{# Import media macro at top of file #}
{% from "partials/media.html" import display_media %}

{# In the note loop, after the title check #}
      {% if has_explicit_title %}
      <h3 class="p-name">{{ note.title }}</h3>
      {% endif %}

      {# Media preview (if available) #}
      {{ display_media(note.media) }}

      {# e-content: note content (preview) #}
      <div class="e-content">
```

#### 3. Route Handler Updates

Update the index route to fetch media for each note:

**`starpunk/routes/public.py`** (lines 219-233):
```python
@bp.route("/")
def index():
    """
    Homepage displaying recent published notes with media

    Returns:
        Rendered homepage template with note list including media

    Template: templates/index.html
    Microformats: h-feed containing h-entry items with u-photo
    """
    from starpunk.media import get_note_media

    # Get recent published notes (limit 20)
    notes = list_notes(published_only=True, limit=20)

    # Attach media to each note for display
    for note in notes:
        media = get_note_media(note.id)
        # Use object.__setattr__ since Note is frozen dataclass
        object.__setattr__(note, 'media', media)

    return render_template("index.html", notes=notes)
```

### Implementation Guidelines

#### Phase 1: CSS Foundation
1. Add media display styles to `/static/css/style.css`
2. Test with 1, 2, 3, and 4 image layouts
3. Verify responsive behavior on mobile/tablet/desktop
4. Ensure images don't overflow containers

#### Phase 2: Template Refactoring
1. Create `templates/partials/` directory if not exists
2. Create `media.html` with display macro
3. Update `note.html` to use macro
4. Update `index.html` to import and use macro
5. Remove figcaption rendering completely

#### Phase 3: Route Updates
1. Import `get_note_media` in index route
2. Fetch media for each note in loop
3. Attach media using `object.__setattr__`
4. Verify media passes to template

### Testing Checklist

#### Visual Tests
- [ ] Single image displays at reasonable size
- [ ] Two images display side-by-side
- [ ] Three images display in 2x2 grid (one empty)
- [ ] Four images display in 2x2 grid
- [ ] Images maintain aspect ratio appropriately
- [ ] No layout overflow on any screen size
- [ ] Captions not visible (alt text only)

#### Functional Tests
- [ ] Homepage shows media for notes
- [ ] Individual note page shows media
- [ ] Media lazy loads below fold
- [ ] Alt text present for accessibility
- [ ] Microformats2 u-photo preserved

#### Performance Tests
- [ ] Page load time acceptable with media
- [ ] Images don't block initial render
- [ ] Lazy loading works correctly

### Security Considerations
- Media paths already sanitized in media_file route
- Alt text must be HTML-escaped in templates
- No user-controlled CSS injection points

### Accessibility Requirements
- Alt text MUST be present (fallback to "Image")
- Images must not convey information not in text
- Focus indicators for keyboard navigation
- Proper semantic HTML (figure elements)

### Future Enhancements (Not for V1)
- Image optimization/resizing on upload
- WebP format support with fallbacks
- Lightbox for full-size viewing
- Video/audio media support
- CDN integration for media serving

## Decision Rationale

### Why Grid Layout?
- Native CSS, no JavaScript required
- Excellent responsive support
- Handles variable image counts elegantly
- Familiar social media pattern

### Why Hide Captions?
- Follows Twitter/Mastodon pattern
- Captions are for accessibility (alt text)
- Cleaner visual presentation
- Text content provides context

### Why Lazy Loading?
- Improves initial page load
- Reduces bandwidth for visitors
- Native browser support
- Progressive enhancement

### Why Aspect Ratio Control?
- Prevents layout shift during load
- Creates consistent grid appearance
- Matches social media expectations
- Improves visual harmony

## Implementation Priority
1. **Critical**: Fix homepage media display (functionality gap)
2. **High**: Add CSS constraints (UX/visual issue)
3. **Medium**: Hide captions (visual polish)

All three fixes should be implemented together for consistency.