* { overscroll-behavior: none; } /* Keep app chrome non-selectable so iOS long-press doesn't open the edit menu (AutoFill/Copy/Siri). Inputs and opted-in regions stay selectable. */ body { -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; } input, textarea, [contenteditable="true"], [contenteditable=""], [data-selectable], [data-selectable] * { -webkit-touch-callout: default; -webkit-user-select: text; user-select: text; } @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } .app { transition: transform 500ms cubic-bezier(0.32, 0.72, 0, 1), border-radius 500ms cubic-bezier(0.32, 0.72, 0, 1); transform: scale(1) translateY(0); border-radius: 0; will-change: transform; } .app.drawer-scaling, [data-drawer-level].drawer-scaling { transform: scale(0.94) translateY(-8px); border-radius: 12px; overflow: hidden; } [data-drawer-level="2"].drawer-scaling { transform: scale(0.92) translateY(-6px); } [data-drawer-level="3"].drawer-scaling { transform: scale(0.90) translateY(-4px); } /* keep the page tappable mid-transition (rapid tab switching) */ ::view-transition { pointer-events: none; } /* Theme texture overlays (scanlines, rays, grain) paint ABOVE all content; a real element with its own transition group keeps them on top and static mid-transition — a body::before overlay sinks into the root snapshot and blinks off the content for the duration of every transition. */ .flxn-theme-overlay { position: fixed; pointer-events: none; view-transition-name: theme-overlay; } /* Root (wallpaper + tab bar) is identical across routes — swap it instantly instead of crossfading; Safari's plus-lighter blend of the pair dims the whole backdrop mid-fade. Same explicit-state treatment for the header and theme overlay. */ ::view-transition-old(root), ::view-transition-old(app-header), ::view-transition-old(theme-overlay) { animation: none; opacity: 0; } ::view-transition-new(root), ::view-transition-new(app-header), ::view-transition-new(theme-overlay) { animation: none; opacity: 1; mix-blend-mode: normal; } ::view-transition-image-pair(root), ::view-transition-image-pair(app-header), ::view-transition-image-pair(theme-overlay), ::view-transition-image-pair(page) { isolation: auto; } ::view-transition-old(page), ::view-transition-new(page) { animation-duration: 180ms; animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1); mix-blend-mode: normal; } /* content pushes edge-to-edge over the static theme background; panes stay transparent so the background never appears to move. The header is not in these rules on purpose: it crossfades in place (its own group's default) — sliding its chrome reads as a band appearing on continuous-bg themes */ html[data-nav-direction="forward"]::view-transition-old(page) { animation: 250ms cubic-bezier(0.32, 0.72, 0, 1) both flxn-page-out-left; } html[data-nav-direction="forward"]::view-transition-new(page) { animation: 250ms cubic-bezier(0.32, 0.72, 0, 1) both flxn-page-in-right; } html[data-nav-direction="back"]::view-transition-old(page) { animation: 250ms cubic-bezier(0.32, 0.72, 0, 1) both flxn-page-out-right; } html[data-nav-direction="back"]::view-transition-new(page) { animation: 250ms cubic-bezier(0.32, 0.72, 0, 1) both flxn-page-in-left; } /* a header appearing or disappearing rides with its page instead of popping; opacity overrides the pinned instant-swap defaults above */ html[data-nav-direction="forward"][data-header-vt="exit"]::view-transition-old(app-header) { animation: 250ms cubic-bezier(0.32, 0.72, 0, 1) both flxn-page-out-left; opacity: 1; } html[data-nav-direction="back"][data-header-vt="exit"]::view-transition-old(app-header) { animation: 250ms cubic-bezier(0.32, 0.72, 0, 1) both flxn-page-out-right; opacity: 1; } html[data-nav-direction="forward"][data-header-vt="enter"]::view-transition-new(app-header) { animation: 250ms cubic-bezier(0.32, 0.72, 0, 1) both flxn-page-in-right; } html[data-nav-direction="back"][data-header-vt="enter"]::view-transition-new(app-header) { animation: 250ms cubic-bezier(0.32, 0.72, 0, 1) both flxn-page-in-left; } @keyframes flxn-page-out-left { to { transform: translateX(-100%); } } @keyframes flxn-page-in-right { from { transform: translateX(100%); } } @keyframes flxn-page-out-right { to { transform: translateX(100%); } } @keyframes flxn-page-in-left { from { transform: translateX(-100%); } } @keyframes flxn-vt-fade-out { to { opacity: 0; } } @keyframes flxn-vt-fade-in { from { opacity: 0; } } @media (prefers-reduced-motion: reduce) { ::view-transition-group(*), ::view-transition-image-pair(*) { animation: none !important; } ::view-transition-old(*) { animation: 120ms ease both flxn-vt-fade-out !important; z-index: auto !important; } ::view-transition-new(*) { animation: 120ms ease both flxn-vt-fade-in !important; } ::view-transition-old(root), ::view-transition-old(app-header), ::view-transition-old(theme-overlay), ::view-transition-new(root), ::view-transition-new(app-header), ::view-transition-new(theme-overlay) { animation: none !important; } .app, [data-drawer-level] { transition: none; } .app.drawer-scaling, [data-drawer-level].drawer-scaling { transform: none; border-radius: 0; } [data-vaul-drawer], [data-vaul-overlay] { animation: none !important; transition: none !important; } @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(0deg); } } }