diff --git a/src/components/SidebarDrawer.tsx b/src/components/SidebarDrawer.tsx
index 94bf05e8..d7342572 100644
--- a/src/components/SidebarDrawer.tsx
+++ b/src/components/SidebarDrawer.tsx
@@ -1459,10 +1459,9 @@ function makeStyles(c: ThemePalette) {
color: c.text,
},
rowLabelAccount: {
- ...typography.caption,
- fontWeight: '600',
- textTransform: 'uppercase',
- letterSpacing: 0.5,
+ fontSize: 13,
+ lineHeight: 18,
+ fontWeight: '500',
color: c.textSecondary,
},
counts: {
diff --git a/src/components/email/QuickReplyBox.tsx b/src/components/email/QuickReplyBox.tsx
index 2dd0593d..cadb8fff 100644
--- a/src/components/email/QuickReplyBox.tsx
+++ b/src/components/email/QuickReplyBox.tsx
@@ -155,7 +155,7 @@ export function QuickReplyBox({ email, jmapAccountId, onMoreOptions, onSent, onF
onFocus={onFocus}
onBlur={onBlur}
placeholder={t('email_viewer.quick_reply_placeholder', 'Write a quick reply...')}
- placeholderTextColor={c.textMuted}
+ placeholderTextColor={c.mutedForeground}
style={styles.input}
multiline
editable={!sending}
@@ -189,7 +189,7 @@ function makeStyles(c: ThemePalette) {
borderWidth: 1,
borderColor: c.border,
borderRadius: radius.md,
- backgroundColor: c.surface,
+ backgroundColor: c.background,
padding: spacing.sm,
gap: spacing.sm,
},
@@ -211,10 +211,10 @@ function makeStyles(c: ThemePalette) {
gap: spacing.xs,
backgroundColor: c.primary,
paddingHorizontal: spacing.md,
- paddingVertical: 6,
- borderRadius: radius.full,
+ minHeight: 36,
+ borderRadius: radius.sm,
},
sendDisabled: { opacity: 0.5 },
- sendText: { ...typography.caption, color: c.primaryForeground, fontWeight: '600' },
+ sendText: { ...typography.bodyMedium, color: c.primaryForeground },
});
}
diff --git a/src/components/email/ThreadMessageCard.tsx b/src/components/email/ThreadMessageCard.tsx
index b7718f36..a70b0e28 100644
--- a/src/components/email/ThreadMessageCard.tsx
+++ b/src/components/email/ThreadMessageCard.tsx
@@ -74,16 +74,16 @@ export function ThreadMessageCard({
{!bare && (
-
-
+
+
{t('email_viewer.reply', 'Reply')}
-
-
+
+
{t('email_viewer.reply_all', 'Reply All')}
-
-
+
+
{t('email_viewer.forward', 'Forward')}
@@ -159,13 +159,14 @@ function makeStyles(c: ThemePalette) {
flexDirection: 'row',
alignItems: 'center',
gap: spacing.xs,
+ // Webmail thread card: outline buttons, h-9, rounded-md.
paddingHorizontal: spacing.md,
- paddingVertical: 6,
- borderRadius: radius.full,
+ minHeight: 36,
+ borderRadius: radius.sm,
borderWidth: 1,
borderColor: c.border,
- backgroundColor: c.surface,
+ backgroundColor: 'transparent',
},
- actionLabel: { ...typography.caption, color: c.textSecondary },
+ actionLabel: { ...typography.bodyMedium, color: c.text },
});
}
diff --git a/src/screens/EmailListScreen.tsx b/src/screens/EmailListScreen.tsx
index c7d3a10b..6389a1fd 100644
--- a/src/screens/EmailListScreen.tsx
+++ b/src/screens/EmailListScreen.tsx
@@ -11,7 +11,7 @@ import {
} from '../components/icons';
import { useNavigation } from '@react-navigation/native';
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
-import { spacing, radius, typography, componentSizes, type ThemePalette } from '../theme/tokens';
+import { spacing, radius, typography, componentSizes, mix, type ThemePalette } from '../theme/tokens';
import { useColors, useResolvedTheme } from '../theme/colors';
import { useTypography, useDensity } from '../theme/dynamic';
import SidebarDrawer from '../components/SidebarDrawer';
@@ -168,6 +168,7 @@ const EmailRow = React.memo(function EmailRow({
style={({ pressed }) => [
styles.emailRow,
{ paddingVertical: density.rowPaddingVertical },
+ unread && styles.emailRowUnread,
pressed && styles.emailRowPressed,
active && styles.emailRowActive,
selected && styles.emailRowSelected,
@@ -2002,10 +2003,9 @@ function makeStyles(c: ThemePalette) {
// A minimum, not a fixed height: at a larger system font the text used
// to be cut off at the top.
minHeight: componentSizes.inputHeight, // h-10 = 40px
- backgroundColor: c.surface,
- borderRadius: radius.sm,
- borderWidth: 1,
- borderColor: c.border,
+ // Webmail mobile header: a filled pill, no border.
+ backgroundColor: c.muted,
+ borderRadius: radius.full,
gap: spacing.sm,
},
// No lineHeight and no Android default padding on a TextInput: both clip
@@ -2298,6 +2298,9 @@ function makeStyles(c: ThemePalette) {
borderBottomWidth: 1,
borderBottomColor: c.border, // border-b border-border
},
+ // Webmail: unread rows get bg-accent/30. Opaque, since the row covers
+ // its swipe actions.
+ emailRowUnread: { backgroundColor: mix(c.accent, c.background, 0.3) },
emailRowPressed: { backgroundColor: c.surface },
emailRowSelected: { backgroundColor: c.selection },
// The message open next to the list (webmail: bg-selection with a primary
diff --git a/src/screens/EmailThreadScreen.tsx b/src/screens/EmailThreadScreen.tsx
index d1879a44..342c43bc 100644
--- a/src/screens/EmailThreadScreen.tsx
+++ b/src/screens/EmailThreadScreen.tsx
@@ -1683,11 +1683,12 @@ function makeStyles(c: ThemePalette) {
subjectStar: {
paddingTop: 4,
},
+ // Webmail viewer on a phone: text-lg font-bold tracking-tight.
subjectText: {
flex: 1,
- fontSize: 20,
+ fontSize: 18,
fontWeight: '700',
- lineHeight: 28,
+ lineHeight: 26,
color: c.text,
letterSpacing: -0.2,
},
diff --git a/src/theme/__tests__/mix.test.ts b/src/theme/__tests__/mix.test.ts
new file mode 100644
index 00000000..b5152626
--- /dev/null
+++ b/src/theme/__tests__/mix.test.ts
@@ -0,0 +1,17 @@
+import { describe, it, expect } from 'vitest';
+import { mix, DARK_COLORS, LIGHT_COLORS } from '../tokens';
+
+describe('mix', () => {
+ it('blends like a translucent layer, but opaque', () => {
+ expect(mix('#ffffff', '#000000', 0.5)).toBe('#808080');
+ expect(mix('#1e3a8a', '#0a0a0a', 0)).toBe('#0a0a0a');
+ expect(mix('#1e3a8a', '#0a0a0a', 1)).toBe('#1e3a8a');
+ expect(mix('rgba(0,0,0,0.5)', '#0a0a0a', 0.3)).toBe('#0a0a0a');
+ });
+
+ it('gives the webmail unread row tint (bg-accent/30)', () => {
+ // #1e3a8a at 30% over #0a0a0a, and #dbeafe at 30% over white.
+ expect(mix(DARK_COLORS.accent, DARK_COLORS.background, 0.3)).toBe('#101830');
+ expect(mix(LIGHT_COLORS.accent, LIGHT_COLORS.background, 0.3)).toBe('#f4f9ff');
+ });
+});
diff --git a/src/theme/tokens.ts b/src/theme/tokens.ts
index 68bc78d0..5b146b88 100644
--- a/src/theme/tokens.ts
+++ b/src/theme/tokens.ts
@@ -329,3 +329,22 @@ export const componentSizes = {
toggleHeight: 24, toggleWidth: 44, toggleThumb: 20,
fab: 56, badgeSize: 16,
} as const;
+
+/**
+ * `top` over `bottom` at `amount` opacity, as an opaque #rrggbb: the
+ * webmail's `bg-accent/30` for a surface that must hide what is behind it
+ * (a list row over its swipe actions). Takes #rrggbb; anything else
+ * returns `bottom` unchanged.
+ */
+export function mix(top: string, bottom: string, amount: number): string {
+ const parse = (hex: string) => {
+ const m = /^#([0-9a-f]{6})$/i.exec(hex);
+ if (!m) return null;
+ const n = parseInt(m[1], 16);
+ return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
+ };
+ const a = parse(top);
+ const b = parse(bottom);
+ if (!a || !b) return bottom;
+ return `#${a.map((v, i) => Math.round(v * amount + b[i] * (1 - amount)).toString(16).padStart(2, '0')).join('')}`;
+}