# Export site — mobile-first responsive redesign ## Context The `export/` app (the public static archive: jeralyzer.pages.dev etc., plus hub mode) was designed desktop-first at `max-w-6xl` and relies on `flex-wrap` everywhere. Measured against a built `export/out` served locally at iPhone-13 width (390px) on 2026-08-29: - **Header** wraps to three rows inside a fixed `h-14`, so rows 2–3 render *outside* the sticky header's background and page content scrolls through them (`Header.tsx:43`). - **Footer** row A has no `flex-wrap` + `whitespace-nowrap` → horizontal overflow at 360px. - **Search page**: profile row, query builder, and the fully expanded filter stack (channels, type, audience, availability ×6, dates, advanced) occupy ~2 screens *above* the results — on `/ask` too, before the user ever sees "Ask a question". The `/?q=election` full page is 43,000px tall (every hit of every video rendered inline); `/duplicates` is 68,000px. - **Transcript modal** reserves the player slot with `calc(min(92vw,960px)*9/16 + 3rem)` and `100vh`, leaving ~100px for the cue list at 360px; its control bar is 13 unlabeled 32px emoji buttons (`title` tooltips only — invisible on touch) wrapping to 2–4 rows. - **Ask pane** has zero breakpoint classes; provider settings, grounding palette, pinned panel, saved chats, context, report and the `max-h-[60vh]` nested message scroller stack in one column with the composer at the very bottom. - Result-card headers are single-row `truncate` (title → "Hasan…"), hit rows spend a `w-16` gutter, checkboxes are 16px, many buttons are `h-6`/`text-xs`. - Only viewport-aware JS in the app: `WorkspaceView.tsx:51` (`min-width:1024px` gating Split). Only breakpoint classes outside the workspace: five `sm:` utilities. No `dvh`, no safe-area. Goal: a redesign that keeps **every** feature and every e2e hook, is designed mobile-first and works at any width, puts the standard path (type → results → open transcript / ask) in front, and folds the power features (query tree, profiles, advanced throughput, sweeps, clips…) behind explicit, discoverable disclosure that scales up to always-visible on wide screens. Screenshots for reference: `/home/user/.claude/jobs/a61f2393/tmp/shots/` (phone_* / desktop_*). ## Non-negotiable invariants 1. **Mount architecture is unchanged.** `(workspace)/layout.tsx → SiteWorkspace` keeps `PlayerProvider > SingleSiteDataProvider > SearchSessionProvider > [bar, WorkspaceView]` with `TranscriptModal`/`PostModal` siblings. Both panes stay lazily-mounted-then-latched and are hidden with CSS, never unmounted (`WorkspaceView.tsx:76-121`). Streaming chat, the AbortController, and the pre-hydration nav-click guard depend on it. Hub mode (`TranscriptSearch.tsx`, `HubHome`, `AskHub`) keeps bypassing the shell; it inherits the redesigned bar/results automatically and must not assume a player or search session exists. 2. **State contracts are unchanged.** `SearchSessionContext` (draft/committed filters, profiles, selection/grounding, view + chartShape, advanced opts, URL params `qt/view/cs/v/t/vm` + share v2 keys), `useAskChat`, and every `ytdlp-tb:*` localStorage key keep their shapes. This is a view-layer restructuring. 3. **Every e2e hook survives with identical strings** (full list in §Hooks). E2E runs at 1440×1200 (`export/playwright.config.ts`), so the desktop layout must expose everything the specs reach today with at most the small helper edits listed per slice. 4. Reduced-motion opt-outs stay on every animation; `hover:` never gates a *function* (use `@media (hover: hover)` for reveal-on-hover). ## Design ### Breakpoint tiers (Tailwind v4 defaults) | Tier | Width | Shape | |---|---|---| | base | <640 | one column; sticky compact search bar; sheets for filters/menu; full-screen player view; sticky composer & selection bar with safe-area padding | | sm | ≥640 | same column, wider chips/rows; filters sheet slides from the right; modal returns to the centred `min(92vw,960px)` card | | md | ≥768 | header nav inline (no hamburger) | | lg | ≥1024 | Split (search ‖ chat) offered, as today; chart options + grounding panels expanded by default | | xl | ≥1280 | filters become an inline, collapsible left sidebar next to results | Shared plumbing: `common/lib/useMediaQuery.ts` (`useSyncExternalStore`, server snapshot `false`, used for `wide` and the filters container); `generateViewport` gains `viewportFit:"cover"` + `interactiveWidget:"resizes-content"`; `100vh → 100dvh`; safe-area utilities (`pb-[env(safe-area-inset-bottom)]`); manifest drops `orientation:"portrait-primary"` (video wants landscape). Inputs keep `text-base md:text-sm` (iOS zoom guard). Primary-path tap targets ≥ 44px, secondary ≥ 36px; checkbox labels get padded rows. ### 1. Shell — header, footer, menu - `Header.tsx`: `h-14 → min-h-14`, no wrap. Brand left. **< md**: right cluster = ThemeToggle + hamburger (`aria-label="Open menu"`) opening a `Sheet side="right"` (`ui/sheet.tsx`, currently unused anywhere) with: primary links (Search, **Ask AI** (new link, `/ask/`), Duplicates, Downloads, Use with AI, Offline when `site.pwa`, Changelog), a "Sites" section listing `resolveRelatedSites` groups, the Hub backlink (today `hidden sm:inline-flex` — it becomes reachable on phones), then Theme family + Mode radio lists. Header is a server component: add a small client `MobileMenu` that receives serialised link groups as props. **md+**: inline nav as today plus "Ask AI"; SiblingSwitcher/ThemeMenu/ThemeToggle unchanged (their roles are test hooks). - `Footer.tsx`: `flex-wrap` on row A and its inner group, stack below sm, drop `whitespace-nowrap`, safe-area bottom padding. All links/gating unchanged. ### 2. Search bar (the standard path) `WorkspaceSearchBar.tsx` (910 lines) splits into `SearchBar` + `FiltersPanel` (+ the existing `QueryBuilder`). `SearchBar` is **sticky below the header on < lg** (top offset = header height): - **Row 1**: the compact leaf's input (`leaf-query-`, `type=search`, 44px tall, flex-1) + Search submit (`data-testid="search-submit"`, `data-dirty`; icon-only below sm with `aria-label="Search"`, label from sm). - **Row 2**: a horizontally scrolling chip row (`overflow-x-auto`, no scrollbar, snap): `WorkspaceNav` [Search|Chat|Split] (moved here from `WorkspaceView`; Split still gated on `wide`), scope `