Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/app/globals.css
Original file line numberDiff line numberDiff line change
Expand Up@@ -338,3 +338,17 @@ html {
html {
overflow-x: clip;
}

/* iOS in-app WebView (Telegram, Instagram, Facebook, Twitter, TikTok)
downgrade only: drop sticky on the site header so the iOS 26 WebKit
jitter bug (bugs.webkit.org/297779) cannot expose a gap at the top.
The `ios-webview` class is set by an inline script in layout.tsx
based on userAgent (iOS device with no `Safari/` token = WebView).
Regular mobile Safari / Chrome iOS keep sticky because the symptom
does not visibly trigger there. Desktop is sticky everywhere. */
@media (max-width: 767px) {
html.ios-webview .site-header-root {
position: relative;
top: auto;
}
}
12 changes: 12 additions & 0 deletions src/app/layout.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -124,6 +124,18 @@ export default function RootLayout({
__html: `(function(){try{var t=localStorage.getItem('ocb-theme');var d=t==='dark'||(!t&&window.matchMedia('(prefers-color-scheme: dark)').matches);if(d)document.documentElement.classList.add('dark');}catch(e){}})();`,
}}
/>
{/* Detect iOS in-app WebView (Telegram, Instagram, FB, Twitter, etc.).
These hosts hit the iOS 26 WebKit fixed/sticky jitter bug
(bugs.webkit.org/297779) harder than regular Safari because their
own URL-bar overlay animates the layout viewport on top of the
WebKit regression. CSS below downgrades the site header to
non-sticky only when this class is present. Inline so the class
is on the html element before React hydrates — no flash. */}
<script
dangerouslySetInnerHTML={{
__html: `(function(){try{var ua=navigator.userAgent;if(/iPhone|iPad|iPod/.test(ua)&&!/Safari\\//.test(ua))document.documentElement.classList.add('ios-webview');}catch(e){}})();`,
}}
/>
</head>
{/* grid (not flex) so position: sticky on <SiteHeader> works reliably
on iOS Safari — sticky inside a flex column has known quirks.
Expand Down
18 changes: 8 additions & 10 deletions src/components/site-header.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -39,16 +39,14 @@ export function SiteHeader() {

return (
<div
// Sticky only on md+ (desktop). On mobile the header stays in normal
// flow and scrolls away with the page. Reason: WebKit iOS 26 has a
// regression (bugs.webkit.org/297779) where sticky AND fixed elements
// jitter 10-24 px on scroll direction change, leaving a visible gap
// at the top in every WKWebView host (Telegram in-app, Chrome iOS,
// Mastodon and LinkedIn ship the same symptom). Apple's fix is
// partial; until iOS 26.2+ lands across the user base, non-sticky on
// mobile is the only way to avoid the jitter entirely. Standard
// mobile UX anyway — pull to top to see the nav.
className="relative md:sticky md:top-0 z-50 flex flex-col font-sans bg-surface"
// Sticky everywhere by default. Only iOS in-app WebViews (Telegram,
// Instagram, FB, etc.) get downgraded to non-sticky to dodge the
// iOS 26 WebKit jitter bug (bugs.webkit.org/297779). Regular mobile
// Safari and Chrome iOS keep their sticky nav because the symptom is
// not visible there in practice. Override lives in globals.css
// (`html.ios-webview .site-header-root` rule) so the WebView-only
// selector stays out of the Tailwind class soup here.
className="site-header-root sticky top-0 z-50 flex flex-col font-sans bg-surface"
>
<header className="border-b border-rule py-4 md:py-5 px-4 sm:px-6 shrink-0 text-sm bg-surface relative">
<div className="max-w-[1400px] mx-auto flex items-center justify-between gap-3">
Expand Down
Loading