diff --git a/.github/ui-review/chat-mobile-type-dark.webp b/.github/ui-review/chat-mobile-type-dark.webp new file mode 100644 index 0000000..ea35173 Binary files /dev/null and b/.github/ui-review/chat-mobile-type-dark.webp differ diff --git a/.github/ui-review/chat-mobile-type-light.webp b/.github/ui-review/chat-mobile-type-light.webp new file mode 100644 index 0000000..4e2f62f Binary files /dev/null and b/.github/ui-review/chat-mobile-type-light.webp differ diff --git a/AGENTS.md b/AGENTS.md index 1ec8038..ea152d3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,4 +9,7 @@ This repository is a VERY EARLY WIP. Proposing sweeping changes that improve lon * Optimal performance on both frontend and backend. * Excellent security and privacy, as long as it doesn't hurt UX too much. ## Maintainability -Long term maintainability is a core priority. If you add new functionality, first check if there is shared logic that can be extracted to a separate module. Duplicate logic across multiple files is a code smell and should be avoided. Don't be afraid to change existing code. Don't take shortcuts by just adding local logic to solve a problem. \ No newline at end of file +Long term maintainability is a core priority. If you add new functionality, first check if there is shared logic that can be extracted to a separate module. Duplicate logic across multiple files is a code smell and should be avoided. Don't be afraid to change existing code. Don't take shortcuts by just adding local logic to solve a problem. +## Typography +Text is sized from the named scale in `apps/web/src/styles.css` (`text-meta` … `text-display-lg`), never with an arbitrary `text-[Npx]`. Two reasons: the tokens are in `rem`, so they follow the reader's browser font-size preference, and a scale of eleven named steps can be retuned in one place. The floor is 11px, and any text-entry field must reach 16px on mobile or iOS Safari zooms the viewport on focus. +Secondary copy on an accent fill uses `text-on-accent-muted`, not `text-on-accent` at reduced alpha — the accent barely clears 4.5:1 with full-strength text, so alpha puts it under the contrast minimum. \ No newline at end of file diff --git a/apps/web/public/marketing.css b/apps/web/public/marketing.css index 39585cf..4385b85 100644 --- a/apps/web/public/marketing.css +++ b/apps/web/public/marketing.css @@ -42,7 +42,10 @@ body { color-mix(in srgb, var(--accent) 16%, transparent), transparent 70% ), var(--bg); - font-size: 16px; + /* `1rem`, not `16px`: the rest of this sheet sizes text in `rem`, so + pinning the root to a pixel value would silently discard the reader's + browser font-size preference for the whole page. */ + font-size: 1rem; line-height: 1.6; } diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css index 6afb13b..be61616 100644 --- a/apps/web/src/styles.css +++ b/apps/web/src/styles.css @@ -24,6 +24,7 @@ --color-accent-press: var(--c-accent-press); --color-accent-soft: var(--c-accent-soft); --color-on-accent: var(--c-on-accent); + --color-on-accent-muted: var(--c-on-accent-muted); --color-danger: var(--c-danger); --color-danger-soft: var(--c-danger-soft); @@ -43,6 +44,31 @@ --font-display: "Bricolage Grotesque Variable", "Hanken Grotesk Variable", sans-serif; --font-mono: "JetBrains Mono Variable", ui-monospace, "SF Mono", Menlo, Consolas, monospace; + /* Type scale. + + Every size is in `rem`, never `px`: a `px` size is frozen at whatever the + designer typed, so a reader who raised the default font size in their + browser gets nothing. `rem` tracks that preference (WCAG 1.4.4 asks that + text survive a 200% resize; honouring the *setting* is the part a zoom + gesture can't stand in for). + + The floor is 11px. Both platform guidelines put their smallest text there + — Apple's HIG says never below 11pt, Material's `labelSmall` is 11sp — and + the 10px timestamps this app used to ship were under it. Sizes are named + by role rather than by measurement so the scale can move without a + find-and-replace across every component. */ + --text-meta: 0.6875rem; /* 11px — timestamps, badges, status pills */ + --text-caption: 0.78125rem; /* 12.5px — secondary metadata */ + --text-note: 0.84375rem; /* 13.5px — dense labels, list rows */ + --text-body: 0.90625rem; /* 14.5px — default reading size */ + --text-body-lg: 0.9375rem; /* 15px — document base */ + --text-lead: 1rem; /* 16px — mobile reading size, inputs */ + --text-title-sm: 1.0625rem; /* 17px */ + --text-title: 1.1875rem; /* 19px */ + --text-title-lg: 1.375rem; /* 22px */ + --text-display: 1.5rem; /* 24px */ + --text-display-lg: 1.625rem; /* 26px */ + --animate-spin-fast: spin 0.65s linear infinite; } @@ -53,6 +79,12 @@ --c-accent-soft: color-mix(in srgb, var(--c-accent) 12%, transparent); --c-accent-glow: color-mix(in srgb, var(--c-accent) 45%, transparent); --c-on-accent: #ffffff; + /* Secondary copy on an accent fill. This has to be a solid colour, not + `on-accent` at reduced alpha: the accent background only clears white by + 5.4:1 to begin with, so any alpha under ~90% drops the text below the + 4.5:1 that WCAG 1.4.3 asks of small text. Verified at 4.74:1 on `accent` + and 6.08:1 on the darkened attachment surface inside a sent bubble. */ + --c-on-accent-muted: #f0f0fb; --c-bg: #f6f6f7; --c-surface: #ffffff; @@ -89,6 +121,11 @@ --c-accent-soft: color-mix(in srgb, var(--c-accent) 16%, transparent); --c-accent-glow: color-mix(in srgb, var(--c-accent) 40%, transparent); --c-on-accent: #0c0c14; + /* Dark theme inverts the relationship — the accent is light and the text + on it is near-black — but the margin is just as thin, so this is a + solid colour here too. 5.03:1 on `accent`, 6.25:1 on the lightened + attachment surface. */ + --c-on-accent-muted: #17172a; --c-bg: #0a0a0c; --c-surface: #141417; @@ -154,7 +191,7 @@ font-family: var(--font-sans); background: var(--c-bg); color: var(--c-ink); - font-size: 15px; + font-size: var(--text-body-lg); line-height: 1.5; letter-spacing: -0.006em; -webkit-font-smoothing: antialiased; @@ -244,8 +281,12 @@ @apply bg-surface shadow-soft dark:bg-surface-2 dark:ring-1 dark:ring-inset dark:ring-line; } + /* Safari on iOS zooms the viewport when a field smaller than 16px takes + focus, and never zooms back out — so every text entry point in the app + steps up to `lead` on narrow screens. It reads better under a thumb + anyway. */ .field-input { - @apply w-full rounded-card border border-line-strong bg-surface px-3.5 py-3 text-[14.5px] text-ink transition; + @apply w-full rounded-card border border-line-strong bg-surface px-3.5 py-3 text-body text-ink transition max-md:text-lead; } .field-input::placeholder { color: var(--c-muted); diff --git a/apps/web/src/ui/App.tsx b/apps/web/src/ui/App.tsx index a67d48f..f043f07 100644 --- a/apps/web/src/ui/App.tsx +++ b/apps/web/src/ui/App.tsx @@ -96,11 +96,11 @@ function StartupError({ message }: { message: string }): JSX.Element {
-

+

Secure connection required

-

{message}

-

+

{message}

+

For local development, open the app through http://localhost or configure HTTPS for the host you are using.

@@ -141,7 +141,7 @@ function SpaceView({ section }: { section: SpaceSection }): JSX.Element { onClick={() => setRenaming(true)} class="group mr-auto flex min-w-0 items-center gap-1.5 rounded-[10px] px-1.5 py-[7px] text-left transition hover:bg-surface-3" > - + {spaceName} @@ -162,7 +162,7 @@ function SpaceView({ section }: { section: SpaceSection }): JSX.Element { ))} -
+
)} - + {section === "chat" ? spaceName : sectionLabel(section)}
@@ -202,7 +202,7 @@ function SpaceView({ section }: { section: SpaceSection }): JSX.Element { {/* Desktop view header */}