fix(home): card fills the slide; preview uses photo's natural aspect
CI / test (push) Has been cancelled

Two complaints, one root cause: the FrameCard was floating in the slide with
a min-height-padded preview, so (1) photos got top/bottom gray bars instead
of fitting their container, and (2) there was a fat empty gap between the
card body and the bottom nav.

Restructured the large card to flex-fill its slide:
- preview hugs the photo's intrinsic aspect ratio (img with width:100%
  height:auto); no min-height, no aspect-ratio override → no letterbox
- card body has flex:1, info pinned at top, Add Photo button pinned at
  bottom via margin-top:auto and width:100%
- HomeView main / single-card / carousel all flex:1 down through the
  layout so the slide gets the full available height
- empty-state placeholder still reserves the device's aspect so the
  card doesn't jump while images load

Result: the photo fills its container left/right with no bars; the body
absorbs all remaining space below, with the action button always sitting
just above the bottom nav.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-06 18:39:58 -04:00
parent d266770170
commit 78405b644d
15 changed files with 61 additions and 33 deletions
+38 -16
View File
@@ -28,7 +28,7 @@
:alt="`Current photo on ${name}`"
class="frame-card__img"
/>
<div v-else class="frame-card__empty-preview" aria-hidden="true">
<div v-else class="frame-card__empty-preview" :style="emptyAspectStyle" aria-hidden="true">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<rect x="3" y="3" width="18" height="18" rx="2"/>
<circle cx="8.5" cy="8.5" r="1.5"/>
@@ -85,9 +85,15 @@ const props = defineProps<{
defineEmits<{ 'add-photo': [deviceId: number]; edit: [deviceId: number] }>()
const previewStyle = computed(() =>
// On large cards we let the <img> drive the preview height (its intrinsic
// aspect ratio is the device's actual aspect). Until the image loads — or
// when no thumbnail exists yet — we want the empty placeholder to reserve
// roughly the same shape so the layout doesn't jump.
const previewStyle = computed(() => ({}))
const emptyAspectStyle = computed(() =>
props.size === 'large'
? { aspectRatio: props.orientation === 'portrait' ? '3/5' : '5/3' }
? { aspectRatio: props.orientation === 'portrait' ? '3 / 5' : '5 / 3' }
: {}
)
@@ -169,38 +175,49 @@ const statusText = computed(() => {
}
// ── Large (single device or carousel slide) ──────────────────────────────
// Floor the preview to a healthy chunk of the viewport so landscape frames
// (5:3) don't render as a thin strip. Portrait frames (3:5) keep their
// natural aspect — taller still wins.
&--large &__preview {
background: var(--color-surface-2);
// The card stretches to fill its parent (slide or single-card container).
// The preview hugs the photo's natural aspect — no letterbox bars, no min-
// height forcing extra blank space. Whatever vertical room is left after
// the photo gets absorbed by the body, with the Add button pinned to the
// bottom so the card never has a "huge gap" floating above the nav.
&--large {
display: flex;
align-items: center;
justify-content: center;
min-height: 50dvh;
flex-direction: column;
height: 100%;
}
&--large &__preview {
flex: 0 0 auto;
background: var(--color-surface-2);
display: block;
overflow: hidden;
}
&--large &__img {
display: block;
width: 100%;
height: 100%;
object-fit: contain;
height: auto;
}
&--large &__empty-preview {
color: var(--color-text-muted);
opacity: 0.5;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
}
&--large &__body {
flex: 1;
min-height: 0;
padding: var(--space-4);
display: flex;
align-items: flex-start;
justify-content: space-between;
flex-direction: column;
gap: var(--space-3);
}
&--large &__info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
@@ -212,6 +229,11 @@ const statusText = computed(() => {
font-weight: 700;
}
&--large &__add-btn {
margin-top: auto;
width: 100%;
}
// ── Compact (multi device) ───────────────────────────────────────────────
&--compact {
display: flex;