slide animation
CI/CD Pipeline / Build and Push PocketBase Docker Image (push) Successful in 13s
CI/CD Pipeline / i18n Catalog Check (push) Successful in 31s
CI/CD Pipeline / Build and Push App Docker Image (push) Successful in 3m25s
CI/CD Pipeline / Deploy to Kubernetes (push) Successful in 8m29s
CI/CD Pipeline / Build and Push PocketBase Docker Image (push) Successful in 13s
CI/CD Pipeline / i18n Catalog Check (push) Successful in 31s
CI/CD Pipeline / Build and Push App Docker Image (push) Successful in 3m25s
CI/CD Pipeline / Deploy to Kubernetes (push) Successful in 8m29s
This commit is contained in:
+72
-28
@@ -50,62 +50,97 @@ textarea,
|
||||
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-new(root),
|
||||
::view-transition-old(page),
|
||||
::view-transition-new(page),
|
||||
::view-transition-old(app-header),
|
||||
::view-transition-new(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: 300ms cubic-bezier(0.32, 0.72, 0, 1) both flxn-page-under-out;
|
||||
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: 300ms cubic-bezier(0.32, 0.72, 0, 1) both flxn-page-over-in;
|
||||
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: 300ms cubic-bezier(0.32, 0.72, 0, 1) both flxn-page-over-out;
|
||||
z-index: 1;
|
||||
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: 300ms cubic-bezier(0.32, 0.72, 0, 1) both flxn-page-under-in;
|
||||
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: 300ms cubic-bezier(0.32, 0.72, 0, 1) both flxn-header-out;
|
||||
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: 300ms cubic-bezier(0.32, 0.72, 0, 1) both flxn-page-over-out;
|
||||
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: 300ms cubic-bezier(0.32, 0.72, 0, 1) both flxn-page-over-in;
|
||||
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: 300ms cubic-bezier(0.32, 0.72, 0, 1) both flxn-header-in;
|
||||
animation: 250ms cubic-bezier(0.32, 0.72, 0, 1) both flxn-page-in-left;
|
||||
}
|
||||
|
||||
@keyframes flxn-page-over-in {
|
||||
@keyframes flxn-page-out-left {
|
||||
to { transform: translateX(-100%); }
|
||||
}
|
||||
@keyframes flxn-page-in-right {
|
||||
from { transform: translateX(100%); }
|
||||
}
|
||||
@keyframes flxn-page-over-out {
|
||||
@keyframes flxn-page-out-right {
|
||||
to { transform: translateX(100%); }
|
||||
}
|
||||
@keyframes flxn-page-under-out {
|
||||
to { transform: translateX(-25%); opacity: 0.8; }
|
||||
}
|
||||
@keyframes flxn-page-under-in {
|
||||
from { transform: translateX(-25%); opacity: 0.8; }
|
||||
}
|
||||
|
||||
@keyframes flxn-header-out {
|
||||
45% { opacity: 0; }
|
||||
to { transform: translateX(-25%); opacity: 0; }
|
||||
}
|
||||
@keyframes flxn-header-in {
|
||||
from { transform: translateX(-25%); opacity: 0; }
|
||||
@keyframes flxn-page-in-left {
|
||||
from { transform: translateX(-100%); }
|
||||
}
|
||||
|
||||
@keyframes flxn-vt-fade-out {
|
||||
@@ -130,6 +165,15 @@ html[data-nav-direction="back"][data-header-vt="enter"]::view-transition-new(app
|
||||
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;
|
||||
|
||||
@@ -183,7 +183,19 @@ function RootComponent() {
|
||||
ensureSuperTokensFrontend();
|
||||
initTelemetry(router);
|
||||
if (import.meta.env.PROD && 'serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('/sw.js').catch(() => track('sw_register_failed'));
|
||||
// first install claims the page too; only reload on real updates
|
||||
const hadController = !!navigator.serviceWorker.controller;
|
||||
let reloading = false;
|
||||
navigator.serviceWorker.addEventListener('controllerchange', () => {
|
||||
if (!hadController || reloading) return;
|
||||
reloading = true;
|
||||
window.location.reload();
|
||||
});
|
||||
navigator.serviceWorker.register('/sw.js').then((registration) => {
|
||||
document.addEventListener('visibilitychange', () => {
|
||||
if (document.visibilityState === 'visible') registration.update().catch(() => {});
|
||||
});
|
||||
}).catch(() => track('sw_register_failed'));
|
||||
}
|
||||
}, [router]);
|
||||
|
||||
@@ -243,6 +255,7 @@ function RootDocument({ children }: { children: React.ReactNode }) {
|
||||
}}
|
||||
>
|
||||
<div className="app">{children}</div>
|
||||
<div className="flxn-theme-overlay" aria-hidden="true" />
|
||||
<Scripts />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -48,11 +48,9 @@ function SwipeableTabs({
|
||||
}, [search?.tab, tabs, defaultTab]);
|
||||
|
||||
const [activeTab, setActiveTab] = useState(getActiveTabFromUrl);
|
||||
const [mountAll, setMountAll] = useState(false);
|
||||
useEffect(() => {
|
||||
const timeoutId = setTimeout(() => startTransition(() => setMountAll(true)), 400);
|
||||
return () => clearTimeout(timeoutId);
|
||||
}, []);
|
||||
const [visitedTabs, setVisitedTabs] = useState<ReadonlySet<number>>(
|
||||
() => new Set([getActiveTabFromUrl()])
|
||||
);
|
||||
const [stuck, setStuck] = useState(false);
|
||||
const sentinelRef = useRef<HTMLDivElement>(null);
|
||||
const [rootRef, setRootRef] = useState<HTMLDivElement | null>(null);
|
||||
@@ -65,6 +63,9 @@ function SwipeableTabs({
|
||||
if (index === activeTab || index < 0 || index >= tabs.length) return;
|
||||
|
||||
setActiveTab(index);
|
||||
startTransition(() => {
|
||||
setVisitedTabs((prev) => (prev.has(index) ? prev : new Set(prev).add(index)));
|
||||
});
|
||||
embla?.scrollTo(index);
|
||||
onTabChange?.(index, tabs[index]);
|
||||
|
||||
@@ -230,7 +231,7 @@ function SwipeableTabs({
|
||||
{tabs.map((tab, index) => (
|
||||
<Carousel.Slide key={`${tab.label}-content-${index}`}>
|
||||
<Box ref={setSlideRef(index)} style={{ height: "auto", width: "100%", maxWidth: "100vw", overflow: "hidden" }}>
|
||||
{(mountAll || index === activeTab) ? tab.content : null}
|
||||
{(visitedTabs.has(index) || index === activeTab) ? tab.content : null}
|
||||
</Box>
|
||||
</Carousel.Slide>
|
||||
))}
|
||||
|
||||
@@ -52,11 +52,10 @@ const Layout: React.FC<PropsWithChildren> = ({ children }) => {
|
||||
maw='100dvw'
|
||||
style={{ overflow: 'hidden' }}
|
||||
>
|
||||
{/* pane box must be opaque and route-invariant or slides drift */}
|
||||
{/* pane box must be route-invariant or slides drift */}
|
||||
<Box
|
||||
style={{
|
||||
viewTransitionName: 'page',
|
||||
backgroundColor: 'var(--mantine-color-body)',
|
||||
marginTop: 'calc(-1 * var(--app-shell-header-offset, 0px))',
|
||||
paddingTop: 'var(--app-shell-header-offset, 0px)',
|
||||
height: 'calc(100% + var(--app-shell-header-offset, 0px))',
|
||||
|
||||
@@ -178,15 +178,14 @@
|
||||
/* 3. Fixed sky backdrop with static bubbles (below all content) */
|
||||
/* ---------------------------------------------------------------- */
|
||||
|
||||
[data-theme="aero"] body::before {
|
||||
content: "";
|
||||
[data-theme="aero"] .flxn-theme-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
[data-theme="aero"][data-mantine-color-scheme="light"] body::before {
|
||||
[data-theme="aero"][data-mantine-color-scheme="light"] .flxn-theme-overlay {
|
||||
background-color: #e9f6fe;
|
||||
background-image:
|
||||
radial-gradient(circle 0.6rem at 13.5% 20%, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0) 70%),
|
||||
@@ -198,7 +197,7 @@
|
||||
linear-gradient(180deg, #a5daf6 0%, #c9e9fa 32%, #e9f6fe 60%, #ffffff 100%);
|
||||
}
|
||||
|
||||
[data-theme="aero"][data-mantine-color-scheme="dark"] body::before {
|
||||
[data-theme="aero"][data-mantine-color-scheme="dark"] .flxn-theme-overlay {
|
||||
background-color: #071e2d;
|
||||
background-image:
|
||||
radial-gradient(circle 4rem at 16% 24%, rgba(120, 205, 245, 0) 60%, rgba(120, 205, 245, 0.2) 67%, rgba(120, 205, 245, 0) 74%),
|
||||
|
||||
@@ -203,8 +203,7 @@
|
||||
|
||||
/* ---------- 4. Backdrop — studio key light + clay grain ---------- */
|
||||
|
||||
[data-theme="clay"] body::before {
|
||||
content: "";
|
||||
[data-theme="clay"] .flxn-theme-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: -1; /* behind content — never tints text or surfaces */
|
||||
@@ -212,7 +211,7 @@
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.05'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
[data-theme="clay"][data-mantine-color-scheme="light"] body::before {
|
||||
[data-theme="clay"][data-mantine-color-scheme="light"] .flxn-theme-overlay {
|
||||
background-image: radial-gradient(
|
||||
120% 70% at 50% 0%,
|
||||
rgba(255, 252, 244, 0.65),
|
||||
@@ -221,7 +220,7 @@
|
||||
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.05'/%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
[data-theme="clay"][data-mantine-color-scheme="dark"] body::before {
|
||||
[data-theme="clay"][data-mantine-color-scheme="dark"] .flxn-theme-overlay {
|
||||
background-image: radial-gradient(
|
||||
110% 65% at 50% 0%,
|
||||
rgba(255, 208, 158, 0.09),
|
||||
|
||||
@@ -363,8 +363,7 @@
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* 4. Sunburst fan — faint gold rays from top center */
|
||||
/* ------------------------------------------------------------------ */
|
||||
[data-theme="deco"] body::before {
|
||||
content: "";
|
||||
[data-theme="deco"] .flxn-theme-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 9998;
|
||||
|
||||
@@ -409,8 +409,7 @@
|
||||
* 3. Full-page decoration: candlelit vignette (dark scheme only)
|
||||
* ---------------------------------------------------------------- */
|
||||
|
||||
[data-theme="gothic"][data-mantine-color-scheme="dark"] body::before {
|
||||
content: "";
|
||||
[data-theme="gothic"][data-mantine-color-scheme="dark"] .flxn-theme-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 9998;
|
||||
|
||||
@@ -153,8 +153,7 @@
|
||||
/* 3. Fixed backdrop (below all content) */
|
||||
/* ---------------------------------------------------------------- */
|
||||
|
||||
[data-theme="ocean"] body::before {
|
||||
content: "";
|
||||
[data-theme="ocean"] .flxn-theme-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: -1;
|
||||
@@ -163,7 +162,7 @@
|
||||
|
||||
/* Abyssal descent: deep teal fading to ink navy, sparse static bubbles
|
||||
rising (bigger and brighter near the sea floor, fading toward the top). */
|
||||
[data-theme="ocean"][data-mantine-color-scheme="dark"] body::before {
|
||||
[data-theme="ocean"][data-mantine-color-scheme="dark"] .flxn-theme-overlay {
|
||||
background-color: #062632;
|
||||
background-image:
|
||||
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='260' height='640' viewBox='0 0 260 640'%3E%3Cg fill='none' stroke='%239beee2'%3E%3Ccircle cx='36' cy='584' r='5' stroke-opacity='.13'/%3E%3Ccircle cx='44' cy='512' r='3' stroke-opacity='.1'/%3E%3Ccircle cx='210' cy='612' r='4' stroke-opacity='.12'/%3E%3Ccircle cx='202' cy='540' r='2.5' stroke-opacity='.09'/%3E%3Ccircle cx='128' cy='430' r='3.5' stroke-opacity='.1'/%3E%3Ccircle cx='120' cy='360' r='2' stroke-opacity='.08'/%3E%3Ccircle cx='232' cy='300' r='3' stroke-opacity='.09'/%3E%3Ccircle cx='60' cy='250' r='2.5' stroke-opacity='.08'/%3E%3Ccircle cx='150' cy='160' r='2' stroke-opacity='.07'/%3E%3Ccircle cx='90' cy='80' r='1.5' stroke-opacity='.06'/%3E%3C/g%3E%3Cg fill='%239beee2'%3E%3Ccircle cx='70' cy='470' r='1.2' fill-opacity='.1'/%3E%3Ccircle cx='180' cy='390' r='1' fill-opacity='.09'/%3E%3Ccircle cx='40' cy='330' r='1' fill-opacity='.08'/%3E%3Ccircle cx='222' cy='190' r='1' fill-opacity='.07'/%3E%3Ccircle cx='140' cy='40' r='1' fill-opacity='.05'/%3E%3C/g%3E%3C/svg%3E"),
|
||||
@@ -175,7 +174,7 @@
|
||||
|
||||
/* Sunlit shallows: seafoam fading to a sandy floor, with a faint white
|
||||
caustic ripple tile — refracted light, kept very subtle. */
|
||||
[data-theme="ocean"][data-mantine-color-scheme="light"] body::before {
|
||||
[data-theme="ocean"][data-mantine-color-scheme="light"] .flxn-theme-overlay {
|
||||
background-color: #eaf6f6;
|
||||
background-image:
|
||||
url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='320' height='220' viewBox='0 0 320 220'%3E%3Cg fill='none' stroke='%23ffffff' stroke-width='1.4'%3E%3Cpath d='M0 40 Q 40 10 80 42 T 160 44 T 240 38 T 320 42' stroke-opacity='.4'/%3E%3Cpath d='M0 110 Q 45 82 90 112 T 180 110 T 268 114 T 320 108' stroke-opacity='.3'/%3E%3Cpath d='M0 180 Q 50 152 100 182 T 200 178 T 300 184 T 320 180' stroke-opacity='.35'/%3E%3C/g%3E%3C/svg%3E"),
|
||||
|
||||
@@ -414,8 +414,7 @@
|
||||
* 3. Full-page decoration: worn parchment edges / torchlight
|
||||
* ---------------------------------------------------------------- */
|
||||
|
||||
[data-theme="quest"][data-mantine-color-scheme="light"] body::before {
|
||||
content: "";
|
||||
[data-theme="quest"][data-mantine-color-scheme="light"] .flxn-theme-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 9998;
|
||||
@@ -426,8 +425,7 @@
|
||||
radial-gradient(55% 38% at 95% 98%, rgba(122, 91, 42, 0.07), transparent 70%);
|
||||
}
|
||||
|
||||
[data-theme="quest"][data-mantine-color-scheme="dark"] body::before {
|
||||
content: "";
|
||||
[data-theme="quest"][data-mantine-color-scheme="dark"] .flxn-theme-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 9998;
|
||||
|
||||
@@ -151,8 +151,7 @@
|
||||
/* 3. Paper grain / chalk dust — one fixed overlay, feTurbulence */
|
||||
/* ---------------------------------------------------------------- */
|
||||
|
||||
[data-theme="sketch"] body::before {
|
||||
content: "";
|
||||
[data-theme="sketch"] .flxn-theme-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 9998;
|
||||
@@ -163,11 +162,19 @@
|
||||
opacity: 0.05;
|
||||
}
|
||||
|
||||
[data-theme="sketch"][data-mantine-color-scheme="dark"] body::before {
|
||||
[data-theme="sketch"][data-mantine-color-scheme="dark"] .flxn-theme-overlay {
|
||||
mix-blend-mode: screen;
|
||||
opacity: 0.07;
|
||||
}
|
||||
|
||||
[data-theme="sketch"]::view-transition-new(theme-overlay) {
|
||||
mix-blend-mode: multiply;
|
||||
}
|
||||
|
||||
[data-theme="sketch"][data-mantine-color-scheme="dark"]::view-transition-new(theme-overlay) {
|
||||
mix-blend-mode: screen;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------- */
|
||||
/* 4. Surfaces — every card its own doodle */
|
||||
/* ---------------------------------------------------------------- */
|
||||
|
||||
@@ -575,8 +575,7 @@
|
||||
}
|
||||
|
||||
/* 4. Decoration: fixed scanline overlay + faint RGB aperture mask */
|
||||
[data-theme="terminal"] body::before {
|
||||
content: "";
|
||||
[data-theme="terminal"] .flxn-theme-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 9998;
|
||||
@@ -589,7 +588,7 @@
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
[data-theme="terminal"] body::before {
|
||||
[data-theme="terminal"] .flxn-theme-overlay {
|
||||
animation: terminal-phosphor-drift 9s ease-in-out infinite;
|
||||
}
|
||||
@keyframes terminal-phosphor-drift {
|
||||
|
||||
@@ -150,8 +150,7 @@
|
||||
|
||||
/* ---------- 3. Decoration: static perspective floor-grid hint ---------- */
|
||||
|
||||
[data-theme="wireframe"] body::before {
|
||||
content: "";
|
||||
[data-theme="wireframe"] .flxn-theme-overlay {
|
||||
position: fixed;
|
||||
left: -30vw;
|
||||
right: -30vw;
|
||||
|
||||
Reference in New Issue
Block a user