diff --git a/src/app/globals.css b/src/app/globals.css index 6265a4d..568b7e2 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -725,9 +725,19 @@ .scroll-area { flex: 1; - overflow: auto; + overflow-x: hidden; + overflow-y: auto; padding: 20px 24px 60px; } + +/* Bare routes (/s/*, /login, signed-out) — same scrollport as .scroll-area + without app chrome padding. Needed because html/body are overflow:hidden. */ +.bare-scroll { + height: 100%; + height: 100dvh; + overflow-x: hidden; + overflow-y: auto; +} :where(html[data-nav="bottom"]) .scroll-area, :where(html[data-nav="fab"]) .scroll-area { padding: 12px 14px 74px; @@ -1617,7 +1627,8 @@ select { } /* Momentum scrolling + prevent page bounce fighting in-app scroll */ - .scroll-area { + .scroll-area, + .bare-scroll { -webkit-overflow-scrolling: touch; overscroll-behavior-y: contain; } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 7da8be5..182b8e6 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,4 +1,5 @@ import type { Metadata, Viewport } from "next"; +import { headers } from "next/headers"; import "./globals.css"; import { Inter, Source_Serif_4, Newsreader, Fraunces, JetBrains_Mono } from "next/font/google"; import { cn } from "@/lib/utils"; @@ -71,9 +72,13 @@ export const metadata: Metadata = { // Pre-paint: read user's theme prefs from localStorage and apply data-* + .dark. // Falls back to clay/serif-sans/regular/sidebar/system if nothing is stored. +// Public share pages always use ink so guests get a neutral, consistent look. const prePaintScript = `(function(){ try { - var palette = localStorage.getItem('themePalette') || localStorage.getItem('theme') || 'clay'; + var isShare = location.pathname.indexOf('/s/') === 0; + var palette = isShare + ? 'ink' + : (localStorage.getItem('themePalette') || localStorage.getItem('theme') || 'clay'); var mode = localStorage.getItem('themeMode') || 'system'; var fontPair = localStorage.getItem('themeFontPair') || 'serif-sans'; var density = localStorage.getItem('themeDensity') || 'regular'; @@ -106,6 +111,10 @@ export default async function RootLayout({ children }: { children: React.ReactNo let assistantName = DEFAULT_ASSISTANT_NAME; let assistantModel: string | null = null; + const headersList = await headers(); + const pathname = headersList.get("x-pathname") ?? ""; + const isSharePage = pathname === "/s" || pathname.startsWith("/s/"); + const session = await auth(); if (session?.user?.id) { signedIn = true; @@ -146,6 +155,10 @@ export default async function RootLayout({ children }: { children: React.ReactNo .orderBy(asc(dashboards.position), asc(dashboards.createdAt)); } + if (isSharePage) { + palette = "ink"; + } + // Server-side initial dark guess: only for `dark` mode (system mode is corrected // before paint by the inline script). Avoids a flash on signed-in users. const isDark = mode === "dark"; diff --git a/src/components/app-shell.tsx b/src/components/app-shell.tsx index b11d3a9..d6c3c3d 100644 --- a/src/components/app-shell.tsx +++ b/src/components/app-shell.tsx @@ -24,7 +24,8 @@ export async function AppShell({ signedIn, navStyle, children }: Props) { if (bare || !signedIn) { // No shell — share viewer and signed-out pages render bare. - return <>{children}; + // html/body are overflow:hidden for the app grid; bare pages need their own scroller. + return
{children}
; } const useTopVariant = navStyle === "top-nav"; diff --git a/src/components/rich-text/rich-text.css b/src/components/rich-text/rich-text.css index 05e1c3c..3ca9964 100644 --- a/src/components/rich-text/rich-text.css +++ b/src/components/rich-text/rich-text.css @@ -64,6 +64,9 @@ margin: 0.8em 0; max-width: 100%; overflow-x: auto; + overflow-y: hidden; + -webkit-overflow-scrolling: touch; + overscroll-behavior-x: contain; padding: 0.75rem 0.9rem; } @@ -91,7 +94,12 @@ display: block; margin: 0.8em 0; max-width: 100%; + /* overflow-x alone computes overflow-y to auto, which traps vertical + touch scrolling on iOS when a wide table fills the viewport. */ overflow-x: auto; + overflow-y: hidden; + -webkit-overflow-scrolling: touch; + overscroll-behavior-x: contain; width: max-content; }