From 827d9df3c7a6d240e85b97e089bfb7be43cc8424 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 6 Sep 2026 04:07:32 +0000 Subject: [PATCH] Remove the remaining capsule buttons and fix the crushed suggestion card Two problems the first pass missed. Capsule buttons. I filed everything in simulator.css as "in-phone, so iOS convention applies" and left it. Two of those were not in the phone at all: "Expand preview" and the focus-mode "Close" are the site's own preview chrome and had no native counterpart. The in-phone ones were wrong on the facts as well: all three Swift consent sheets build their confirm button with RoundedRectangle(cornerRadius: 18), and the Capsule() calls near them are the sheet grab handles. So the mock's fully-rounded Apply button was a fidelity bug against the app it mirrors, not a deliberate convention. Site chrome moves onto the site's radius scale, and the sheet buttons to the stage-scale equivalent of 18pt. EmptyStateView was the one native button using Capsule(); it is now RoundedRectangle(18) like every other primary button in the app, so the mock and the app still agree. That Swift change is unverified here, since the iOS lane cannot be built on Linux. What remains capsule is capsule in the native app too: progress bars, slider tracks, grab handles, the switch and the toast. Four unreferenced rules (.chat-chip, .chapter-strip button, .try-it-pill, .coming-pill) are deleted; they rendered nowhere and were the confusing part of auditing this file. Crushed card. .app-screen is a column flex container whose children default to flex-shrink: 1, so a screen taller than the stage squeezed the last row instead of scrolling: the "Lumen noticed" card collapsed from 106px of content into a 14px sliver at 1024x800 and 1280x720, and clipped at 1440x900. overflow-y: auto never engaged because flexbox shrinks before it scrolls. Children now hold their natural height and the screen scrolls. That leaves the card below the fold on shorter viewports, which is what a real phone does, but it was also missing the fold by 5px at 1440x900. Tightening the preview's gap and padding and lifting the stage from 80vh to 82vh brings the card fully into view at 1440x900, 1512x982 and above, and on mobile. Both defects are now covered by e2e tests, each confirmed to fail against the old CSS before being kept. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01WmouTty4UranwAXwByjsKy --- AGENTS.md | 11 ++++- CLAUDE.md | 11 ++++- Lumen/Components/EmptyStateView.swift | 2 +- e2e/live-demo.e2e.js | 43 ++++++++++++++++++ src/App.css | 65 +++++---------------------- src/simulator.css | 17 ++++--- 6 files changed, 83 insertions(+), 66 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 068a539..72d129c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -287,8 +287,15 @@ landing page read as machine-generated. Keep it that way when extending it: headings or nav any more, so the case you write is the case that renders. - **No em dashes in site copy.** Use a full stop or a comma. `src/main.test.jsx` guards this against the rendered DOM. -- **Radius scale is 6-14px** for site chrome. The capsule (`99px`) radius is reserved - for progress bars and for the in-phone surfaces that mirror native iOS controls. +- **No capsule buttons anywhere.** Site chrome sits on a 6-14px radius scale; the + in-phone buttons use ~7px, which is `RoundedRectangle(cornerRadius: 18)` from the + Swift consent sheets at the stage's scale. The `99px` radius is reserved for + progress bars, slider tracks, sheet grab handles, the iOS switch and the toast, + all of which are capsule in the native app too. An e2e test enforces this. +- **`.app-screen` children carry `flex-shrink: 0`.** It is a column flex container, + so without it a screen taller than the stage squeezes the last card instead of + scrolling. That silently collapsed the "Lumen noticed" card to a 14px sliver at + common laptop sizes; an e2e test now guards it. - **One reveal per section.** `FadeIn` is opacity plus a short travel, with no delay prop and no per-card stagger. Do not wrap individual grid items in it. - **Icons come from lucide-react**, never emoji or dingbat glyphs. diff --git a/CLAUDE.md b/CLAUDE.md index 068a539..72d129c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -287,8 +287,15 @@ landing page read as machine-generated. Keep it that way when extending it: headings or nav any more, so the case you write is the case that renders. - **No em dashes in site copy.** Use a full stop or a comma. `src/main.test.jsx` guards this against the rendered DOM. -- **Radius scale is 6-14px** for site chrome. The capsule (`99px`) radius is reserved - for progress bars and for the in-phone surfaces that mirror native iOS controls. +- **No capsule buttons anywhere.** Site chrome sits on a 6-14px radius scale; the + in-phone buttons use ~7px, which is `RoundedRectangle(cornerRadius: 18)` from the + Swift consent sheets at the stage's scale. The `99px` radius is reserved for + progress bars, slider tracks, sheet grab handles, the iOS switch and the toast, + all of which are capsule in the native app too. An e2e test enforces this. +- **`.app-screen` children carry `flex-shrink: 0`.** It is a column flex container, + so without it a screen taller than the stage squeezes the last card instead of + scrolling. That silently collapsed the "Lumen noticed" card to a 14px sliver at + common laptop sizes; an e2e test now guards it. - **One reveal per section.** `FadeIn` is opacity plus a short travel, with no delay prop and no per-card stagger. Do not wrap individual grid items in it. - **Icons come from lucide-react**, never emoji or dingbat glyphs. diff --git a/Lumen/Components/EmptyStateView.swift b/Lumen/Components/EmptyStateView.swift index 548c801..764c53a 100644 --- a/Lumen/Components/EmptyStateView.swift +++ b/Lumen/Components/EmptyStateView.swift @@ -32,7 +32,7 @@ struct EmptyStateView: View { .font(.body.bold()) .padding(.horizontal, 24) .padding(.vertical, 10) - .background(Color("MuhaBrown"), in: Capsule()) + .background(Color("MuhaBrown"), in: RoundedRectangle(cornerRadius: 18)) .foregroundStyle(Color("MuhaCream")) } .padding(.top, 4) diff --git a/e2e/live-demo.e2e.js b/e2e/live-demo.e2e.js index 10a6329..2664adb 100644 --- a/e2e/live-demo.e2e.js +++ b/e2e/live-demo.e2e.js @@ -86,6 +86,49 @@ test.describe('Lumen live demo', () => { await expect(page.locator('.app-fullscreen')).toHaveCount(0); }); + test('preview rows keep their height instead of being crushed', async ({ page }) => { + // .app-screen is a column flex container, so its children shrink below + // their own content by default rather than letting the screen scroll. That + // squeezed the "Lumen noticed" card from 106px down to 14px at common + // laptop sizes. Check the short viewport, where the squeeze was worst. + await page.setViewportSize({ width: 1280, height: 720 }); + await page.goto('/'); + + const crushed = await page.evaluate(() => { + const screen = document.querySelector('.app-preview-stage .app-screen'); + return [...screen.children] + .filter(el => el.scrollHeight > el.clientHeight + 2) + .map(el => `${el.className}: ${el.scrollHeight} > ${el.clientHeight}`); + }); + + expect(crushed).toEqual([]); + }); + + test('no button in the preview or its chrome is capsule-shaped', async ({ page }) => { + // The native app uses RoundedRectangle(cornerRadius: 18) for its buttons; + // capsules there are grab handles, progress bars and the iOS switch. A + // fully-rounded button is the landing-page tell we removed, so keep it out. + await page.goto('/'); + await page.locator('#demo').getByRole('button', { name: /review evening scene/i }).click(); + await expect(page.locator('#demo').getByText(/why lumen noticed/i)).toBeVisible(); + + const pills = await page.evaluate(() => { + const out = []; + for (const el of document.querySelectorAll('button, a[href]')) { + if (el.offsetParent === null) continue; + const h = el.getBoundingClientRect().height; + const cs = getComputedStyle(el); + if (!h || cs.borderRadius.includes('%')) continue; // circular icon buttons are fine + if (parseFloat(cs.borderRadius) >= h / 2 - 0.5) { + out.push(`${el.className}: r=${cs.borderRadius} h=${Math.round(h)}`); + } + } + return out; + }); + + expect(pills).toEqual([]); + }); + test('mobile stage is tappable without a nested bezel', async ({ page }) => { await page.setViewportSize({ width: 390, height: 844 }); await page.goto('/'); diff --git a/src/App.css b/src/App.css index fd58074..7f791f0 100644 --- a/src/App.css +++ b/src/App.css @@ -1092,16 +1092,6 @@ p { flex-wrap: wrap; } -.chat-chip { - padding: 4px 9px; - border-radius: 99px; - border: 1px solid rgba(255,255,255,0.1); - background: rgba(255,255,255,0.04); - color: rgba(232,224,212,0.52); - font-size: 9px; - cursor: pointer; - font-family: inherit; -} .chat-chip.apply { border-color: rgba(200,184,154,0.3); @@ -1357,20 +1347,6 @@ p { justify-content: center; } -.chapter-strip button { - display: flex; - align-items: center; - gap: 6px; - padding: 7px 13px; - border-radius: 99px; - border: 1px solid rgba(255,255,255,0.07); - background: transparent; - color: rgba(232,224,212,0.38); - font-size: 11px; - font-family: inherit; - cursor: pointer; - transition: color 0.2s, border-color 0.2s, background 0.2s; -} .chapter-strip button:hover { color: rgba(232,224,212,0.7); @@ -1414,6 +1390,14 @@ p { gap: 5px; } +/* Column flex children shrink below their content by default, so a screen + taller than the stage squeezes the last card instead of scrolling: the + "Lumen noticed" card collapsed from 106px to 14px at common laptop sizes. + Hold every row at its natural height and let .app-screen scroll. */ +.app-screen > * { + flex-shrink: 0; +} + .app-screen.dimmed { opacity: 0.3; filter: blur(1.5px); @@ -1915,7 +1899,7 @@ p { width: 100%; margin-top: 6px; padding: 8px 14px; - border-radius: 99px; + border-radius: 7px; border: none; background: #C49A6C; color: #0e0819; @@ -1936,7 +1920,7 @@ p { width: 100%; margin-top: 6px; padding: 8px 14px; - border-radius: 99px; + border-radius: 7px; background: #C49A6C; color: #0e0819; font-size: 9.5px; @@ -2789,38 +2773,9 @@ p { z-index: 0; } -.try-it-pill { - display: inline-flex; - align-items: center; - padding: 3px 8px; - border-radius: 99px; - background: rgba(196,154,108,0.16); - border: 1px solid rgba(196,154,108,0.32); - color: #C49A6C; - font-size: 8px; - letter-spacing: 0.1em; - text-transform: uppercase; - margin-left: auto; - flex-shrink: 0; - animation: pulseGlow 2.2s ease-in-out infinite; -} /* ===== COMING PILL ===== */ -.coming-pill { - display: inline-flex; - align-items: center; - padding: 3px 8px; - border-radius: 99px; - background: rgba(138,180,248,0.12); - border: 1px solid rgba(160,100,255,0.18); - color: rgba(180,130,255,0.65); - font-size: 8px; - letter-spacing: 0.1em; - text-transform: uppercase; - margin-left: auto; - flex-shrink: 0; -} /* ===== RESPONSIVE (additions) ===== */ diff --git a/src/simulator.css b/src/simulator.css index ef161ad..7cbd95d 100644 --- a/src/simulator.css +++ b/src/simulator.css @@ -318,7 +318,9 @@ .app-preview-stage { width: min(390px, 100%); - height: min(80vh, 780px); + /* 82vh rather than 80: at a 900px-tall viewport, the most common laptop + size, 80vh left the suggestion card 5px short of the fold. */ + height: min(82vh, 780px); min-height: 620px; display: flex; flex-direction: column; @@ -348,8 +350,11 @@ } .app-preview-stage .app-screen { - padding: 12px 18px 14px; - gap: 10px; + /* Tight enough that the "Lumen noticed" card, which is the whole point of + the preview, clears the fold on a 900px-tall viewport. Below that the + screen scrolls, the same as the real phone would. */ + padding: 12px 18px 10px; + gap: 8px; } .app-preview-stage .app-tabbar { @@ -583,7 +588,7 @@ gap: 8px; margin-top: 4px; padding: 11px 20px; - border-radius: 99px; + border-radius: 12px; border: 1px solid rgba(196, 154, 108, 0.4); background: rgba(196, 154, 108, 0.12); color: #e8d3b6; @@ -653,7 +658,7 @@ align-items: center; gap: 5px; padding: 7px 13px; - border-radius: 99px; + border-radius: 10px; border: 1px solid rgba(255, 255, 255, 0.14); background: rgba(20, 14, 34, 0.7); backdrop-filter: blur(8px); @@ -790,7 +795,7 @@ align-self: flex-start; margin-top: 4px; padding: 7px 12px; - border-radius: 99px; + border-radius: 7px; border: 1px solid rgba(196, 154, 108, 0.35); background: rgba(196, 154, 108, 0.1); color: #e8d3b6;