UI enhancement

This commit is contained in:
debu-2 2026-10-03 22:28:20 +05:30
parent 45e3898acb
commit 23239c5cb6
5 changed files with 225 additions and 129 deletions

View file

@ -89,26 +89,34 @@ export default function MailRow({
aria-label={starred ? "Unstar message" : "Star message"}
aria-pressed={starred}
>
<Star size={15} fill={starred ? "currentColor" : "none"} />
<Star size={14} fill={starred ? "currentColor" : "none"} />
</button>
<SenderAvatar email={email} className="mailbox-avatar" />
<span className="mailbox-sender">
<div className="mailbox-content">
{unread && <i className="mailbox-unread-pip" aria-hidden="true" />}
{senderName}
</span>
<span className="mailbox-summary">
<strong>{subject}</strong>
{snippet && <span>{snippet}</span>}
{labels.map((label) => (
<em key={label.name} className={`mail-label mail-label-${label.tone}`}>{label.name}</em>
))}
{files > 0 && (
<em className="mail-attachment" title={`${files} attachment${files === 1 ? "" : "s"}`}>
<Paperclip size={12} /> {files > 1 ? files : ""}
</em>
<span className="mailbox-sender">{senderName}</span>
<span className="mailbox-subject">{subject}</span>
{snippet && (
<span className="mailbox-preview">
<span className="mailbox-preview-sep" aria-hidden="true">—</span>
{snippet}
</span>
)}
</span>
<time dateTime={timestamp || undefined}>{formatTimestamp(timestamp)}</time>
{(labels.length > 0 || files > 0) && (
<span className="mailbox-meta">
{labels.map((label) => (
<em key={label.name} className={`mail-label mail-label-${label.tone}`}>{label.name}</em>
))}
{files > 0 && (
<em className="mail-attachment" title={`${files} attachment${files === 1 ? "" : "s"}`}>
<Paperclip size={11} />
{files > 1 ? files : ""}
</em>
)}
</span>
)}
</div>
<time className="mailbox-time" dateTime={timestamp || undefined}>{formatTimestamp(timestamp)}</time>
<div className="mailbox-hover-actions">
<button type="button" title="Archive" aria-label="Archive" onClick={(event) => stop(event, () => onArchive?.(email))}><Archive size={14} /></button>
<button type="button" title="Snooze" aria-label="Snooze" onClick={(event) => stop(event, () => onSnooze?.(email))}><Clock3 size={14} /></button>

View file

@ -6,10 +6,16 @@ import {
CircleHelp,
Filter,
Inbox,
LayoutGrid,
Mail,
RefreshCw,
Search,
Settings,
Sparkles,
Sun,
Tag,
Users,
Wallet,
} from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react";
import {
@ -34,7 +40,14 @@ import {
} from "../utils/inboxSignals";
const PAGE_SIZE = 25;
const CATEGORIES = ["Primary", "Updates", "Promotions", "Social", "Finance", "All Mail"];
const CATEGORY_TABS = [
{ name: "Primary", icon: LayoutGrid },
{ name: "Updates", icon: Bell },
{ name: "Promotions", icon: Tag },
{ name: "Social", icon: Users },
{ name: "Finance", icon: Wallet },
{ name: "All Mail", icon: Mail },
];
function resultPage(result) {
if (Array.isArray(result)) return { items: result, nextCursor: null };
@ -343,41 +356,47 @@ export default function MailboxView({
return (
<section className="mailbox-workspace">
<header className="workspace-header">
<label className="workspace-search">
<Search size={16} />
<input
id="workspace-search"
type="search"
value={filters.query}
onChange={(event) => changeFilter("query", event.target.value)}
placeholder="Search emails, people, or ask your agent..."
/>
<kbd>Ctrl K</kbd>
</label>
<button
className={`workspace-filter${filtersOpen || hasFilters ? " active" : ""}`}
type="button"
onClick={() => setFiltersOpen((open) => !open)}
aria-expanded={filtersOpen}
aria-label="Advanced filters"
>
<Filter size={16} />
</button>
<button className="workspace-ask" type="button" onClick={() => runQuickAction("summarize")}>
<Sparkles size={15} /> Ask Agent
</button>
<button type="button" className="workspace-icon" aria-label="Refresh" onClick={load}><RefreshCw size={16} /></button>
<button type="button" className="workspace-icon" aria-label="Notifications"><Bell size={16} /></button>
<button type="button" className="workspace-icon" aria-label="Help"><CircleHelp size={16} /></button>
<button type="button" className="workspace-icon" aria-label="Settings" onClick={onOpenSettings}><Settings size={16} /></button>
<button type="button" className="workspace-account" onClick={onLogout} title="Log out">
<span>{initials || "AG"}</span>
<div>
<strong>{account?.first_name} {account?.last_name}</strong>
<small>{account?.email}</small>
</div>
<ChevronDown size={14} />
</button>
<div className="workspace-zone workspace-zone-command">
<label className="workspace-search">
<Search size={16} />
<input
id="workspace-search"
type="search"
value={filters.query}
onChange={(event) => changeFilter("query", event.target.value)}
placeholder="Search emails, people, or ask your agent..."
/>
<kbd>Ctrl K</kbd>
</label>
</div>
<div className="workspace-zone workspace-zone-actions">
<button
className={`workspace-filter${filtersOpen || hasFilters ? " active" : ""}`}
type="button"
onClick={() => setFiltersOpen((open) => !open)}
aria-expanded={filtersOpen}
aria-label="Advanced filters"
>
<Filter size={16} />
</button>
<button className="workspace-ask" type="button" onClick={() => runQuickAction("summarize")}>
<Sparkles size={15} /> Ask Agent
</button>
</div>
<div className="workspace-zone workspace-zone-account">
<button type="button" className="workspace-icon" aria-label="Refresh" onClick={load}><RefreshCw size={16} /></button>
<button type="button" className="workspace-icon" aria-label="Theme"><Sun size={16} /></button>
<button type="button" className="workspace-icon" aria-label="Help"><CircleHelp size={16} /></button>
<button type="button" className="workspace-icon" aria-label="Settings" onClick={onOpenSettings}><Settings size={16} /></button>
<button type="button" className="workspace-account" onClick={onLogout} title="Log out">
<span>{initials || "AG"}</span>
<div>
<strong>{account?.first_name} {account?.last_name}</strong>
<small>{account?.email}</small>
</div>
<ChevronDown size={14} />
</button>
</div>
</header>
<div className="mailbox-stage">
@ -386,7 +405,7 @@ export default function MailboxView({
<div className="mailbox-list-pane">
{mailbox === "inbox" && (
<div className="mailbox-categories" role="tablist" aria-label="Inbox categories">
{CATEGORIES.map((name) => (
{CATEGORY_TABS.map(({ name, icon: Icon }) => (
<button
key={name}
type="button"
@ -395,8 +414,13 @@ export default function MailboxView({
className={category === name ? "active" : ""}
onClick={() => setCategory(name)}
>
{name}
{name !== "All Mail" && counts[name] > 0 && <span>{counts[name]}</span>}
<Icon size={14} strokeWidth={1.75} />
<span className="mailbox-category-label">{name}</span>
{name !== "All Mail" && (
<span className={`mailbox-category-count${counts[name] > 0 ? "" : " muted"}`}>
{counts[name] || 0}
</span>
)}
</button>
))}
<small className="mailbox-range">

View file

@ -1,12 +1,17 @@
import { useState } from "react";
import { senderAvatar } from "../utils/senderAvatar";
export default function SenderAvatar({ email, className = "" }) {
const { name, initials, color, imageUrl } = senderAvatar(email);
const { initials, color, imageUrl, brandUrl, isBrand } = senderAvatar(email);
const [broken, setBroken] = useState(false);
const src = imageUrl || brandUrl;
if (imageUrl) {
if (src && !broken) {
return (
<span className={`sender-avatar${className ? ` ${className}` : ""}`}>
<img src={imageUrl} alt="" aria-hidden="true" />
<span
className={`sender-avatar${isBrand ? " sender-avatar-brand" : ""}${className ? ` ${className}` : ""}`}
>
<img src={src} alt="" aria-hidden="true" onError={() => setBroken(true)} />
</span>
);
}

View file

@ -181,46 +181,25 @@ h1 em { color:var(--coral); font-style:normal; font-weight:500; }
.dashboard-scrim { display:none; }
.dashboard-notice { padding:12px 16px; position:fixed; right:24px; bottom:24px; z-index:30; border-radius:11px; background:var(--ink); color:white; box-shadow:0 14px 35px #08082138; font-size:12px; font-weight:600; }
.mailbox-workspace { min-height:calc(100vh - 56px); display:grid; grid-template-columns:minmax(0,70fr) minmax(0,30fr); align-items:stretch; gap:22px; }
.mailbox-main-column { min-width:0; display:flex; flex-direction:column; }
.mailbox-shell { min-height:calc(95vh - 158px); margin-top:24px; overflow:hidden; border:1px solid #e6e2de; border-radius:18px; background:white; box-shadow:0 1px 2px #08082106,0 12px 40px #0808210a; }
.mailbox-list-pane { min-width:0; min-height:calc(95vh - 158px); position:relative; display:flex; flex-direction:column; }
.mailbox-list-pane { min-width:0; position:relative; }
.mailbox-notes-pane { min-width:0; min-height:calc(100vh - 56px); max-height:calc(100vh - 56px); overflow:hidden; display:flex; flex-direction:column; border:1px solid #e6e2de; border-radius:18px; background:#fffefd; box-shadow:0 1px 2px #08082106,0 14px 42px #08082110; }
.mailbox-toolbar { min-height:62px; padding:11px 15px; display:flex; align-items:center; gap:8px; border-bottom:1px solid #ebe7e3; background:#fffefd; }
.mailbox-toolbar>label { height:38px; min-width:160px; flex:1; padding:0 12px; display:flex; align-items:center; gap:8px; border:1px solid #e8e3df; border-radius:10px; color:#aaa4ac; background:#fcfbfa; }
.mailbox-toolbar input { min-width:0; width:100%; border:0; outline:0; background:transparent; color:var(--ink); font-size:12px; }
.mailbox-toolbar>button,.mail-detail-toolbar button { height:36px; padding:0 10px; display:flex; align-items:center; justify-content:center; gap:6px; border:0; border-radius:9px; background:transparent; color:#77717a; font-size:10px; font-weight:700; }
.mailbox-toolbar>button:hover,.mailbox-toolbar>button.active,.mail-detail-toolbar button:hover { background:#fff0eb; color:#bd5947; }
.mailbox-filters { padding:13px 15px; display:grid; grid-template-columns:1fr 1.5fr 1fr 1fr; gap:10px; border-bottom:1px solid var(--line); background:#fcfaf9; }
.mailbox-filters { padding:10px 12px; display:grid; grid-template-columns:1fr 1.5fr 1fr 1fr; gap:10px; border-bottom:1px solid #ddd8d2; background:#e3e0db; }
.mailbox-filters label { display:grid; gap:5px; color:#76717a; font-size:9px; font-weight:700; text-transform:uppercase; letter-spacing:.5px; }
.mailbox-filters input,.mailbox-filters select { min-width:0; height:34px; padding:0 9px; border:1px solid #e4dfdc; border-radius:8px; outline:0; background:white; color:#393745; font-size:10px; text-transform:none; }
.mailbox-filters input:focus,.mailbox-filters select:focus { border-color:#f0ad9f; box-shadow:0 0 0 2px #ff806c12; }
.mailbox-rows { min-height:120px; flex:0 0 auto; }
.mailbox-row { width:100%; min-height:62px; padding:10px 15px 10px 13px; display:grid; grid-template-columns:20px 36px minmax(85px,140px) minmax(0,1fr) auto 28px; align-items:center; gap:10px 12px; border:0; border-bottom:1px solid #ebe7e3; border-left:2px solid transparent; background:white; color:#4a4652; text-align:left; cursor:pointer; transition:background .15s ease,border-color .15s ease; }
.mailbox-row:focus-visible { outline:2px solid #f0ad9f; outline-offset:-2px; }
.sender-avatar { width:36px; height:36px; border-radius:50%; display:grid; place-items:center; color:white; font-size:11px; font-weight:700; letter-spacing:.02em; flex-shrink:0; user-select:none; line-height:1; }
.sender-avatar { width:30px; height:30px; border-radius:50%; display:grid; place-items:center; color:white; font-size:10px; font-weight:700; letter-spacing:.02em; flex-shrink:0; user-select:none; line-height:1; }
.sender-avatar img { width:100%; height:100%; border-radius:50%; object-fit:cover; display:block; }
.mailbox-avatar { align-self:center; }
.mailbox-row:hover { background:#faf8f6; border-left-color:var(--coral); }
.mailbox-row.selected { background:#fff8f5; border-left-color:var(--coral); }
.mailbox-read-dot { display:grid; place-items:center; color:#9a959f; }
.mailbox-read-dot i { width:7px; height:7px; display:block; border-radius:50%; background:var(--coral); box-shadow:0 0 0 3px #fff0eb; }
.mailbox-sender,.mailbox-summary strong,.mailbox-summary span { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.mailbox-sender { font-size:12px; font-weight:500; color:#3a3642; }
.mailbox-summary { min-width:0; display:flex; gap:6px; align-items:baseline; font-size:12px; }
.mailbox-summary strong { min-width:0; max-width:100%; flex:0 1 auto; font-weight:500; color:#2a2733; }
.mailbox-summary span { min-width:0; flex:1 1 0; color:#7a7580; font-weight:400; }
.mailbox-summary b { color:#9a959f; font-weight:400; }
.mailbox-row.unread .mailbox-sender,.mailbox-row.unread .mailbox-summary strong { color:var(--ink); font-weight:700; }
.mailbox-row.unread .mailbox-summary span { color:#5c5763; font-weight:400; }
.mailbox-row time { color:#8f8a94; font-size:10px; white-space:nowrap; }
.mailbox-star { width:28px; height:28px; padding:0; display:grid; place-items:center; border:0; border-radius:8px; background:transparent; color:#c5c0c8; cursor:pointer; transition:color .15s ease,background .15s ease; }
.mailbox-star:hover,.mailbox-star.active { color:#e8a317; background:#fff8e8; }
.mailbox-row.starred .mailbox-star { color:#e8a317; }
.mailbox-row:focus-visible { outline:2px solid #f0ad9f; outline-offset:-2px; }
.mailbox-list-footer { margin-top:auto; padding:18px 20px 20px; display:flex; align-items:center; justify-content:space-between; gap:16px; border-top:1px solid #ebe7e3; background:linear-gradient(180deg,#fcfbfa 0%,#f8f7f5 100%); color:#8f8a94; font-size:11px; }
.mailbox-list-footer p { margin:0; color:#5c5763; font-weight:600; }
.mailbox-list-footer span { color:#a09aa4; font-size:10px; }
.mailbox-state { min-height:360px; padding:30px; display:flex; flex-direction:column; align-items:center; justify-content:center; color:#8d8891; text-align:center; font-size:11px; }
.mailbox-state { min-height:220px; padding:30px; display:flex; flex-direction:column; align-items:center; justify-content:center; color:#8d8891; text-align:center; font-size:11px; background:#ebe8e3; }
.mailbox-state.empty span { width:46px; height:46px; margin-bottom:13px; display:grid; place-items:center; border-radius:14px; color:var(--coral); background:#fff0eb; }
.mailbox-state.empty strong { color:#393541; font:600 15px "Manrope"; }
.mailbox-state p { margin:7px 0; }
@ -486,58 +465,79 @@ h1 em { color:var(--coral); font-style:normal; font-weight:500; }
.dashboard-profile strong { color:#fff; }
.dashboard-profile small,.dashboard-profile button { color:#9a95a8; }
.dashboard-profile button:hover { background:#ffffff12; color:#fff; }
.dashboard-content { padding:14px 16px 16px; background:#f7f6f4; }
.dashboard-content { padding:14px 16px 16px; background:#e4e1dc; }
.dashboard-content-heading h1 { font-size:clamp(28px,3vw,36px); letter-spacing:-1.4px; }
.mailbox-workspace { min-height:calc(100vh - 28px); display:flex; flex-direction:column; gap:12px; }
.workspace-header { min-height:58px; display:flex; align-items:center; gap:8px; }
.workspace-search { height:42px; min-width:0; flex:1; padding:0 8px 0 14px; display:flex; align-items:center; gap:8px; border:1px solid #e6e1dc; border-radius:14px; background:#fff; color:#9a958f; box-shadow:0 1px 2px #08082108; }
.workspace-header { min-height:58px; display:grid; grid-template-columns:minmax(0,1fr) auto auto; gap:0 20px; align-items:center; }
.workspace-zone { display:flex; align-items:center; min-width:0; }
.workspace-zone-command { padding-right:8px; }
.workspace-zone-command .workspace-search { width:min(100%,620px); max-width:620px; }
.workspace-zone-actions { gap:10px; padding:0 16px; border-left:1px solid #d5d0ca; border-right:1px solid #d5d0ca; }
.workspace-zone-account { gap:6px; padding-left:4px; }
.workspace-search { height:40px; min-width:0; padding:0 8px 0 14px; display:flex; align-items:center; gap:8px; border:1px solid #d5d0ca; border-radius:12px; background:#f8f7f5; color:#9a958f; box-shadow:none; }
.workspace-search input { min-width:0; width:100%; border:0; outline:0; background:transparent; color:#241f2b; font-size:13px; }
.workspace-search kbd { padding:3px 7px; border:1px solid #eee8e3; border-radius:7px; background:#faf8f6; color:#8d8792; font:600 10px "DM Sans"; white-space:nowrap; }
.workspace-filter,.workspace-icon { width:38px; height:38px; display:grid; place-items:center; border:0; border-radius:12px; background:transparent; color:#6f6a74; }
.workspace-filter.active,.workspace-icon:hover,.workspace-filter:hover { background:#fff; color:#241f2b; }
.workspace-ask { height:38px; padding:0 14px; display:inline-flex; align-items:center; gap:7px; border:1px solid #ffd3c8; border-radius:12px; background:#fff7f4; color:#d26552; font-size:13px; font-weight:700; white-space:nowrap; }
.workspace-ask:hover { background:#fff1eb; }
.workspace-account { margin-left:4px; padding:4px 8px 4px 4px; display:flex; align-items:center; gap:8px; border:0; border-radius:14px; background:transparent; color:#6d6872; }
.workspace-account:hover { background:#fff; }
.workspace-search kbd { padding:3px 7px; border:1px solid #e8e3de; border-radius:7px; background:#f2f0ec; color:#8d8792; font:600 10px "DM Sans"; white-space:nowrap; }
.workspace-filter,.workspace-icon { width:36px; height:36px; display:grid; place-items:center; border:0; border-radius:10px; background:transparent; color:#6f6a74; }
.workspace-filter.active,.workspace-icon:hover,.workspace-filter:hover { background:#f8f6f3; color:#241f2b; }
.workspace-ask { height:40px; padding:0 16px; display:inline-flex; align-items:center; gap:8px; border:1px solid #ffb09c; border-radius:11px; background:linear-gradient(180deg,#fff8f5 0%,#ffefe8 100%); color:#cf4f36; font-size:13px; font-weight:700; white-space:nowrap; box-shadow:0 2px 10px #ff6a5224, inset 0 1px 0 #ffffffaa; }
.workspace-ask:hover { background:linear-gradient(180deg,#fff3ee 0%,#ffe8df 100%); box-shadow:0 3px 12px #ff6a5230; }
.workspace-account { margin-left:2px; padding:4px 8px 4px 4px; display:flex; align-items:center; gap:8px; border:0; border-radius:12px; background:transparent; color:#6d6872; }
.workspace-account:hover { background:#f8f6f3; }
.workspace-account>span { width:34px; height:34px; display:grid; place-items:center; border-radius:50%; background:#6d4aff; color:#fff; font-size:11px; font-weight:700; }
.workspace-account div { min-width:0; display:grid; text-align:left; }
.workspace-account strong,.workspace-account small { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.workspace-account strong { color:#241f2b; font-size:12px; }
.workspace-account small { color:#8d8792; font-size:10px; max-width:150px; }
.mailbox-stage { min-height:0; flex:1; display:grid; grid-template-columns:minmax(0,1fr) 300px; gap:14px; align-items:start; }
.mailbox-shell { min-height:calc(100vh - 110px); margin-top:0; border-radius:16px; box-shadow:0 1px 2px #08082106,0 10px 30px #08082108; }
.mailbox-list-pane { min-height:calc(100vh - 110px); }
.mailbox-categories { min-height:48px; padding:0 8px; display:flex; align-items:center; gap:2px; border-bottom:1px solid #efeae6; overflow:auto; }
.mailbox-categories button { height:48px; padding:0 12px; position:relative; display:inline-flex; align-items:center; gap:7px; border:0; background:transparent; color:#6e6974; font-size:13px; font-weight:600; white-space:nowrap; }
.mailbox-categories button span { min-width:16px; height:16px; padding:0 5px; display:grid; place-items:center; border-radius:99px; background:#ff6a52; color:#fff; font-size:10px; }
.mailbox-categories button.active { color:#e15b45; }
.mailbox-categories button.active:after { content:""; position:absolute; left:12px; right:12px; bottom:0; height:2px; border-radius:2px; background:#ff6a52; }
.mailbox-range { margin-left:auto; padding-right:10px; color:#9a948e; font-size:11px; white-space:nowrap; }
.mailbox-row { min-height:52px; padding:6px 12px; grid-template-columns:18px 22px 34px minmax(120px,180px) minmax(0,1fr) auto; gap:8px 10px; border-left:0; font-size:13px; }
.mailbox-row.unread { background:#fff; }
.mailbox-row:not(.unread) { background:#fbfaf8; }
.mailbox-row:not(.unread) .mailbox-sender,.mailbox-row:not(.unread) .mailbox-summary strong { font-weight:500; color:#5c5764; }
.mailbox-check { width:15px; height:15px; margin:0; accent-color:#ff6a52; }
.mailbox-unread-pip { width:7px; height:7px; margin-right:6px; display:inline-block; border-radius:50%; background:#ff6a52; vertical-align:middle; }
.mailbox-sender { display:flex; align-items:center; font-size:13px; }
.mailbox-summary { gap:8px; align-items:center; }
.mailbox-summary span { color:#8a847e; }
.mail-label,.mail-attachment { padding:2px 7px; border-radius:99px; font-size:10px; font-style:normal; font-weight:700; white-space:nowrap; }
.mailbox-stage { min-height:0; flex:1; display:grid; grid-template-columns:minmax(0,1fr) minmax(268px,286px); gap:16px; align-items:start; }
.mailbox-shell { min-height:0; max-height:calc(100vh - 110px); margin-top:0; overflow:hidden; border:1px solid #dbd6d0; border-radius:12px; background:#f0eeea; box-shadow:0 1px 2px #08082105; }
.mailbox-list-pane { min-height:0; max-height:calc(100vh - 110px); display:flex; flex-direction:column; background:#f0eeea; border-radius:12px; }
.mailbox-categories { min-height:42px; padding:4px 8px 0; display:flex; align-items:flex-end; gap:2px; border-bottom:1px solid #ddd8d2; background:#e8e5e0; overflow:auto; }
.mailbox-categories button { height:38px; padding:0 10px 8px; position:relative; display:inline-flex; align-items:center; gap:6px; border:0; border-radius:8px 8px 0 0; background:transparent; color:#6b666f; font-size:12px; font-weight:600; white-space:nowrap; }
.mailbox-categories button svg { color:#8a858f; flex-shrink:0; }
.mailbox-category-label { line-height:1; }
.mailbox-category-count { min-width:17px; height:17px; padding:0 5px; display:grid; place-items:center; border-radius:99px; background:#ff6a52; color:#fff; font-size:9px; font-weight:700; line-height:1; }
.mailbox-category-count.muted { background:#d8d3cd; color:#7a7580; font-weight:600; }
.mailbox-categories button.active { color:#d85a44; background:#f3f1ed; }
.mailbox-categories button.active svg { color:#e15b45; }
.mailbox-categories button.active:after { content:""; position:absolute; left:8px; right:8px; bottom:0; height:3px; border-radius:3px 3px 0 0; background:linear-gradient(90deg,#ff8a72,#ff6a52); }
.mailbox-range { margin-left:auto; padding-right:8px; color:#9a948e; font-size:10px; font-weight:500; white-space:nowrap; }
.mailbox-rows { flex:1; min-height:0; overflow:auto; background:#ebe8e3; }
.mailbox-row { width:100%; min-height:44px; padding:0 10px 0 8px; display:grid; grid-template-columns:15px 18px 30px minmax(0,1fr) auto; align-items:center; gap:7px; border:0; border-bottom:1px solid #00000007; background:transparent; color:#4a4652; text-align:left; cursor:pointer; transition:background .12s ease; }
.mailbox-row.unread { background:#faf9f7; }
.mailbox-row:not(.unread) { background:transparent; }
.mailbox-row:hover { background:#e3e0db; }
.mailbox-row.selected { background:#fff1ec; box-shadow:inset 3px 0 0 #ff7a62; }
.mailbox-check { width:14px; height:14px; margin:0; accent-color:#ff6a52; }
.mailbox-star { width:18px; height:18px; padding:0; display:grid; place-items:center; border:0; border-radius:6px; background:transparent; color:#c2bdc6; }
.mailbox-star:hover,.mailbox-star.active { color:#e8a317; background:#fff8e8; }
.mailbox-row.starred .mailbox-star { color:#e8a317; }
.sender-avatar.mailbox-avatar { width:30px; height:30px; font-size:10px; font-weight:700; }
.sender-avatar-brand { background:#fff!important; border:1px solid #e8e4df; padding:4px; }
.sender-avatar-brand img { object-fit:contain; }
.mailbox-content { min-width:0; display:flex; align-items:center; gap:7px; overflow:hidden; }
.mailbox-unread-pip { width:6px; height:6px; flex-shrink:0; border-radius:50%; background:#4f8cff; box-shadow:0 0 0 2px #4f8cff22; }
.mailbox-sender { flex:0 1 auto; max-width:28%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:12.5px; }
.mailbox-subject { flex:0 1 auto; max-width:30%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:12.5px; }
.mailbox-preview { flex:1 1 auto; min-width:48px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-size:12.5px; font-weight:400; color:#9a959f; }
.mailbox-preview-sep { margin-right:4px; color:#c2bdc6; }
.mailbox-meta { flex:0 0 auto; display:inline-flex; align-items:center; gap:4px; }
.mailbox-row.unread .mailbox-sender,.mailbox-row.unread .mailbox-subject { color:#1a1628; font-weight:600; }
.mailbox-row:not(.unread) .mailbox-sender,.mailbox-row:not(.unread) .mailbox-subject { color:#5a5562; font-weight:500; }
.mailbox-time { color:#a39ea8; font-size:10.5px; font-weight:400; white-space:nowrap; }
.mail-label,.mail-attachment { padding:1px 6px; border-radius:99px; font-size:9px; font-style:normal; font-weight:600; white-space:nowrap; }
.mail-label-rose { background:#ffe8e3; color:#c45a48; }
.mail-label-violet { background:#efe9ff; color:#6d54b8; }
.mail-label-blue { background:#e7f1ff; color:#3d6eae; }
.mail-label-amber { background:#fff4d8; color:#9a7420; }
.mail-label-green { background:#e7f6ee; color:#2f7a52; }
.mail-attachment { display:inline-flex; align-items:center; gap:3px; background:#f3f1ef; color:#6d6872; }
.mailbox-hover-actions { display:none; align-items:center; gap:2px; }
.mail-attachment { display:inline-flex; align-items:center; gap:2px; background:#e8e5e0; color:#7a7580; font-weight:500; }
.mailbox-hover-actions { display:none; align-items:center; gap:1px; }
.mailbox-row:hover .mailbox-hover-actions,.mailbox-row:focus-within .mailbox-hover-actions { display:flex; }
.mailbox-row:hover time,.mailbox-row:focus-within time { display:none; }
.mailbox-hover-actions button { width:28px; height:28px; display:grid; place-items:center; border:0; border-radius:8px; background:#fff; color:#6d6872; box-shadow:0 1px 2px #08082110; }
.mailbox-row:hover .mailbox-time,.mailbox-row:focus-within .mailbox-time { display:none; }
.mailbox-hover-actions button { width:26px; height:26px; display:grid; place-items:center; border:0; border-radius:7px; background:#f8f6f3; color:#6d6872; box-shadow:0 1px 2px #0808210c; }
.mailbox-hover-actions button:hover { color:#e15b45; background:#fff4f1; }
.mailbox-star { width:22px; height:22px; }
.sender-avatar.mailbox-avatar { width:32px; height:32px; font-size:11px; }
.mailbox-rail { min-width:0; display:flex; flex-direction:column; gap:12px; max-height:calc(100vh - 96px); position:sticky; top:14px; }
.agent-panel { padding:16px; border:1px solid #ece7e2; border-radius:16px; background:#fff; box-shadow:0 8px 24px #08082108; }
.agent-panel { padding:16px; border:1px solid #e4e0da; border-radius:12px; background:#faf9f7; box-shadow:0 1px 2px #08082105; }
.agent-panel-head { display:flex; align-items:center; gap:10px; }
.agent-panel-mark { width:32px; height:32px; display:grid; place-items:center; border-radius:10px; background:#fff1eb; color:#e15b45; }
.agent-panel-head h2 { margin:0; font:700 15px "Manrope"; letter-spacing:-.3px; }
@ -559,12 +559,15 @@ h1 em { color:var(--coral); font-style:normal; font-weight:500; }
.agent-quick div { display:grid; grid-template-columns:1fr 1fr; gap:8px; }
.agent-quick button { min-height:36px; padding:0 8px; display:flex; align-items:center; justify-content:center; gap:6px; border:1px solid #eee8e3; border-radius:10px; background:#fff; color:#4d4852; font-size:11px; font-weight:600; }
.agent-quick button:hover { border-color:#ffd3c8; color:#e15b45; background:#fff8f5; }
.mailbox-notes-pane.compact { min-height:0; max-height:240px; border-radius:16px; }
.mailbox-notes-pane.compact { min-height:0; max-height:240px; border-radius:12px; border-color:#e4e0da; box-shadow:0 1px 2px #08082105; }
.mailbox-notes-pane.compact .sticky-notes-header { min-height:44px; padding:8px 12px; }
.mailbox-notes-pane.compact .sticky-notes-body { padding:10px; gap:8px; }
.mailbox-notes-pane.compact .sticky-note { height:92px; min-height:92px; padding:12px 12px 8px; }
.mailbox-notes-pane.compact .sticky-note-add { min-height:64px; }
@media(max-width:1100px) {
.workspace-header { grid-template-columns:1fr; gap:10px; }
.workspace-zone-actions { padding:0; border:0; }
.workspace-zone-account { padding-left:0; flex-wrap:wrap; }
.workspace-account div,.workspace-account svg { display:none; }
.mailbox-stage { grid-template-columns:minmax(0,1fr); }
.mailbox-rail { position:static; max-height:none; }
@ -572,10 +575,11 @@ h1 em { color:var(--coral); font-style:normal; font-weight:500; }
@media(max-width:900px) {
.dashboard-sidebar { background:linear-gradient(180deg,#1c1730,#161226); }
.mailbox-notes-pane { display:flex; }
.mailbox-row { grid-template-columns:16px 20px 32px minmax(0,1fr) auto; }
.mailbox-summary { grid-column:3/5; }
.mailbox-sender { grid-column:3; }
.mailbox-row { grid-template-columns:14px 16px 28px minmax(0,1fr) auto; min-height:52px; }
.mailbox-content { flex-wrap:wrap; row-gap:2px; }
.mailbox-sender,.mailbox-subject { max-width:46%; }
.mailbox-preview { flex-basis:100%; max-width:100%; }
.mailbox-hover-actions { display:none!important; }
.mailbox-row:hover time { display:block; }
.mailbox-row:hover .mailbox-time { display:block; }
}
@media(prefers-reduced-motion:reduce) { * { scroll-behavior:auto!important; transition:none!important; } }

View file

@ -2,6 +2,41 @@ import colorMap from "../data/az_2_letter_color_map.json";
const FALLBACK_COLOR = "#5c5763";
const PERSONAL_DOMAINS = new Set([
"gmail.com",
"googlemail.com",
"yahoo.com",
"hotmail.com",
"outlook.com",
"live.com",
"icloud.com",
"me.com",
"proton.me",
"protonmail.com",
"aol.com",
]);
const BRAND_MATCHERS = [
{ tokens: ["google", "google cloud"], domain: "google.com" },
{ tokens: ["github"], domain: "github.com" },
{ tokens: ["twilio"], domain: "twilio.com" },
{ tokens: ["meta", "facebook", "instagram"], domain: "meta.com" },
{ tokens: ["microsoft", "azure", "office 365"], domain: "microsoft.com" },
{ tokens: ["amazon", "aws"], domain: "amazon.com" },
{ tokens: ["apple"], domain: "apple.com" },
{ tokens: ["stripe"], domain: "stripe.com" },
{ tokens: ["slack"], domain: "slack.com" },
{ tokens: ["notion"], domain: "notion.so" },
{ tokens: ["linkedin"], domain: "linkedin.com" },
{ tokens: ["dropbox"], domain: "dropbox.com" },
{ tokens: ["zoom"], domain: "zoom.us" },
{ tokens: ["atlassian", "jira"], domain: "atlassian.com" },
];
function faviconUrl(domain) {
return `https://www.google.com/s2/favicons?domain=${domain}&sz=64`;
}
export function addressText(sender) {
if (typeof sender === "string") return sender;
return sender?.email || sender?.address || "";
@ -17,7 +52,6 @@ export function senderDisplayName(email) {
return displayName?.trim() || address.split("@")[0] || "Unknown sender";
}
/** Two-letter key for color lookup: first + last word, or first two chars. */
export function initialsFromName(name) {
const cleaned = String(name || "")
.trim()
@ -46,18 +80,39 @@ export function colorForInitials(initials) {
return colorMap[key] || FALLBACK_COLOR;
}
/** Registered-user avatar URL hook — wire when profile photos exist. */
export function senderAvatarUrl(email) {
return email.sender_avatar_url || email.sender?.avatar_url || null;
}
export function brandLogoUrl(email) {
const address = addressText(email.sender || email.from).toLowerCase();
const name = senderDisplayName(email).toLowerCase();
const emailDomain = address.split("@")[1] || "";
const haystack = `${name} ${address} ${emailDomain}`;
for (const brand of BRAND_MATCHERS) {
if (brand.tokens.some((token) => haystack.includes(token))) {
return faviconUrl(brand.domain);
}
}
if (emailDomain && !PERSONAL_DOMAINS.has(emailDomain)) {
return faviconUrl(emailDomain);
}
return null;
}
export function senderAvatar(email) {
const name = senderDisplayName(email);
const initials = initialsFromName(name);
const profileUrl = senderAvatarUrl(email);
const brandUrl = profileUrl ? null : brandLogoUrl(email);
return {
name,
initials,
color: colorForInitials(initials),
imageUrl: senderAvatarUrl(email),
imageUrl: profileUrl,
brandUrl,
isBrand: Boolean(brandUrl),
};
}