feat(offline): offline-mode state, Settings toggle + blue chrome (Phase C)
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 1m9s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m39s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Has been skipped
CI / integration (pull_request) Has been skipped
CI / ui (pull_request) Successful in 1m9s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m39s
The deliberate offline mode is now visible: an installed web-PWA user with a confirmed email can switch to offline in Settings, and the header turns blue with an "Offline" chip. (Network gating, the dict preload, the offline lobby + local-game creation, and the service-worker precache follow in later Phase C steps.) - offline.ts / offline.svelte.ts: a sticky, device-scoped reactive offline flag (offlineMode), distinct from connection.svelte's transient reachability signal, with the pure persistence + readiness helpers (offlineReady / missingDicts) unit-tested. - Settings.svelte: an Online / Offline toggle, shown only for an installed web PWA with a confirmed email (the SW-launch + durable-account preconditions). - Header.svelte: a blue-tinted nav (color-mix from the accent, so it tracks light/dark and a Telegram theme override) + an "Offline" chip while offline; the deliberate mode suppresses the transient "Connecting…" indicator. Behaviour-preserving online (the toggle is hidden and offlineMode is false there; e2e 196 green). App entry stays within its size budget (111.6 / 112 KB gzip).
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { navigate } from '../lib/router.svelte';
|
||||
import { connection } from '../lib/connection.svelte';
|
||||
import { offlineMode } from '../lib/offline.svelte';
|
||||
import { t } from '../lib/i18n/index.svelte';
|
||||
import { app, openDebug } from '../lib/app.svelte';
|
||||
import Spinner from './Spinner.svelte';
|
||||
@@ -27,7 +28,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<header class="nav" class:grow>
|
||||
<header class="nav" class:grow class:offline={offlineMode.active}>
|
||||
<div class="bar">
|
||||
{#if showBack}
|
||||
<button class="icon back" onclick={() => back && navigate(back)} aria-label="Back">
|
||||
@@ -36,15 +37,20 @@
|
||||
{:else}
|
||||
<span class="spacer"></span>
|
||||
{/if}
|
||||
{#if connection.online}
|
||||
{#if connection.online || offlineMode.active}
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
||||
<h1 onclick={onTitleTap}>{title}</h1>
|
||||
{:else}
|
||||
<h1 class="connecting"><Spinner /> <span>{t('connection.connecting')}</span></h1>
|
||||
{/if}
|
||||
<!-- A right-hand spacer balances the back button so the title stays centred. -->
|
||||
<span class="spacer"></span>
|
||||
<!-- A right-hand spacer balances the back button so the title stays centred; in offline mode it
|
||||
carries the "Offline" chip so the deliberate mode is always visible (not just the blue tint). -->
|
||||
{#if offlineMode.active}
|
||||
<span class="chip">{t('settings.offline')}</span>
|
||||
{:else}
|
||||
<span class="spacer"></span>
|
||||
{/if}
|
||||
</div>
|
||||
<!-- The ad banner lives inside the nav, directly under the title bar, so it sits in the
|
||||
same place on every screen — and in the game (grown nav) the spare height falls below
|
||||
@@ -74,6 +80,12 @@
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
/* Deliberate offline mode: a blue-tinted nav, mixed from the accent so it tracks the light/dark
|
||||
theme (and any Telegram theme override) — the offline state is unmistakable at a glance. */
|
||||
.nav.offline {
|
||||
background: color-mix(in srgb, var(--accent) 20%, var(--bg-elev));
|
||||
border-bottom-color: color-mix(in srgb, var(--accent) 35%, var(--border));
|
||||
}
|
||||
.nav.grow {
|
||||
/* Grow into spare height (banner under the title, the board pinned to the bottom), but
|
||||
never shrink: on a short viewport the banner keeps its height and the board's own
|
||||
@@ -114,6 +126,22 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
/* The offline chip: a compact accent-tinted pill in the header's right slot. */
|
||||
.chip {
|
||||
min-width: 40px;
|
||||
height: 24px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 8px;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
color: var(--accent);
|
||||
background: color-mix(in srgb, var(--accent) 16%, transparent);
|
||||
border: 1px solid color-mix(in srgb, var(--accent) 45%, transparent);
|
||||
border-radius: 999px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.back {
|
||||
background: none;
|
||||
border: none;
|
||||
|
||||
Reference in New Issue
Block a user