Every workspace tab in apples.live must follow the SAME visual shape so the user's L1 grid reads as a coherent set. Inconsistency here is the single most common reason a workspace feels "messy".
Required structure
<!doctype html>
<html><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<style>
*, *::before, *::after { box-sizing: border-box; }
html, body { background: transparent; margin: 0; }
body { font-family: -apple-system, BlinkMacSystemFont, system-ui, sans-serif; color: #1a1a1a; padding: 12px; padding-bottom: 80px; }
.header-card { background: #fff; border-radius: 12px; padding: 12px 16px; margin-bottom: 14px; display: flex; align-items: center; gap: 10px; }
.header-emoji { font-size: 28px; }
.header-text { flex: 1; min-width: 0; }
.header-title { font-size: 18px; font-weight: 700; line-height: 1.2; }
.header-meta { font-size: 13px; color: #6b7280; margin-top: 2px; line-height: 1.2; }
.card { background: #fff; border-radius: 12px; padding: 14px 16px; margin-bottom: 10px; box-shadow: 0 1px 0 rgba(0,0,0,0.04); }
.empty { background: #fff; border-radius: 12px; padding: 20px 22px; color: #6b7280; font-size: 14px; text-align: center; }
</style></head>
<body>
<div class="header-card">
<div class="header-emoji">EMOJI</div>
<div class="header-text">
<div class="header-title">TAB NAME</div>
<div class="header-meta">SHORT DESCRIPTION OR COUNT</div>
</div>
</div>
<!-- Body content here. Each row/section = its own .card -->
<div class="card">Row 1</div>
<div class="card">Row 2</div>
</body></html>Hard rules
1. **Transparent body.** `body { background: transparent }`. Never paint the whole tab white.
2. **Header card.** First element in `<body>` is always a `.header-card` with emoji + title + meta line. The PNG snap on L1 crops the top of the tab; without a header card the user can't tell what tab they're looking at from the thumbnail.
3. **White content cards on transparent bg.** Each row/section is its own `.card`. Leave the 10 px gap between cards so the sky bleeds through.
4. **Empty / loading / error states wrap in a white card.** Never print "Loading...", "No items", "Reconnect Google", or error text directly on the page bg.
5. **No outer wrapper, no inner background colors, no full-bleed colored sections.** Only the cards are opaque.
6. **Tap targets ≥40 px tall on mobile.** Padding inside cards should keep this minimum.
7. **No external CDNs.** Inline all CSS + JS. Cross-origin `<img>` / `<video>` is fine (no allow-same-origin needed for those).
Emoji + title meta examples
- Inbox tab: 📥 / "Inbox" / "12 messages, ranked by Al triage"
- Calendar tab: 📅 / "Calendar" / "3 events, next 7 days"
- Custom tab: pick an emoji that matches the tab's intent + a one-line meta describing what it shows
- Empty state: emoji stays the same, meta switches to "Connect Google to load events" etc.
Why this matters
Tab snapshots on L1 are captured at 390 × 844 viewport. The user sees only the top of the page in the thumbnail. The .header-card is what makes a tile self-identifying. Without it, every tab thumbnail looks like the same generic blob of content cards.
The user explicitly asked for this uniform look. Do not deviate.
Width + responsiveness (every tab, every device)
The tab must look correct on iOS Safari (390 wide), Android Chrome (~400 to 412 wide), iPad portrait (768), and desktop browsers (1280 to 1920). One CSS, no JS device sniffing.
- The single body container caps at 1280 px and centers itself: `max-width: 1280px; margin: 0 auto; padding: 12px;`. Header card + every content card lives INSIDE that container so they all share the same left + right edge.
- The same horizontal padding (`12px`) on both header and content rows. Do NOT use a different padding for the header (the user notices the misalignment immediately).
- Cards inside the container expand to fill the container width by default (`width: 100%; box-sizing: border-box`). Never set a fixed pixel width on a card.
- For grids of items (e.g. integrations list): use `grid-template-columns: repeat(auto-fill, minmax(280px, 1fr))` so it collapses to 1 column on phones and reflows up to N columns on wider screens automatically.
- Honor the iOS safe-area: `padding-bottom: max(80px, env(safe-area-inset-bottom) + 80px)` so the chat composer never covers the last item.
- Touch targets stay ≥ 40 px tall even on desktop (the same UI ships everywhere; don't shrink hit zones).
- Never assume a viewport width. No fixed widths in px on outer containers. No `@media (max-width: 600px)` carve-outs — design mobile-first so the desktop layout is naturally the same with more horizontal room.
If you see a card that's narrower than the others, the wrapper isn't shared. Fix it by moving the misaligned element inside the single body container.