feat(pwa): installable web app + landing web entry
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 19s
CI / ui (pull_request) Successful in 1m8s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m43s
CI / changes (pull_request) Successful in 2s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 19s
CI / ui (pull_request) Successful in 1m8s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m43s
Make the web SPA an installable PWA and surface it to users: - manifest.webmanifest + an install-only service worker + 192/512/maskable icons (ui/public); PWA head tags in index.html; the .webmanifest MIME type registered in the gateway (the distroless image has no /etc/mime.types). - Platform-adaptive install CTA (components/InstallApp.svelte + lib/pwa): one-tap on Chromium, manual Add-to-Home-Screen instructions on iOS Safari, hidden elsewhere / once installed / inside a Mini App. Shown under the logged-out login card and at the bottom of Settings. - The landing gains a third entry linking /app/ (the brand tile), with a caption under all three (Telegram / VK / Веб-версия). The service worker is navigation-only (network-first, cached-shell fallback); hashed assets and the Connect stream are untouched. It exists to satisfy Chromium's installability requirement and is the single growth point for a future opt-in offline mode. Tests: pwa.ts unit tests; a webui probe (manifest/sw.js content-type + the SPA-fallback-to-HTML trap); e2e for the one-tap CTA, the iOS instructions modal and the landing web entry. Docs: ARCHITECTURE §13, FUNCTIONAL (+ru), gateway README.
This commit is contained in:
+31
-16
@@ -80,20 +80,26 @@
|
||||
<section class="hero">
|
||||
<h1>{about.title}</h1>
|
||||
<p class="tagline">{t('landing.tagline')}</p>
|
||||
{#if tgLink || vkLink}
|
||||
<div class="entries">
|
||||
{#if tgLink}
|
||||
<a class="entry" href={tgLink} target="_blank" rel="noopener noreferrer" aria-label={t('landing.playTelegram')}>
|
||||
<img src="telegram-logo.svg" alt="" width="64" height="64" />
|
||||
</a>
|
||||
{/if}
|
||||
{#if vkLink}
|
||||
<a class="entry" href={vkLink} target="_blank" rel="noopener noreferrer" aria-label={t('landing.playVK')}>
|
||||
<img src="vk-logo.svg" alt="" width="64" height="64" />
|
||||
</a>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<!-- Platform entries: Telegram/VK are gated by their build vars; the web version (/app/) is
|
||||
always available. A caption under each clarifies what it opens. -->
|
||||
<div class="entries">
|
||||
{#if tgLink}
|
||||
<a class="entry" href={tgLink} target="_blank" rel="noopener noreferrer" aria-label={t('landing.playTelegram')}>
|
||||
<img src="telegram-logo.svg" alt="" width="64" height="64" />
|
||||
<span class="caption">{t('landing.captionTelegram')}</span>
|
||||
</a>
|
||||
{/if}
|
||||
{#if vkLink}
|
||||
<a class="entry" href={vkLink} target="_blank" rel="noopener noreferrer" aria-label={t('landing.playVK')}>
|
||||
<img src="vk-logo.svg" alt="" width="64" height="64" />
|
||||
<span class="caption">{t('landing.captionVK')}</span>
|
||||
</a>
|
||||
{/if}
|
||||
<a class="entry" href="/app/" aria-label={t('landing.playWeb')}>
|
||||
<img src="favicon.svg" alt="" width="64" height="64" />
|
||||
<span class="caption">{t('landing.captionWeb')}</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="info">
|
||||
@@ -207,8 +213,8 @@
|
||||
color: var(--text-muted);
|
||||
font-size: 1.05rem;
|
||||
}
|
||||
/* The platform entries are just the bigger logos in a row (no button chrome, no captions);
|
||||
each link keeps an aria-label for assistive tech. */
|
||||
/* The platform entries are the bigger logos in a row, each with a short caption below; the
|
||||
link keeps an aria-label for assistive tech. */
|
||||
.entries {
|
||||
align-self: center;
|
||||
display: flex;
|
||||
@@ -217,6 +223,11 @@
|
||||
}
|
||||
.entry {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
text-decoration: none;
|
||||
color: var(--text);
|
||||
}
|
||||
.entry img {
|
||||
display: block;
|
||||
@@ -225,6 +236,10 @@
|
||||
.entry:hover img {
|
||||
transform: scale(1.06);
|
||||
}
|
||||
.caption {
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.info {
|
||||
background: var(--surface-2);
|
||||
border-radius: var(--radius-sm);
|
||||
|
||||
Reference in New Issue
Block a user