Compare commits

...

2 commits

Author SHA1 Message Date
71855a4701 Add trophy case showcase page with iframe previews of all portal mockups
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 22:30:39 +00:00
a8a9861d90 Add messaging UI mockups — inbox feed and conversation views
Slack-inspired mobile-first messaging interface with:
- Main inbox with tab navigation (All, @Mentions, DMs, Channels)
- Slide-in sidebar menu with channels and portal links
- Full conversation view with message bubbles, reactions, typing indicators
- Thread/channel drill-down with compose bar
- Touch gestures (swipe to close sidebar/threads)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 23:27:30 +00:00
3 changed files with 2090 additions and 0 deletions

625
messaging-conversation.html Normal file
View file

@ -0,0 +1,625 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Payfrit — #general</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--primary: #2D7FF9;
--primary-dark: #1A5FD1;
--bg: #FFFFFF;
--bg-secondary: #F5F7FA;
--bg-tertiary: #EDF0F5;
--text: #1A1D26;
--text-secondary: #6B7280;
--text-muted: #9CA3AF;
--border: #E5E7EB;
--green: #10B981;
--red: #EF4444;
--orange: #F59E0B;
--purple: #7C3AED;
--shadow-sm: 0 1px 2px rgba(0,0,0,0.06);
--radius: 12px;
--radius-sm: 8px;
--radius-msg: 18px;
--safe-top: env(safe-area-inset-top, 0px);
--safe-bottom: env(safe-area-inset-bottom, 0px);
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: var(--bg-secondary);
color: var(--text);
line-height: 1.5;
overflow: hidden;
height: 100dvh;
}
.conversation-shell {
display: flex;
flex-direction: column;
height: 100dvh;
}
/* ========== HEADER ========== */
.conv-header {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 16px;
padding-top: calc(10px + var(--safe-top));
background: var(--bg);
border-bottom: 1px solid var(--border);
min-height: 56px;
}
.back-btn {
width: 36px;
height: 36px;
border: none;
background: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--radius-sm);
flex-shrink: 0;
}
.back-btn:active { background: var(--bg-secondary); }
.back-btn svg { width: 22px; height: 22px; color: var(--text); }
.conv-header-info { flex: 1; min-width: 0; }
.conv-header-title {
font-size: 17px;
font-weight: 700;
display: flex;
align-items: center;
gap: 6px;
}
.conv-header-title .channel-hash {
color: var(--text-muted);
font-weight: 400;
}
.conv-header-meta {
font-size: 12px;
color: var(--text-muted);
}
.header-actions {
display: flex;
gap: 4px;
}
.icon-btn {
width: 36px;
height: 36px;
border: none;
background: none;
cursor: pointer;
border-radius: var(--radius-sm);
display: flex;
align-items: center;
justify-content: center;
}
.icon-btn:active { background: var(--bg-secondary); }
.icon-btn svg { width: 20px; height: 20px; color: var(--text-secondary); }
/* ========== MESSAGE FEED ========== */
.message-feed {
flex: 1;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
padding: 16px;
display: flex;
flex-direction: column;
gap: 4px;
}
/* Date divider */
.date-divider {
display: flex;
align-items: center;
gap: 12px;
margin: 12px 0;
}
.date-divider::before, .date-divider::after {
content: '';
flex: 1;
height: 1px;
background: var(--border);
}
.date-divider span {
font-size: 12px;
font-weight: 600;
color: var(--text-muted);
white-space: nowrap;
}
/* Message bubble group */
.msg-group {
display: flex;
gap: 8px;
margin-bottom: 8px;
}
.msg-group-avatar {
width: 34px;
height: 34px;
border-radius: 8px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
font-weight: 700;
color: #fff;
margin-top: 2px;
}
.msg-group-body { flex: 1; min-width: 0; }
.msg-group-header {
display: flex;
align-items: baseline;
gap: 8px;
margin-bottom: 4px;
}
.msg-author {
font-size: 14px;
font-weight: 700;
}
.msg-timestamp {
font-size: 11px;
color: var(--text-muted);
}
.msg-bubble {
background: var(--bg);
padding: 10px 14px;
border-radius: var(--radius-msg);
border-top-left-radius: 4px;
font-size: 15px;
line-height: 1.5;
box-shadow: var(--shadow-sm);
max-width: 100%;
word-wrap: break-word;
}
.msg-bubble + .msg-bubble {
margin-top: 4px;
border-top-left-radius: var(--radius-msg);
}
.msg-bubble.own {
background: var(--primary);
color: #fff;
border-top-left-radius: var(--radius-msg);
border-top-right-radius: 4px;
margin-left: auto;
}
.msg-bubble .mention {
background: rgba(45, 127, 249, 0.15);
color: var(--primary);
padding: 1px 4px;
border-radius: 4px;
font-weight: 600;
}
.msg-bubble.own .mention {
background: rgba(255,255,255,0.2);
color: #fff;
}
/* System message */
.system-msg {
text-align: center;
font-size: 13px;
color: var(--text-muted);
padding: 8px 16px;
font-style: italic;
}
/* Reactions */
.msg-reactions {
display: flex;
gap: 6px;
margin-top: 6px;
flex-wrap: wrap;
}
.reaction {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 8px;
background: var(--bg-tertiary);
border: 1px solid var(--border);
border-radius: 12px;
font-size: 13px;
cursor: pointer;
}
.reaction:active { background: var(--bg-secondary); border-color: var(--primary); }
.reaction.active { background: #E0EDFF; border-color: var(--primary); }
.reaction-count { font-size: 12px; color: var(--text-secondary); font-weight: 600; }
/* Image attachment */
.msg-attachment {
margin-top: 8px;
border-radius: var(--radius);
overflow: hidden;
max-width: 280px;
}
.msg-attachment-img {
width: 100%;
height: 160px;
background: linear-gradient(135deg, var(--bg-tertiary), var(--border));
display: flex;
align-items: center;
justify-content: center;
color: var(--text-muted);
font-size: 13px;
}
.msg-attachment-info {
padding: 8px 10px;
background: var(--bg);
border: 1px solid var(--border);
border-top: none;
border-radius: 0 0 var(--radius) var(--radius);
}
.msg-attachment-name { font-size: 13px; font-weight: 600; }
.msg-attachment-size { font-size: 11px; color: var(--text-muted); }
/* ========== COMPOSE ========== */
.compose-area {
padding: 8px 12px;
padding-bottom: calc(8px + var(--safe-bottom));
background: var(--bg);
border-top: 1px solid var(--border);
}
.compose-row {
display: flex;
align-items: flex-end;
gap: 8px;
}
.compose-actions {
display: flex;
gap: 2px;
padding-bottom: 4px;
}
.compose-input-wrap {
flex: 1;
position: relative;
}
.compose-textarea {
width: 100%;
min-height: 40px;
max-height: 120px;
padding: 10px 14px;
border: 1px solid var(--border);
border-radius: 20px;
font-size: 15px;
font-family: inherit;
background: var(--bg-secondary);
color: var(--text);
outline: none;
resize: none;
line-height: 1.4;
transition: border-color 0.15s;
}
.compose-textarea:focus { border-color: var(--primary); }
.compose-textarea::placeholder { color: var(--text-muted); }
.send-btn {
width: 40px;
height: 40px;
border: none;
background: var(--primary);
color: #fff;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
transition: background 0.15s, transform 0.1s;
margin-bottom: 0;
}
.send-btn:active { background: var(--primary-dark); transform: scale(0.95); }
.send-btn svg { width: 20px; height: 20px; }
/* Typing indicator */
.typing-indicator {
font-size: 12px;
color: var(--text-muted);
padding: 4px 14px 0;
min-height: 20px;
}
.typing-dots {
display: inline-flex;
gap: 3px;
margin-right: 4px;
}
.typing-dots span {
width: 5px;
height: 5px;
background: var(--text-muted);
border-radius: 50%;
animation: typingBounce 1.2s infinite;
}
.typing-dots span:nth-child(2) { animation-delay: 0.2s; }
.typing-dots span:nth-child(3) { animation-delay: 0.4s; }
@keyframes typingBounce {
0%, 60%, 100% { transform: translateY(0); opacity: 0.4; }
30% { transform: translateY(-4px); opacity: 1; }
}
/* ========== SCROLL TO BOTTOM FAB ========== */
.scroll-bottom-fab {
position: fixed;
bottom: 80px;
right: 16px;
width: 40px;
height: 40px;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 50%;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
display: none;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 50;
}
.scroll-bottom-fab svg { width: 20px; height: 20px; color: var(--text-secondary); }
@media (min-width: 768px) {
.message-feed { padding: 16px 24px; }
.conv-header { padding: 10px 20px; }
}
</style>
</head>
<body>
<div class="conversation-shell">
<!-- HEADER -->
<header class="conv-header">
<a class="back-btn" href="messaging.html" aria-label="Back">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 12H5"/><path d="M12 19l-7-7 7-7"/></svg>
</a>
<div class="conv-header-info">
<div class="conv-header-title"><span class="channel-hash">#</span> general</div>
<div class="conv-header-meta">John, Koda, Sarah, Zara, Claude, Ava</div>
</div>
<div class="header-actions">
<button class="icon-btn" aria-label="Search">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/></svg>
</button>
<button class="icon-btn" aria-label="Channel info">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>
</button>
</div>
</header>
<!-- MESSAGE FEED -->
<div class="message-feed" id="messageFeed">
<div class="date-divider"><span>March 25, 2026</span></div>
<!-- John -->
<div class="msg-group">
<div class="msg-group-avatar" style="background: #2D7FF9;">J</div>
<div class="msg-group-body">
<div class="msg-group-header">
<span class="msg-author" style="color: #2D7FF9;">John</span>
<span class="msg-timestamp">9:15 AM</span>
</div>
<div class="msg-bubble">Morning team! Quick update — we've got a demo scheduled for next Friday. Let's make sure the scan → health score → alternatives flow is rock solid by then 💪</div>
<div class="msg-reactions">
<span class="reaction active">🔥 <span class="reaction-count">4</span></span>
<span class="reaction">👍 <span class="reaction-count">3</span></span>
</div>
</div>
</div>
<!-- Koda -->
<div class="msg-group">
<div class="msg-group-avatar" style="background: #7C3AED;">K</div>
<div class="msg-group-body">
<div class="msg-group-header">
<span class="msg-author" style="color: #7C3AED;">Koda</span>
<span class="msg-timestamp">9:22 AM</span>
</div>
<div class="msg-bubble">On it! Android scanner is already reading barcodes reliably. Just need to finalize the API call format with the backend.</div>
<div class="msg-bubble">Also pushed about 50 files yesterday — layouts, activities, data layer. It's coming together 🧱</div>
</div>
</div>
<!-- Sarah -->
<div class="msg-group">
<div class="msg-group-avatar" style="background: #059669;">S</div>
<div class="msg-group-body">
<div class="msg-group-header">
<span class="msg-author" style="color: #059669;">Sarah</span>
<span class="msg-timestamp">9:40 AM</span>
</div>
<div class="msg-bubble">Health score algorithm v3 is finalized! Much better handling of edge cases now. The weighted nutrient scoring really smooths out the weird outliers we were seeing.</div>
<div class="msg-reactions">
<span class="reaction">🎉 <span class="reaction-count">3</span></span>
</div>
</div>
</div>
<!-- Claude -->
<div class="msg-group">
<div class="msg-group-avatar" style="background: #DC2626;">C</div>
<div class="msg-group-body">
<div class="msg-group-header">
<span class="msg-author" style="color: #DC2626;">Claude</span>
<span class="msg-timestamp">10:05 AM</span>
</div>
<div class="msg-bubble">API v2 endpoint for alternatives is live! Response times averaging 120ms. The new matching algorithm considers nutrient similarity + price range + availability.</div>
<div class="msg-attachment">
<div class="msg-attachment-img">📊 API Response Time Chart</div>
<div class="msg-attachment-info">
<div class="msg-attachment-name">api-v2-benchmarks.png</div>
<div class="msg-attachment-size">245 KB</div>
</div>
</div>
</div>
</div>
<div class="date-divider"><span>Today</span></div>
<!-- Zara (own message) -->
<div class="msg-group" style="flex-direction: row-reverse;">
<div class="msg-group-avatar" style="background: #EC4899;">Z</div>
<div class="msg-group-body" style="display: flex; flex-direction: column; align-items: flex-end;">
<div class="msg-group-header" style="flex-direction: row-reverse;">
<span class="msg-author" style="color: #EC4899;">Zara</span>
<span class="msg-timestamp">8:30 AM</span>
</div>
<div class="msg-bubble own">Dashboard page is looking solid! Added the health score breakdown section and the scan history. Testing on mobile now.</div>
<div class="msg-reactions">
<span class="reaction">👀 <span class="reaction-count">2</span></span>
</div>
</div>
</div>
<!-- John -->
<div class="msg-group">
<div class="msg-group-avatar" style="background: #2D7FF9;">J</div>
<div class="msg-group-body">
<div class="msg-group-header">
<span class="msg-author" style="color: #2D7FF9;">John</span>
<span class="msg-timestamp">9:45 AM</span>
</div>
<div class="msg-bubble"><span class="mention">@zara</span> get those mockups created, i want to see some designs. Maybe look at how Slack does their mobile layout — tabs, slide-in menu, the basics.</div>
</div>
</div>
<!-- Koda -->
<div class="msg-group">
<div class="msg-group-avatar" style="background: #7C3AED;">K</div>
<div class="msg-group-body">
<div class="msg-group-header">
<span class="msg-author" style="color: #7C3AED;">Koda</span>
<span class="msg-timestamp">10:02 AM</span>
</div>
<div class="msg-bubble"><span class="mention">@zara</span> can you check the health score display on the scan page? Something looks off with the color mapping — scores in the 60-70 range are showing green instead of yellow</div>
</div>
</div>
<!-- Sarah -->
<div class="msg-group">
<div class="msg-group-avatar" style="background: #059669;">S</div>
<div class="msg-group-body">
<div class="msg-group-header">
<span class="msg-author" style="color: #059669;">Sarah</span>
<span class="msg-timestamp">10:15 AM</span>
</div>
<div class="msg-bubble">That might be related to the v3 scoring changes — thresholds shifted. The new breakpoints are: 0-40 red, 41-65 orange, 66-80 yellow, 81-100 green.</div>
</div>
</div>
<!-- Zara (own) -->
<div class="msg-group" style="flex-direction: row-reverse;">
<div class="msg-group-avatar" style="background: #EC4899;">Z</div>
<div class="msg-group-body" style="display: flex; flex-direction: column; align-items: flex-end;">
<div class="msg-group-header" style="flex-direction: row-reverse;">
<span class="msg-author" style="color: #EC4899;">Zara</span>
<span class="msg-timestamp">10:20 AM</span>
</div>
<div class="msg-bubble own">On it! Building the messaging mockups now — slide-in menu, tab nav, rolling message feed. And I'll fix those color breakpoints on the scan page too 🎨</div>
</div>
</div>
</div>
<!-- COMPOSE AREA -->
<div class="compose-area">
<div class="typing-indicator" id="typingIndicator">
<span class="typing-dots"><span></span><span></span><span></span></span>
Koda is typing...
</div>
<div class="compose-row">
<div class="compose-actions">
<button class="icon-btn" aria-label="Attach file">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.49"/></svg>
</button>
</div>
<div class="compose-input-wrap">
<textarea class="compose-textarea" placeholder="Message #general..." rows="1" id="composeTextarea"></textarea>
</div>
<button class="send-btn" aria-label="Send message">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 2L11 13"/><path d="M22 2l-7 20-4-9-9-4 20-7z"/></svg>
</button>
</div>
</div>
</div>
<!-- Scroll to bottom FAB -->
<button class="scroll-bottom-fab" id="scrollFab" onclick="scrollToBottom()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M12 5v14"/><path d="m19 12-7 7-7-7"/></svg>
</button>
<script>
const feed = document.getElementById('messageFeed');
const fab = document.getElementById('scrollFab');
const textarea = document.getElementById('composeTextarea');
// Scroll to bottom on load
feed.scrollTop = feed.scrollHeight;
// Show/hide scroll FAB
feed.addEventListener('scroll', () => {
const atBottom = feed.scrollHeight - feed.scrollTop - feed.clientHeight < 100;
fab.style.display = atBottom ? 'none' : 'flex';
});
function scrollToBottom() {
feed.scrollTo({ top: feed.scrollHeight, behavior: 'smooth' });
}
// Auto-resize textarea
textarea.addEventListener('input', () => {
textarea.style.height = 'auto';
textarea.style.height = Math.min(textarea.scrollHeight, 120) + 'px';
});
// Simulate typing indicator
const typingEl = document.getElementById('typingIndicator');
setTimeout(() => {
typingEl.innerHTML = '';
}, 4000);
</script>
</body>
</html>

1182
messaging.html Normal file

File diff suppressed because it is too large Load diff

283
trophy-case.html Normal file
View file

@ -0,0 +1,283 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Zara's Trophy Case — Payfrit Portal Mockups</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #0f1117;
--card: #1a1d27;
--border: #2a2e3e;
--accent: #6c63ff;
--accent2: #00c9a7;
--text: #f0f0f5;
--muted: #8b8fa8;
--font: 'Segoe UI', system-ui, -apple-system, sans-serif;
}
body {
background: var(--bg);
color: var(--text);
font-family: var(--font);
min-height: 100vh;
padding: 0;
}
/* Hero header */
.hero {
background: linear-gradient(135deg, #1a1d27 0%, #2a1d4e 50%, #1a2d3e 100%);
padding: 48px 24px 40px;
text-align: center;
border-bottom: 1px solid var(--border);
}
.hero h1 {
font-size: 32px;
font-weight: 800;
letter-spacing: -0.5px;
margin-bottom: 8px;
}
.hero h1 span { color: var(--accent); }
.hero p {
color: var(--muted);
font-size: 16px;
max-width: 500px;
margin: 0 auto;
line-height: 1.5;
}
.hero .badge {
display: inline-block;
background: var(--accent);
color: #fff;
font-size: 12px;
font-weight: 700;
padding: 4px 12px;
border-radius: 20px;
margin-bottom: 12px;
text-transform: uppercase;
letter-spacing: 1px;
}
/* Grid */
.grid {
display: grid;
grid-template-columns: 1fr;
gap: 24px;
padding: 32px 20px;
max-width: 1200px;
margin: 0 auto;
}
@media (min-width: 768px) {
.grid { grid-template-columns: 1fr 1fr; }
}
@media (min-width: 1100px) {
.grid { grid-template-columns: 1fr 1fr 1fr; }
}
/* Card */
.card {
background: var(--card);
border: 1px solid var(--border);
border-radius: 16px;
overflow: hidden;
transition: transform 0.2s, box-shadow 0.2s;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 12px 40px rgba(108, 99, 255, 0.15);
}
.card-preview {
width: 100%;
height: 280px;
border: none;
background: #fff;
pointer-events: none;
transform-origin: top left;
}
.card-info {
padding: 16px 20px;
border-top: 1px solid var(--border);
}
.card-info h3 {
font-size: 17px;
font-weight: 700;
margin-bottom: 4px;
}
.card-info p {
color: var(--muted);
font-size: 13px;
line-height: 1.4;
margin-bottom: 12px;
}
.card-tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-bottom: 12px;
}
.tag {
font-size: 11px;
font-weight: 600;
padding: 3px 10px;
border-radius: 12px;
background: rgba(108, 99, 255, 0.15);
color: var(--accent);
}
.tag.green {
background: rgba(0, 201, 167, 0.15);
color: var(--accent2);
}
.tag.orange {
background: rgba(245, 166, 35, 0.15);
color: #f5a623;
}
.card-link {
display: inline-block;
background: var(--accent);
color: #fff;
text-decoration: none;
font-size: 13px;
font-weight: 700;
padding: 8px 20px;
border-radius: 8px;
transition: background 0.15s;
}
.card-link:hover { background: #5a52e0; }
/* Iframe wrapper with scale trick for preview */
.preview-wrap {
width: 100%;
height: 280px;
overflow: hidden;
position: relative;
background: #fff;
}
.preview-wrap iframe {
width: 375px;
height: 812px;
border: none;
transform: scale(0.48);
transform-origin: top left;
pointer-events: none;
}
/* Footer */
.footer {
text-align: center;
padding: 32px 20px;
color: var(--muted);
font-size: 13px;
border-top: 1px solid var(--border);
margin-top: 20px;
}
.footer strong { color: var(--accent); }
</style>
</head>
<body>
<div class="hero">
<div class="badge">@zara's workspace</div>
<h1><span>Trophy Case</span> — Portal Mockups</h1>
<p>All the consumer-facing mockups I've built for Payfrit. Mobile-first, vanilla HTML/CSS/JS. Click any card to open it full-screen.</p>
</div>
<div class="grid">
<!-- 1. Barcode Scanner -->
<div class="card">
<div class="preview-wrap">
<iframe src="scan.html" loading="lazy" sandbox></iframe>
</div>
<div class="card-info">
<h3>Barcode Scanner</h3>
<p>Camera-powered barcode scanning interface. Tap to scan, get instant product health scores right in the grocery aisle.</p>
<div class="card-tags">
<span class="tag">Mobile-First</span>
<span class="tag green">Camera API</span>
<span class="tag">Core Feature</span>
</div>
<a class="card-link" href="scan.html" target="_blank">Open Full Page →</a>
</div>
</div>
<!-- 2. User Dashboard -->
<div class="card">
<div class="preview-wrap">
<iframe src="dashboard.html" loading="lazy" sandbox></iframe>
</div>
<div class="card-info">
<h3>User Dashboard</h3>
<p>Personal health overview with recent scans, health score trends, quick actions, and personalized product recommendations.</p>
<div class="card-tags">
<span class="tag green">Health Scores</span>
<span class="tag">Analytics</span>
<span class="tag orange">Personalized</span>
</div>
<a class="card-link" href="dashboard.html" target="_blank">Open Full Page →</a>
</div>
</div>
<!-- 3. Product Compare -->
<div class="card">
<div class="preview-wrap">
<iframe src="compare.html" loading="lazy" sandbox></iframe>
</div>
<div class="card-info">
<h3>Product Compare</h3>
<p>Side-by-side product comparison with health scores, ingredients breakdown, nutritional highlights, and healthier alternatives.</p>
<div class="card-tags">
<span class="tag">Comparison</span>
<span class="tag green">Alternatives</span>
<span class="tag orange">Dark Theme</span>
</div>
<a class="card-link" href="compare.html" target="_blank">Open Full Page →</a>
</div>
</div>
<!-- 4. Messaging Hub -->
<div class="card">
<div class="preview-wrap">
<iframe src="messaging.html" loading="lazy" sandbox></iframe>
</div>
<div class="card-info">
<h3>Messaging Hub</h3>
<p>Slack-inspired messaging interface with channels, DMs, @mentions feed, and slide-out navigation. Desktop + mobile responsive.</p>
<div class="card-tags">
<span class="tag">Messaging</span>
<span class="tag green">Channels</span>
<span class="tag orange">Responsive</span>
</div>
<a class="card-link" href="messaging.html" target="_blank">Open Full Page →</a>
</div>
</div>
<!-- 5. Conversation View -->
<div class="card">
<div class="preview-wrap">
<iframe src="messaging-conversation.html" loading="lazy" sandbox></iframe>
</div>
<div class="card-info">
<h3>Conversation View</h3>
<p>Full chat thread view with message bubbles, timestamps, typing indicators, and message input. Native mobile app feel.</p>
<div class="card-tags">
<span class="tag">Chat UI</span>
<span class="tag green">Real-time</span>
<span class="tag">Mobile Native</span>
</div>
<a class="card-link" href="messaging-conversation.html" target="_blank">Open Full Page →</a>
</div>
</div>
</div>
<div class="footer">
Built by <strong>@zara</strong> · Payfrit User Portal · March 2026
</div>
</body>
</html>