diff --git a/ui/src/App.svelte b/ui/src/App.svelte index 9e42fb6..3917034 100644 --- a/ui/src/App.svelte +++ b/ui/src/App.svelte @@ -32,8 +32,9 @@ // Screen transitions: the lobby is the navigation root. Entering a screen from the // lobby slides it in from the right (forward); returning to the lobby slides the - // screen out to the right and reveals the lobby (back). Transitions are local, so - // they do not play on the initial mount, and collapse to nothing under reduce-motion. + // screen out to the right and reveals the lobby (back). The first pane shown after boot + // does not slide (the `started` gate below), and transitions collapse to nothing under + // reduce-motion. // Slide direction by route depth: going deeper (lobby → game → chat/check) slides forward, // returning to a shallower screen slides back. A plain "lobby is back" rule slid the chat/check // back-to-the-game the wrong way. dir is read with the previous depth (the effect updates it @@ -52,7 +53,15 @@ const enterSign = $derived(dir === 'forward' ? 1 : -1); const leaveSign = $derived(dir === 'forward' ? -1 : 1); const routeKey = $derived(router.route.name + (router.route.params.id ?? '')); - const animMs = $derived(app.reduceMotion ? 0 : 260); + // The first pane shown once the app is ready must not slide: it would look like the app + // was navigated into rather than launched. The pane is a local transition inside a {#key} + // block, so it plays on that block's own first mount anyway — hold the duration at 0 until + // just after that mount (the effect runs post-render), then animate every later change. + let started = $state(false); + $effect(() => { + if (app.ready) started = true; + }); + const animMs = $derived(!started || app.reduceMotion ? 0 : 260); // slideX slides a pane horizontally by a full width. sign>0 enters from / exits to // the right; sign<0 the left. Percentage keeps it viewport-relative without reading diff --git a/ui/src/components/TabBar.svelte b/ui/src/components/TabBar.svelte index b6ebbb3..d29a1ad 100644 --- a/ui/src/components/TabBar.svelte +++ b/ui/src/components/TabBar.svelte @@ -34,12 +34,13 @@ width: 100%; user-select: none; -webkit-user-select: none; + -webkit-tap-highlight-color: transparent; /* no WebKit flash on tap */ } :global(.tab:disabled) { opacity: 0.4; } - /* The icon square hugs the emoji (just a little padding) so it is the press-highlight - target and the badge can sit on its corner. */ + /* The icon square hugs the emoji (just a little padding) so the badge can sit on its + corner. */ :global(.tab .sq) { position: relative; display: inline-grid; @@ -50,12 +51,9 @@ line-height: 1; transition: background-color 0.12s; } - :global(.tab:active:not(:disabled) .sq) { - background: var(--surface-2); - } /* A tab that navigates between peer views (Settings / Comms hubs) stays highlighted - while selected: a filled square with an accent underline, distinct from the - momentary press tint above. */ + while selected: a filled square with an accent underline. A tap itself leaves no + highlight — there is no press tint, and the WebKit tap flash is disabled on .tab. */ :global(.tab.active .sq) { background: var(--surface-2); box-shadow: inset 0 -2px 0 var(--accent);