payfrit-portal/messaging-conversation.html
Zara 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

625 lines
19 KiB
HTML

<!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>