Kiosk Redesign V3 — “Gallery Wall”
Date: 2026-03-06 Project: family-dashboard (Vite + React 19 + Tailwind 4 + Supabase) Scope: Kiosk mode layout and visual hierarchy overhaul
Problem Statement
The current kiosk layout crams ~12 content types into a single scrollless portrait viewport. The bottom section’s 55/45 photo/panel split satisfies neither: photos are letterboxed awkwardly at both orientations, and the info panels are too small to read at wall distance (6-10 feet). Cards all share the same visual treatment, creating a flat, undifferentiated layout. The news ticker is invisible. The compact calendar row shows truncated text that’s useless at a glance.
Design Goals
- Dramatic visual hierarchy: hero > supporting > ambient > showcase
- Every text element readable at 6-10 feet
- Photos presented with gallery-quality framing at any aspect ratio
- Content rotation frees space so each panel gets room to breathe
- Pi-safe: no backdrop-filter, no JS animation loops, compositor-only transitions
Non-Goals
- Data layer changes (all hooks unchanged)
- Default/mobile dashboard changes
- Time-of-day theme engine changes (keep existing 5-phase system)
- Photo source/fetching changes
1. Layout Structure
Portrait viewport (1080x1920) divided into 7 grid areas:
+----------------------------------+
| HEADER | ~6%
| Shimmer clock + date | Weather |
+----------------------------------+
| |
| TODAY -- Hero Card | ~28%
| Full event list, weather |
| |
+----------------+-----------------+
| Tomorrow | Day After | ~18%
| 3-4 events | 3-4 events |
+----+----+------+-----------------+
| Thu| Fri| Sat | Sun | ~8%
|icon|icon| icon | icon |
| 2 | 1 | Free | 3 |
+----+----+------+-----------------+
| |
| ROTATING STAGE | ~30%
| Photo > Life Stuff > World Stuff|
| |
+----------------------------------+
| NEWS TICKER | ~5%
+----------------------------------+
| STATUS BAR | ~2%
+----------------------------------+
Grid Definition
.kiosk-grid {
display: grid;
grid-template-areas:
'header'
'today'
'next'
'compact'
'stage'
'ticker'
'status';
grid-template-columns: 1fr;
grid-template-rows: auto auto auto auto 1fr auto auto;
}
The rotating stage gets 1fr — it expands to fill all remaining vertical space after the calendar section.
2. Visual Hierarchy
Today Card — “The Hero”
- Left accent border: 6px solid
var(--fd-accent)(up from 4px) - Elevated background: double the card opacity —
rgba(255,255,255,0.08)dark phases,rgba(255,255,255,0.92)day phase - Title “Today” at
clamp(1.8rem, 3vw, 2.6rem)in accent color - Event rows: generous padding and gaps, each row feels like its own line item
- All other sizing stays as-is (already bumped in v2)
Tomorrow / Day After — “Supporting Cast”
- Standard card treatment:
var(--fd-card-bg)+var(--fd-card-border) - Remove left accent border (only Today gets that distinction)
- Bump text sizes ~10% from current values
- 2-column sub-grid, same as current
Compact Row — “The Glanceable Strip”
Radical simplification. Remove card backgrounds and borders entirely. Each of 4 cells shows only:
- Day name:
clamp(1.2rem, 1.5vw, 1.5rem)weight 600 - Weather icon + high temp in accent color
- Event count (“2 events”) or “Free” at reduced opacity
No individual event text. No truncation. Readable in 0.5 seconds from across the room. The absence of card chrome makes this row visually recede, creating a loud > medium > quiet gradient down the calendar section.
Rotating Stage — “The Gallery”
Full-width zone that cycles through 3 content types on a 60s cadence.
Photo slot: Image displayed at natural aspect ratio, centered, with generous padding. Container has slightly elevated surface (rgba(255,255,255,0.06)) and thin warm border (1px solid rgba(255,200,150,0.08)). Optional caption area below for photo date. The warm ambient background shows through as a matte border — gallery-wall aesthetic.
Life Stuff slot: Full-width card. Sections: Coming Up (countdowns), Groceries, Chores. All text dramatically larger now that it has 100% width and ~30% viewport height:
- Section titles: clamp(0.9rem, 1.3vw, 1.15rem) (up from clamp(11px, 1vw, 14px))
- Item text: clamp(1.1rem, 1.5vw, 1.4rem) (up from clamp(13px, 1.2vw, 17px))
World Stuff slot: Full-width card. Sections: Stars (horoscopes), Transit, World (country-of-day). Same size bumps as Life Stuff.
News Ticker — “The Marquee”
Distinct from the rotating stage above with its own visual presence:
- Background: rgba(255,255,255,0.06) dark phases, rgba(0,0,0,0.04) day phase
- Left accent pip (small var(--fd-accent) dot) before source label
- Source label in accent color, bold
- Text: clamp(1rem, 1.3vw, 1.2rem) (~30% larger than current)
- Padding: 0.6rem 1rem (up from 0.35rem 0)
- Slide-up carousel animation unchanged (compositor-friendly, works on Pi)
Status Bar — “The Quiet Footer”
Keeps: “Last refresh: HH:MM:SS”, “Made in Unicorn.Land”, “Culley.Haynes Information Station”
Font size nudged up to clamp(0.9rem, 1.1vw, 1.05rem) to match ticker’s new presence.
3. Rotation Mechanics
Cadence
Photo (60s) --> Life Stuff (60s) --> World Stuff (60s) --> Photo (60s) --> ...
3-minute full cycle. ~20 photos visible per hour.
Transitions
- 800ms opacity crossfade between stage slots (compositor-only)
- No slide/swipe — fades feel ambient and gallery-like
- Photos advance index once per photo-slot appearance (new photo every 3 minutes, no mid-slot transitions)
Implementation
Replace KioskBottomSection (which renders photo + rotating panel side-by-side) with a new KioskRotatingStage component:
- Uses a
useStageRotation(cadenceMs = 60000)hook - Stage index cycles 0 (photo) > 1 (life) > 2 (world)
- Each slot renders at
position: absolute; inset: 0with opacity transitions - Photo slot reuses
KioskPhotoFramewithobject-fit: contain - Life slot reuses
KioskPageA(with updated sizing) - World slot reuses
KioskPageB(with updated sizing)
The existing useGooglePhotos hook’s currentIndex advances when stage rotates back to slot 0.
4. What Changes vs. What Stays
New Components
KioskRotatingStage— replacesKioskBottomSection, manages 3-slot rotation
New Hooks
useStageRotation(cadenceMs)— returns{ stageIndex, isTransitioning }
Modified
KioskDashboard.tsx— new grid structure, replace bottom section with rotating stageKioskCompactRow.tsx— simplified to day name + weather icon + event count onlyKioskPageA.tsx— larger text sizes, layout adjustments for full-widthKioskPageB.tsx— larger text sizes, layout adjustments for full-widthKioskNewsTicker.tsx— larger text, accent pip, new container stylingKioskTodayCard.tsx— 6px accent border, elevated backgroundKioskNextDayCard.tsx— remove left accent border, bump text 10%index.css— new grid definition, stage styles, ticker styles, compact row styles, hero elevationStatusBar.tsx— font size bump
Removed
KioskBottomSection.tsx— replaced byKioskRotatingStageKioskRotatingPanel.tsx— rotation logic moves intoKioskRotatingStage
Unchanged
- All data hooks (weather, calendar, timers, groceries, chores, transit, horoscope, country, news, photos)
KioskPhotoFrame.tsx(reused inside rotating stage)- Header component
- Time-of-day theme engine
- Error boundaries (re-wrapped around new stage)
- Supabase schema & API integrations
- Default/mobile dashboard
5. Performance Budget
- No new JS animation loops
- One new CSS opacity transition (800ms, compositor-only)
useStageRotationhook: singlesetIntervalat 60s- Removes the old
usePageRotationhook (net zero) - No backdrop-filter
- Clock shimmer animation unchanged
- Total new CSS: ~60 lines (stage styles, ticker restyle, compact simplification)
- Total new JS: ~40 lines (useStageRotation hook + KioskRotatingStage component)