v6.8.12: new design for exchanging invitations
This commit is contained in:
@@ -1,5 +1,11 @@
|
||||
# Changelog
|
||||
|
||||
## v6.8.12 — New design for exchanging invitations
|
||||
|
||||
New design for the screens where you exchange invitations and answers with your
|
||||
peer, and for the safety code check. The download buttons now give you desktop
|
||||
version 1.0.5.
|
||||
|
||||
## v6.8.8 — Project support section
|
||||
|
||||
Added a section to support the project.
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
No accounts. No servers storing your messages. No installation required.
|
||||
|
||||
[](LICENSE)
|
||||
[](CHANGELOG.md)
|
||||
[](CHANGELOG.md)
|
||||
[](https://snapcraft.io/securebit-chat)
|
||||
[](#install-as-an-app)
|
||||
[](#security-model)
|
||||
@@ -31,7 +31,7 @@ From the web app at [securebit.chat](https://securebit.chat) — no install, no
|
||||
|
||||
| Open a channel | Verify the safety code |
|
||||
| :---: | :---: |
|
||||
|  |  |
|
||||
|  |  |
|
||||
| Your device generates the keys and a one-time invitation. Nothing touches a server. | Compare the code out of band. Until both sides confirm, the chat stays locked. |
|
||||
|
||||
| Encrypted conversation | Voice and video |
|
||||
|
||||
+19
-19
@@ -30,18 +30,18 @@
|
||||
|
||||
<!-- PWA Manifest -->
|
||||
<link rel="manifest" href="/ar/manifest.json">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
|
||||
|
||||
<!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html>
|
||||
from localStorage while the parser is still in <head>, so the page paints in the
|
||||
chosen theme on the first frame instead of flashing dark and correcting itself.
|
||||
Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). -->
|
||||
<script src="/src/scripts/theme-boot.js?v=1790225533889"></script>
|
||||
<script src="/src/scripts/theme-boot.js?v=1790540575787"></script>
|
||||
|
||||
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
|
||||
not replayed, so the listener has to exist before Chrome decides the page
|
||||
is installable. Deferred/module scripts are already too late. -->
|
||||
<script src="/src/scripts/pwa-install-capture.js?v=1790225533889"></script>
|
||||
<script src="/src/scripts/pwa-install-capture.js?v=1790540575787"></script>
|
||||
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
@@ -116,7 +116,7 @@
|
||||
<link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png">
|
||||
|
||||
<!-- Apple Touch Icons -->
|
||||
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790225533889">
|
||||
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790540575787">
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png">
|
||||
@@ -125,7 +125,7 @@
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790225533889">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790540575787">
|
||||
|
||||
<!-- Microsoft Tiles -->
|
||||
<meta name="msapplication-TileColor" content="#ff6b35">
|
||||
@@ -264,7 +264,7 @@
|
||||
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
|
||||
both execute in document order after parsing, so React still runs before the
|
||||
app modules below, but the parser / first paint is no longer blocked. -->
|
||||
<script defer src="/config/ice-servers.js?v=1790225533889"></script>
|
||||
<script defer src="/config/ice-servers.js?v=1790540575787"></script>
|
||||
<script defer src="/libs/react/react.production.min.js"></script>
|
||||
<script defer src="/libs/react-dom/react-dom.production.min.js"></script>
|
||||
<!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only —
|
||||
@@ -278,8 +278,8 @@
|
||||
the end of <body>: 21 KB of CSS in total, but eight round trips before the
|
||||
browser could paint, which GTmetrix measured at 441 ms on a throttled mobile
|
||||
connection. Bytes were never the problem; requests were. -->
|
||||
<link rel="stylesheet" href="/assets/app.css?v=1790225533889">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
|
||||
<link rel="stylesheet" href="/assets/app.css?v=1790540575787">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
|
||||
<!-- Preload only the fonts needed for first paint. Inter is one variable file that
|
||||
answers for every weight, so there is one to preload rather than the two of five
|
||||
copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and
|
||||
@@ -287,19 +287,19 @@
|
||||
<link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
|
||||
<script defer src="/src/scripts/load-async-css.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/load-async-css.js?v=1790540575787"></script>
|
||||
<noscript>
|
||||
<link rel="stylesheet" href="/libs/prism/prism.css">
|
||||
</noscript>
|
||||
<script defer src="/src/scripts/fa-check.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/fa-check.js?v=1790540575787"></script>
|
||||
<!-- This locale's dictionary, registered before anything can ask for a string.
|
||||
Only the default locale's is bundled (src/i18n/index.js imports it, because t()
|
||||
falls back to it); every other page loads its own here instead of all thirteen
|
||||
riding along inside dist/app.js. Absent on the default locale's own page. -->
|
||||
<script type="module" src="/src/i18n/dict/ar.js?v=1790225533889"></script>
|
||||
<script type="module" src="/src/i18n/dict/ar.js?v=1790540575787"></script>
|
||||
<!-- Update Manager - система принудительного обновления -->
|
||||
<script defer src="/src/utils/updateManager.js?v=1790225533889"></script>
|
||||
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script>
|
||||
<script defer src="/src/utils/updateManager.js?v=1790540575787"></script>
|
||||
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790540575787"></script>
|
||||
<!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the
|
||||
app has mounted and the browser is idle, so the 142 KB it weighs is off the
|
||||
first load. src/components/QRScanner.js used to be here too and registered
|
||||
@@ -487,12 +487,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="/dist/app-boot.js?v=1790225533889"></script>
|
||||
<script type="module" src="/dist/app.js?v=1790225533889"></script>
|
||||
<script type="module" src="/dist/app-boot.js?v=1790540575787"></script>
|
||||
<script type="module" src="/dist/app.js?v=1790540575787"></script>
|
||||
|
||||
<script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script>
|
||||
<script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script>
|
||||
<script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script>
|
||||
<script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/pwa-register.js?v=1790540575787"></script>
|
||||
<script src="/src/pwa/install-prompt.js?v=1790540575787" type="module"></script>
|
||||
<script src="/src/pwa/pwa-manager.js?v=1790540575787" type="module"></script>
|
||||
<script defer src="/src/scripts/pwa-offline-test.js?v=1790540575787"></script>
|
||||
</body>
|
||||
</html>
|
||||
+1
-1
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 127 KiB After Width: | Height: | Size: 98 KiB |
+19
-19
@@ -30,18 +30,18 @@
|
||||
|
||||
<!-- PWA Manifest -->
|
||||
<link rel="manifest" href="/de/manifest.json">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
|
||||
|
||||
<!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html>
|
||||
from localStorage while the parser is still in <head>, so the page paints in the
|
||||
chosen theme on the first frame instead of flashing dark and correcting itself.
|
||||
Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). -->
|
||||
<script src="/src/scripts/theme-boot.js?v=1790225533889"></script>
|
||||
<script src="/src/scripts/theme-boot.js?v=1790540575787"></script>
|
||||
|
||||
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
|
||||
not replayed, so the listener has to exist before Chrome decides the page
|
||||
is installable. Deferred/module scripts are already too late. -->
|
||||
<script src="/src/scripts/pwa-install-capture.js?v=1790225533889"></script>
|
||||
<script src="/src/scripts/pwa-install-capture.js?v=1790540575787"></script>
|
||||
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
@@ -116,7 +116,7 @@
|
||||
<link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png">
|
||||
|
||||
<!-- Apple Touch Icons -->
|
||||
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790225533889">
|
||||
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790540575787">
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png">
|
||||
@@ -125,7 +125,7 @@
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790225533889">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790540575787">
|
||||
|
||||
<!-- Microsoft Tiles -->
|
||||
<meta name="msapplication-TileColor" content="#ff6b35">
|
||||
@@ -264,7 +264,7 @@
|
||||
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
|
||||
both execute in document order after parsing, so React still runs before the
|
||||
app modules below, but the parser / first paint is no longer blocked. -->
|
||||
<script defer src="/config/ice-servers.js?v=1790225533889"></script>
|
||||
<script defer src="/config/ice-servers.js?v=1790540575787"></script>
|
||||
<script defer src="/libs/react/react.production.min.js"></script>
|
||||
<script defer src="/libs/react-dom/react-dom.production.min.js"></script>
|
||||
<!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only —
|
||||
@@ -278,8 +278,8 @@
|
||||
the end of <body>: 21 KB of CSS in total, but eight round trips before the
|
||||
browser could paint, which GTmetrix measured at 441 ms on a throttled mobile
|
||||
connection. Bytes were never the problem; requests were. -->
|
||||
<link rel="stylesheet" href="/assets/app.css?v=1790225533889">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
|
||||
<link rel="stylesheet" href="/assets/app.css?v=1790540575787">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
|
||||
<!-- Preload only the fonts needed for first paint. Inter is one variable file that
|
||||
answers for every weight, so there is one to preload rather than the two of five
|
||||
copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and
|
||||
@@ -287,19 +287,19 @@
|
||||
<link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
|
||||
<script defer src="/src/scripts/load-async-css.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/load-async-css.js?v=1790540575787"></script>
|
||||
<noscript>
|
||||
<link rel="stylesheet" href="/libs/prism/prism.css">
|
||||
</noscript>
|
||||
<script defer src="/src/scripts/fa-check.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/fa-check.js?v=1790540575787"></script>
|
||||
<!-- This locale's dictionary, registered before anything can ask for a string.
|
||||
Only the default locale's is bundled (src/i18n/index.js imports it, because t()
|
||||
falls back to it); every other page loads its own here instead of all thirteen
|
||||
riding along inside dist/app.js. Absent on the default locale's own page. -->
|
||||
<script type="module" src="/src/i18n/dict/de.js?v=1790225533889"></script>
|
||||
<script type="module" src="/src/i18n/dict/de.js?v=1790540575787"></script>
|
||||
<!-- Update Manager - система принудительного обновления -->
|
||||
<script defer src="/src/utils/updateManager.js?v=1790225533889"></script>
|
||||
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script>
|
||||
<script defer src="/src/utils/updateManager.js?v=1790540575787"></script>
|
||||
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790540575787"></script>
|
||||
<!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the
|
||||
app has mounted and the browser is idle, so the 142 KB it weighs is off the
|
||||
first load. src/components/QRScanner.js used to be here too and registered
|
||||
@@ -487,12 +487,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="/dist/app-boot.js?v=1790225533889"></script>
|
||||
<script type="module" src="/dist/app.js?v=1790225533889"></script>
|
||||
<script type="module" src="/dist/app-boot.js?v=1790540575787"></script>
|
||||
<script type="module" src="/dist/app.js?v=1790540575787"></script>
|
||||
|
||||
<script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script>
|
||||
<script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script>
|
||||
<script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script>
|
||||
<script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/pwa-register.js?v=1790540575787"></script>
|
||||
<script src="/src/pwa/install-prompt.js?v=1790540575787" type="module"></script>
|
||||
<script src="/src/pwa/pwa-manager.js?v=1790540575787" type="module"></script>
|
||||
<script defer src="/src/scripts/pwa-offline-test.js?v=1790540575787"></script>
|
||||
</body>
|
||||
</html>
|
||||
Vendored
+3
-3
File diff suppressed because one or more lines are too long
Vendored
+2
-2
File diff suppressed because one or more lines are too long
Vendored
+9
-9
File diff suppressed because one or more lines are too long
Vendored
+3
-3
File diff suppressed because one or more lines are too long
+2
-2
@@ -7,7 +7,7 @@ this document describes.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| Release | v6.8.8 |
|
||||
| Release | v6.8.12 |
|
||||
| Protocol version | 4.1 |
|
||||
| Ratchet wire version | 1 |
|
||||
|
||||
@@ -347,5 +347,5 @@ no leg may stop a capture it borrowed.
|
||||
|
||||
## Scope
|
||||
|
||||
This describes the browser implementation as it stands in v6.8.8. It is not a
|
||||
This describes the browser implementation as it stands in v6.8.12. It is not a
|
||||
substitute for independent cryptographic review.
|
||||
|
||||
@@ -153,7 +153,7 @@
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td>Release</td>
|
||||
<td>v6.8.8</td>
|
||||
<td>v6.8.12</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Protocol version</td>
|
||||
@@ -486,7 +486,7 @@
|
||||
shared across every leg, and stopped when the call, the group or the tab ends;
|
||||
no leg may stop a capture it borrowed.</p>
|
||||
<h2 id="scope">Scope</h2>
|
||||
<p>This describes the browser implementation as it stands in v6.8.8. It is not a
|
||||
<p>This describes the browser implementation as it stands in v6.8.12. It is not a
|
||||
substitute for independent cryptographic review.</p>
|
||||
|
||||
<nav class="more">
|
||||
|
||||
+19
-19
@@ -30,18 +30,18 @@
|
||||
|
||||
<!-- PWA Manifest -->
|
||||
<link rel="manifest" href="/es/manifest.json">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
|
||||
|
||||
<!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html>
|
||||
from localStorage while the parser is still in <head>, so the page paints in the
|
||||
chosen theme on the first frame instead of flashing dark and correcting itself.
|
||||
Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). -->
|
||||
<script src="/src/scripts/theme-boot.js?v=1790225533889"></script>
|
||||
<script src="/src/scripts/theme-boot.js?v=1790540575787"></script>
|
||||
|
||||
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
|
||||
not replayed, so the listener has to exist before Chrome decides the page
|
||||
is installable. Deferred/module scripts are already too late. -->
|
||||
<script src="/src/scripts/pwa-install-capture.js?v=1790225533889"></script>
|
||||
<script src="/src/scripts/pwa-install-capture.js?v=1790540575787"></script>
|
||||
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
@@ -116,7 +116,7 @@
|
||||
<link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png">
|
||||
|
||||
<!-- Apple Touch Icons -->
|
||||
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790225533889">
|
||||
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790540575787">
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png">
|
||||
@@ -125,7 +125,7 @@
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790225533889">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790540575787">
|
||||
|
||||
<!-- Microsoft Tiles -->
|
||||
<meta name="msapplication-TileColor" content="#ff6b35">
|
||||
@@ -264,7 +264,7 @@
|
||||
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
|
||||
both execute in document order after parsing, so React still runs before the
|
||||
app modules below, but the parser / first paint is no longer blocked. -->
|
||||
<script defer src="/config/ice-servers.js?v=1790225533889"></script>
|
||||
<script defer src="/config/ice-servers.js?v=1790540575787"></script>
|
||||
<script defer src="/libs/react/react.production.min.js"></script>
|
||||
<script defer src="/libs/react-dom/react-dom.production.min.js"></script>
|
||||
<!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only —
|
||||
@@ -278,8 +278,8 @@
|
||||
the end of <body>: 21 KB of CSS in total, but eight round trips before the
|
||||
browser could paint, which GTmetrix measured at 441 ms on a throttled mobile
|
||||
connection. Bytes were never the problem; requests were. -->
|
||||
<link rel="stylesheet" href="/assets/app.css?v=1790225533889">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
|
||||
<link rel="stylesheet" href="/assets/app.css?v=1790540575787">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
|
||||
<!-- Preload only the fonts needed for first paint. Inter is one variable file that
|
||||
answers for every weight, so there is one to preload rather than the two of five
|
||||
copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and
|
||||
@@ -287,19 +287,19 @@
|
||||
<link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
|
||||
<script defer src="/src/scripts/load-async-css.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/load-async-css.js?v=1790540575787"></script>
|
||||
<noscript>
|
||||
<link rel="stylesheet" href="/libs/prism/prism.css">
|
||||
</noscript>
|
||||
<script defer src="/src/scripts/fa-check.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/fa-check.js?v=1790540575787"></script>
|
||||
<!-- This locale's dictionary, registered before anything can ask for a string.
|
||||
Only the default locale's is bundled (src/i18n/index.js imports it, because t()
|
||||
falls back to it); every other page loads its own here instead of all thirteen
|
||||
riding along inside dist/app.js. Absent on the default locale's own page. -->
|
||||
<script type="module" src="/src/i18n/dict/es.js?v=1790225533889"></script>
|
||||
<script type="module" src="/src/i18n/dict/es.js?v=1790540575787"></script>
|
||||
<!-- Update Manager - система принудительного обновления -->
|
||||
<script defer src="/src/utils/updateManager.js?v=1790225533889"></script>
|
||||
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script>
|
||||
<script defer src="/src/utils/updateManager.js?v=1790540575787"></script>
|
||||
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790540575787"></script>
|
||||
<!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the
|
||||
app has mounted and the browser is idle, so the 142 KB it weighs is off the
|
||||
first load. src/components/QRScanner.js used to be here too and registered
|
||||
@@ -487,12 +487,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="/dist/app-boot.js?v=1790225533889"></script>
|
||||
<script type="module" src="/dist/app.js?v=1790225533889"></script>
|
||||
<script type="module" src="/dist/app-boot.js?v=1790540575787"></script>
|
||||
<script type="module" src="/dist/app.js?v=1790540575787"></script>
|
||||
|
||||
<script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script>
|
||||
<script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script>
|
||||
<script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script>
|
||||
<script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/pwa-register.js?v=1790540575787"></script>
|
||||
<script src="/src/pwa/install-prompt.js?v=1790540575787" type="module"></script>
|
||||
<script src="/src/pwa/pwa-manager.js?v=1790540575787" type="module"></script>
|
||||
<script defer src="/src/scripts/pwa-offline-test.js?v=1790540575787"></script>
|
||||
</body>
|
||||
</html>
|
||||
+19
-19
@@ -30,18 +30,18 @@
|
||||
|
||||
<!-- PWA Manifest -->
|
||||
<link rel="manifest" href="/fa/manifest.json">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
|
||||
|
||||
<!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html>
|
||||
from localStorage while the parser is still in <head>, so the page paints in the
|
||||
chosen theme on the first frame instead of flashing dark and correcting itself.
|
||||
Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). -->
|
||||
<script src="/src/scripts/theme-boot.js?v=1790225533889"></script>
|
||||
<script src="/src/scripts/theme-boot.js?v=1790540575787"></script>
|
||||
|
||||
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
|
||||
not replayed, so the listener has to exist before Chrome decides the page
|
||||
is installable. Deferred/module scripts are already too late. -->
|
||||
<script src="/src/scripts/pwa-install-capture.js?v=1790225533889"></script>
|
||||
<script src="/src/scripts/pwa-install-capture.js?v=1790540575787"></script>
|
||||
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
@@ -116,7 +116,7 @@
|
||||
<link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png">
|
||||
|
||||
<!-- Apple Touch Icons -->
|
||||
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790225533889">
|
||||
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790540575787">
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png">
|
||||
@@ -125,7 +125,7 @@
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790225533889">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790540575787">
|
||||
|
||||
<!-- Microsoft Tiles -->
|
||||
<meta name="msapplication-TileColor" content="#ff6b35">
|
||||
@@ -264,7 +264,7 @@
|
||||
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
|
||||
both execute in document order after parsing, so React still runs before the
|
||||
app modules below, but the parser / first paint is no longer blocked. -->
|
||||
<script defer src="/config/ice-servers.js?v=1790225533889"></script>
|
||||
<script defer src="/config/ice-servers.js?v=1790540575787"></script>
|
||||
<script defer src="/libs/react/react.production.min.js"></script>
|
||||
<script defer src="/libs/react-dom/react-dom.production.min.js"></script>
|
||||
<!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only —
|
||||
@@ -278,8 +278,8 @@
|
||||
the end of <body>: 21 KB of CSS in total, but eight round trips before the
|
||||
browser could paint, which GTmetrix measured at 441 ms on a throttled mobile
|
||||
connection. Bytes were never the problem; requests were. -->
|
||||
<link rel="stylesheet" href="/assets/app.css?v=1790225533889">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
|
||||
<link rel="stylesheet" href="/assets/app.css?v=1790540575787">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
|
||||
<!-- Preload only the fonts needed for first paint. Inter is one variable file that
|
||||
answers for every weight, so there is one to preload rather than the two of five
|
||||
copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and
|
||||
@@ -287,19 +287,19 @@
|
||||
<link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
|
||||
<script defer src="/src/scripts/load-async-css.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/load-async-css.js?v=1790540575787"></script>
|
||||
<noscript>
|
||||
<link rel="stylesheet" href="/libs/prism/prism.css">
|
||||
</noscript>
|
||||
<script defer src="/src/scripts/fa-check.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/fa-check.js?v=1790540575787"></script>
|
||||
<!-- This locale's dictionary, registered before anything can ask for a string.
|
||||
Only the default locale's is bundled (src/i18n/index.js imports it, because t()
|
||||
falls back to it); every other page loads its own here instead of all thirteen
|
||||
riding along inside dist/app.js. Absent on the default locale's own page. -->
|
||||
<script type="module" src="/src/i18n/dict/fa.js?v=1790225533889"></script>
|
||||
<script type="module" src="/src/i18n/dict/fa.js?v=1790540575787"></script>
|
||||
<!-- Update Manager - система принудительного обновления -->
|
||||
<script defer src="/src/utils/updateManager.js?v=1790225533889"></script>
|
||||
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script>
|
||||
<script defer src="/src/utils/updateManager.js?v=1790540575787"></script>
|
||||
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790540575787"></script>
|
||||
<!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the
|
||||
app has mounted and the browser is idle, so the 142 KB it weighs is off the
|
||||
first load. src/components/QRScanner.js used to be here too and registered
|
||||
@@ -487,12 +487,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="/dist/app-boot.js?v=1790225533889"></script>
|
||||
<script type="module" src="/dist/app.js?v=1790225533889"></script>
|
||||
<script type="module" src="/dist/app-boot.js?v=1790540575787"></script>
|
||||
<script type="module" src="/dist/app.js?v=1790540575787"></script>
|
||||
|
||||
<script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script>
|
||||
<script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script>
|
||||
<script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script>
|
||||
<script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/pwa-register.js?v=1790540575787"></script>
|
||||
<script src="/src/pwa/install-prompt.js?v=1790540575787" type="module"></script>
|
||||
<script src="/src/pwa/pwa-manager.js?v=1790540575787" type="module"></script>
|
||||
<script defer src="/src/scripts/pwa-offline-test.js?v=1790540575787"></script>
|
||||
</body>
|
||||
</html>
|
||||
+19
-19
@@ -30,18 +30,18 @@
|
||||
|
||||
<!-- PWA Manifest -->
|
||||
<link rel="manifest" href="/fr/manifest.json">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
|
||||
|
||||
<!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html>
|
||||
from localStorage while the parser is still in <head>, so the page paints in the
|
||||
chosen theme on the first frame instead of flashing dark and correcting itself.
|
||||
Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). -->
|
||||
<script src="/src/scripts/theme-boot.js?v=1790225533889"></script>
|
||||
<script src="/src/scripts/theme-boot.js?v=1790540575787"></script>
|
||||
|
||||
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
|
||||
not replayed, so the listener has to exist before Chrome decides the page
|
||||
is installable. Deferred/module scripts are already too late. -->
|
||||
<script src="/src/scripts/pwa-install-capture.js?v=1790225533889"></script>
|
||||
<script src="/src/scripts/pwa-install-capture.js?v=1790540575787"></script>
|
||||
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
@@ -116,7 +116,7 @@
|
||||
<link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png">
|
||||
|
||||
<!-- Apple Touch Icons -->
|
||||
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790225533889">
|
||||
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790540575787">
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png">
|
||||
@@ -125,7 +125,7 @@
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790225533889">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790540575787">
|
||||
|
||||
<!-- Microsoft Tiles -->
|
||||
<meta name="msapplication-TileColor" content="#ff6b35">
|
||||
@@ -264,7 +264,7 @@
|
||||
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
|
||||
both execute in document order after parsing, so React still runs before the
|
||||
app modules below, but the parser / first paint is no longer blocked. -->
|
||||
<script defer src="/config/ice-servers.js?v=1790225533889"></script>
|
||||
<script defer src="/config/ice-servers.js?v=1790540575787"></script>
|
||||
<script defer src="/libs/react/react.production.min.js"></script>
|
||||
<script defer src="/libs/react-dom/react-dom.production.min.js"></script>
|
||||
<!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only —
|
||||
@@ -278,8 +278,8 @@
|
||||
the end of <body>: 21 KB of CSS in total, but eight round trips before the
|
||||
browser could paint, which GTmetrix measured at 441 ms on a throttled mobile
|
||||
connection. Bytes were never the problem; requests were. -->
|
||||
<link rel="stylesheet" href="/assets/app.css?v=1790225533889">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
|
||||
<link rel="stylesheet" href="/assets/app.css?v=1790540575787">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
|
||||
<!-- Preload only the fonts needed for first paint. Inter is one variable file that
|
||||
answers for every weight, so there is one to preload rather than the two of five
|
||||
copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and
|
||||
@@ -287,19 +287,19 @@
|
||||
<link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
|
||||
<script defer src="/src/scripts/load-async-css.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/load-async-css.js?v=1790540575787"></script>
|
||||
<noscript>
|
||||
<link rel="stylesheet" href="/libs/prism/prism.css">
|
||||
</noscript>
|
||||
<script defer src="/src/scripts/fa-check.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/fa-check.js?v=1790540575787"></script>
|
||||
<!-- This locale's dictionary, registered before anything can ask for a string.
|
||||
Only the default locale's is bundled (src/i18n/index.js imports it, because t()
|
||||
falls back to it); every other page loads its own here instead of all thirteen
|
||||
riding along inside dist/app.js. Absent on the default locale's own page. -->
|
||||
<script type="module" src="/src/i18n/dict/fr.js?v=1790225533889"></script>
|
||||
<script type="module" src="/src/i18n/dict/fr.js?v=1790540575787"></script>
|
||||
<!-- Update Manager - система принудительного обновления -->
|
||||
<script defer src="/src/utils/updateManager.js?v=1790225533889"></script>
|
||||
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script>
|
||||
<script defer src="/src/utils/updateManager.js?v=1790540575787"></script>
|
||||
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790540575787"></script>
|
||||
<!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the
|
||||
app has mounted and the browser is idle, so the 142 KB it weighs is off the
|
||||
first load. src/components/QRScanner.js used to be here too and registered
|
||||
@@ -487,12 +487,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="/dist/app-boot.js?v=1790225533889"></script>
|
||||
<script type="module" src="/dist/app.js?v=1790225533889"></script>
|
||||
<script type="module" src="/dist/app-boot.js?v=1790540575787"></script>
|
||||
<script type="module" src="/dist/app.js?v=1790540575787"></script>
|
||||
|
||||
<script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script>
|
||||
<script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script>
|
||||
<script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script>
|
||||
<script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/pwa-register.js?v=1790540575787"></script>
|
||||
<script src="/src/pwa/install-prompt.js?v=1790540575787" type="module"></script>
|
||||
<script src="/src/pwa/pwa-manager.js?v=1790540575787" type="module"></script>
|
||||
<script defer src="/src/scripts/pwa-offline-test.js?v=1790540575787"></script>
|
||||
</body>
|
||||
</html>
|
||||
+19
-19
@@ -30,18 +30,18 @@
|
||||
|
||||
<!-- PWA Manifest -->
|
||||
<link rel="manifest" href="/he/manifest.json">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
|
||||
|
||||
<!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html>
|
||||
from localStorage while the parser is still in <head>, so the page paints in the
|
||||
chosen theme on the first frame instead of flashing dark and correcting itself.
|
||||
Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). -->
|
||||
<script src="/src/scripts/theme-boot.js?v=1790225533889"></script>
|
||||
<script src="/src/scripts/theme-boot.js?v=1790540575787"></script>
|
||||
|
||||
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
|
||||
not replayed, so the listener has to exist before Chrome decides the page
|
||||
is installable. Deferred/module scripts are already too late. -->
|
||||
<script src="/src/scripts/pwa-install-capture.js?v=1790225533889"></script>
|
||||
<script src="/src/scripts/pwa-install-capture.js?v=1790540575787"></script>
|
||||
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
@@ -116,7 +116,7 @@
|
||||
<link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png">
|
||||
|
||||
<!-- Apple Touch Icons -->
|
||||
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790225533889">
|
||||
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790540575787">
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png">
|
||||
@@ -125,7 +125,7 @@
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790225533889">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790540575787">
|
||||
|
||||
<!-- Microsoft Tiles -->
|
||||
<meta name="msapplication-TileColor" content="#ff6b35">
|
||||
@@ -264,7 +264,7 @@
|
||||
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
|
||||
both execute in document order after parsing, so React still runs before the
|
||||
app modules below, but the parser / first paint is no longer blocked. -->
|
||||
<script defer src="/config/ice-servers.js?v=1790225533889"></script>
|
||||
<script defer src="/config/ice-servers.js?v=1790540575787"></script>
|
||||
<script defer src="/libs/react/react.production.min.js"></script>
|
||||
<script defer src="/libs/react-dom/react-dom.production.min.js"></script>
|
||||
<!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only —
|
||||
@@ -278,8 +278,8 @@
|
||||
the end of <body>: 21 KB of CSS in total, but eight round trips before the
|
||||
browser could paint, which GTmetrix measured at 441 ms on a throttled mobile
|
||||
connection. Bytes were never the problem; requests were. -->
|
||||
<link rel="stylesheet" href="/assets/app.css?v=1790225533889">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
|
||||
<link rel="stylesheet" href="/assets/app.css?v=1790540575787">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
|
||||
<!-- Preload only the fonts needed for first paint. Inter is one variable file that
|
||||
answers for every weight, so there is one to preload rather than the two of five
|
||||
copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and
|
||||
@@ -287,19 +287,19 @@
|
||||
<link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
|
||||
<script defer src="/src/scripts/load-async-css.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/load-async-css.js?v=1790540575787"></script>
|
||||
<noscript>
|
||||
<link rel="stylesheet" href="/libs/prism/prism.css">
|
||||
</noscript>
|
||||
<script defer src="/src/scripts/fa-check.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/fa-check.js?v=1790540575787"></script>
|
||||
<!-- This locale's dictionary, registered before anything can ask for a string.
|
||||
Only the default locale's is bundled (src/i18n/index.js imports it, because t()
|
||||
falls back to it); every other page loads its own here instead of all thirteen
|
||||
riding along inside dist/app.js. Absent on the default locale's own page. -->
|
||||
<script type="module" src="/src/i18n/dict/he.js?v=1790225533889"></script>
|
||||
<script type="module" src="/src/i18n/dict/he.js?v=1790540575787"></script>
|
||||
<!-- Update Manager - система принудительного обновления -->
|
||||
<script defer src="/src/utils/updateManager.js?v=1790225533889"></script>
|
||||
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script>
|
||||
<script defer src="/src/utils/updateManager.js?v=1790540575787"></script>
|
||||
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790540575787"></script>
|
||||
<!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the
|
||||
app has mounted and the browser is idle, so the 142 KB it weighs is off the
|
||||
first load. src/components/QRScanner.js used to be here too and registered
|
||||
@@ -487,12 +487,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="/dist/app-boot.js?v=1790225533889"></script>
|
||||
<script type="module" src="/dist/app.js?v=1790225533889"></script>
|
||||
<script type="module" src="/dist/app-boot.js?v=1790540575787"></script>
|
||||
<script type="module" src="/dist/app.js?v=1790540575787"></script>
|
||||
|
||||
<script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script>
|
||||
<script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script>
|
||||
<script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script>
|
||||
<script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/pwa-register.js?v=1790540575787"></script>
|
||||
<script src="/src/pwa/install-prompt.js?v=1790540575787" type="module"></script>
|
||||
<script src="/src/pwa/pwa-manager.js?v=1790540575787" type="module"></script>
|
||||
<script defer src="/src/scripts/pwa-offline-test.js?v=1790540575787"></script>
|
||||
</body>
|
||||
</html>
|
||||
+19
-19
@@ -30,18 +30,18 @@
|
||||
|
||||
<!-- PWA Manifest -->
|
||||
<link rel="manifest" href="/hi/manifest.json">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
|
||||
|
||||
<!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html>
|
||||
from localStorage while the parser is still in <head>, so the page paints in the
|
||||
chosen theme on the first frame instead of flashing dark and correcting itself.
|
||||
Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). -->
|
||||
<script src="/src/scripts/theme-boot.js?v=1790225533889"></script>
|
||||
<script src="/src/scripts/theme-boot.js?v=1790540575787"></script>
|
||||
|
||||
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
|
||||
not replayed, so the listener has to exist before Chrome decides the page
|
||||
is installable. Deferred/module scripts are already too late. -->
|
||||
<script src="/src/scripts/pwa-install-capture.js?v=1790225533889"></script>
|
||||
<script src="/src/scripts/pwa-install-capture.js?v=1790540575787"></script>
|
||||
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
@@ -116,7 +116,7 @@
|
||||
<link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png">
|
||||
|
||||
<!-- Apple Touch Icons -->
|
||||
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790225533889">
|
||||
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790540575787">
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png">
|
||||
@@ -125,7 +125,7 @@
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790225533889">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790540575787">
|
||||
|
||||
<!-- Microsoft Tiles -->
|
||||
<meta name="msapplication-TileColor" content="#ff6b35">
|
||||
@@ -264,7 +264,7 @@
|
||||
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
|
||||
both execute in document order after parsing, so React still runs before the
|
||||
app modules below, but the parser / first paint is no longer blocked. -->
|
||||
<script defer src="/config/ice-servers.js?v=1790225533889"></script>
|
||||
<script defer src="/config/ice-servers.js?v=1790540575787"></script>
|
||||
<script defer src="/libs/react/react.production.min.js"></script>
|
||||
<script defer src="/libs/react-dom/react-dom.production.min.js"></script>
|
||||
<!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only —
|
||||
@@ -278,8 +278,8 @@
|
||||
the end of <body>: 21 KB of CSS in total, but eight round trips before the
|
||||
browser could paint, which GTmetrix measured at 441 ms on a throttled mobile
|
||||
connection. Bytes were never the problem; requests were. -->
|
||||
<link rel="stylesheet" href="/assets/app.css?v=1790225533889">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
|
||||
<link rel="stylesheet" href="/assets/app.css?v=1790540575787">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
|
||||
<!-- Preload only the fonts needed for first paint. Inter is one variable file that
|
||||
answers for every weight, so there is one to preload rather than the two of five
|
||||
copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and
|
||||
@@ -287,19 +287,19 @@
|
||||
<link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
|
||||
<script defer src="/src/scripts/load-async-css.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/load-async-css.js?v=1790540575787"></script>
|
||||
<noscript>
|
||||
<link rel="stylesheet" href="/libs/prism/prism.css">
|
||||
</noscript>
|
||||
<script defer src="/src/scripts/fa-check.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/fa-check.js?v=1790540575787"></script>
|
||||
<!-- This locale's dictionary, registered before anything can ask for a string.
|
||||
Only the default locale's is bundled (src/i18n/index.js imports it, because t()
|
||||
falls back to it); every other page loads its own here instead of all thirteen
|
||||
riding along inside dist/app.js. Absent on the default locale's own page. -->
|
||||
<script type="module" src="/src/i18n/dict/hi.js?v=1790225533889"></script>
|
||||
<script type="module" src="/src/i18n/dict/hi.js?v=1790540575787"></script>
|
||||
<!-- Update Manager - система принудительного обновления -->
|
||||
<script defer src="/src/utils/updateManager.js?v=1790225533889"></script>
|
||||
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script>
|
||||
<script defer src="/src/utils/updateManager.js?v=1790540575787"></script>
|
||||
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790540575787"></script>
|
||||
<!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the
|
||||
app has mounted and the browser is idle, so the 142 KB it weighs is off the
|
||||
first load. src/components/QRScanner.js used to be here too and registered
|
||||
@@ -487,12 +487,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="/dist/app-boot.js?v=1790225533889"></script>
|
||||
<script type="module" src="/dist/app.js?v=1790225533889"></script>
|
||||
<script type="module" src="/dist/app-boot.js?v=1790540575787"></script>
|
||||
<script type="module" src="/dist/app.js?v=1790540575787"></script>
|
||||
|
||||
<script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script>
|
||||
<script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script>
|
||||
<script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script>
|
||||
<script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/pwa-register.js?v=1790540575787"></script>
|
||||
<script src="/src/pwa/install-prompt.js?v=1790540575787" type="module"></script>
|
||||
<script src="/src/pwa/pwa-manager.js?v=1790540575787" type="module"></script>
|
||||
<script defer src="/src/scripts/pwa-offline-test.js?v=1790540575787"></script>
|
||||
</body>
|
||||
</html>
|
||||
+18
-18
@@ -30,18 +30,18 @@
|
||||
|
||||
<!-- PWA Manifest -->
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
|
||||
|
||||
<!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html>
|
||||
from localStorage while the parser is still in <head>, so the page paints in the
|
||||
chosen theme on the first frame instead of flashing dark and correcting itself.
|
||||
Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). -->
|
||||
<script src="/src/scripts/theme-boot.js?v=1790225533889"></script>
|
||||
<script src="/src/scripts/theme-boot.js?v=1790540575787"></script>
|
||||
|
||||
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
|
||||
not replayed, so the listener has to exist before Chrome decides the page
|
||||
is installable. Deferred/module scripts are already too late. -->
|
||||
<script src="/src/scripts/pwa-install-capture.js?v=1790225533889"></script>
|
||||
<script src="/src/scripts/pwa-install-capture.js?v=1790540575787"></script>
|
||||
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
@@ -116,7 +116,7 @@
|
||||
<link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png">
|
||||
|
||||
<!-- Apple Touch Icons -->
|
||||
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790225533889">
|
||||
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790540575787">
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png">
|
||||
@@ -125,7 +125,7 @@
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790225533889">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790540575787">
|
||||
|
||||
<!-- Microsoft Tiles -->
|
||||
<meta name="msapplication-TileColor" content="#ff6b35">
|
||||
@@ -264,7 +264,7 @@
|
||||
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
|
||||
both execute in document order after parsing, so React still runs before the
|
||||
app modules below, but the parser / first paint is no longer blocked. -->
|
||||
<script defer src="/config/ice-servers.js?v=1790225533889"></script>
|
||||
<script defer src="/config/ice-servers.js?v=1790540575787"></script>
|
||||
<script defer src="/libs/react/react.production.min.js"></script>
|
||||
<script defer src="/libs/react-dom/react-dom.production.min.js"></script>
|
||||
<!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only —
|
||||
@@ -278,8 +278,8 @@
|
||||
the end of <body>: 21 KB of CSS in total, but eight round trips before the
|
||||
browser could paint, which GTmetrix measured at 441 ms on a throttled mobile
|
||||
connection. Bytes were never the problem; requests were. -->
|
||||
<link rel="stylesheet" href="/assets/app.css?v=1790225533889">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
|
||||
<link rel="stylesheet" href="/assets/app.css?v=1790540575787">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
|
||||
<!-- Preload only the fonts needed for first paint. Inter is one variable file that
|
||||
answers for every weight, so there is one to preload rather than the two of five
|
||||
copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and
|
||||
@@ -287,18 +287,18 @@
|
||||
<link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
|
||||
<script defer src="/src/scripts/load-async-css.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/load-async-css.js?v=1790540575787"></script>
|
||||
<noscript>
|
||||
<link rel="stylesheet" href="/libs/prism/prism.css">
|
||||
</noscript>
|
||||
<script defer src="/src/scripts/fa-check.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/fa-check.js?v=1790540575787"></script>
|
||||
<!-- This locale's dictionary, registered before anything can ask for a string.
|
||||
Only the default locale's is bundled (src/i18n/index.js imports it, because t()
|
||||
falls back to it); every other page loads its own here instead of all thirteen
|
||||
riding along inside dist/app.js. Absent on the default locale's own page. -->
|
||||
<!-- Update Manager - система принудительного обновления -->
|
||||
<script defer src="/src/utils/updateManager.js?v=1790225533889"></script>
|
||||
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script>
|
||||
<script defer src="/src/utils/updateManager.js?v=1790540575787"></script>
|
||||
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790540575787"></script>
|
||||
<!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the
|
||||
app has mounted and the browser is idle, so the 142 KB it weighs is off the
|
||||
first load. src/components/QRScanner.js used to be here too and registered
|
||||
@@ -486,12 +486,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="/dist/app-boot.js?v=1790225533889"></script>
|
||||
<script type="module" src="/dist/app.js?v=1790225533889"></script>
|
||||
<script type="module" src="/dist/app-boot.js?v=1790540575787"></script>
|
||||
<script type="module" src="/dist/app.js?v=1790540575787"></script>
|
||||
|
||||
<script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script>
|
||||
<script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script>
|
||||
<script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script>
|
||||
<script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/pwa-register.js?v=1790540575787"></script>
|
||||
<script src="/src/pwa/install-prompt.js?v=1790540575787" type="module"></script>
|
||||
<script src="/src/pwa/pwa-manager.js?v=1790540575787" type="module"></script>
|
||||
<script defer src="/src/scripts/pwa-offline-test.js?v=1790540575787"></script>
|
||||
</body>
|
||||
</html>
|
||||
+19
-19
@@ -30,18 +30,18 @@
|
||||
|
||||
<!-- PWA Manifest -->
|
||||
<link rel="manifest" href="/ko/manifest.json">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
|
||||
|
||||
<!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html>
|
||||
from localStorage while the parser is still in <head>, so the page paints in the
|
||||
chosen theme on the first frame instead of flashing dark and correcting itself.
|
||||
Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). -->
|
||||
<script src="/src/scripts/theme-boot.js?v=1790225533889"></script>
|
||||
<script src="/src/scripts/theme-boot.js?v=1790540575787"></script>
|
||||
|
||||
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
|
||||
not replayed, so the listener has to exist before Chrome decides the page
|
||||
is installable. Deferred/module scripts are already too late. -->
|
||||
<script src="/src/scripts/pwa-install-capture.js?v=1790225533889"></script>
|
||||
<script src="/src/scripts/pwa-install-capture.js?v=1790540575787"></script>
|
||||
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
@@ -116,7 +116,7 @@
|
||||
<link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png">
|
||||
|
||||
<!-- Apple Touch Icons -->
|
||||
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790225533889">
|
||||
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790540575787">
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png">
|
||||
@@ -125,7 +125,7 @@
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790225533889">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790540575787">
|
||||
|
||||
<!-- Microsoft Tiles -->
|
||||
<meta name="msapplication-TileColor" content="#ff6b35">
|
||||
@@ -264,7 +264,7 @@
|
||||
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
|
||||
both execute in document order after parsing, so React still runs before the
|
||||
app modules below, but the parser / first paint is no longer blocked. -->
|
||||
<script defer src="/config/ice-servers.js?v=1790225533889"></script>
|
||||
<script defer src="/config/ice-servers.js?v=1790540575787"></script>
|
||||
<script defer src="/libs/react/react.production.min.js"></script>
|
||||
<script defer src="/libs/react-dom/react-dom.production.min.js"></script>
|
||||
<!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only —
|
||||
@@ -278,8 +278,8 @@
|
||||
the end of <body>: 21 KB of CSS in total, but eight round trips before the
|
||||
browser could paint, which GTmetrix measured at 441 ms on a throttled mobile
|
||||
connection. Bytes were never the problem; requests were. -->
|
||||
<link rel="stylesheet" href="/assets/app.css?v=1790225533889">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
|
||||
<link rel="stylesheet" href="/assets/app.css?v=1790540575787">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
|
||||
<!-- Preload only the fonts needed for first paint. Inter is one variable file that
|
||||
answers for every weight, so there is one to preload rather than the two of five
|
||||
copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and
|
||||
@@ -287,19 +287,19 @@
|
||||
<link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
|
||||
<script defer src="/src/scripts/load-async-css.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/load-async-css.js?v=1790540575787"></script>
|
||||
<noscript>
|
||||
<link rel="stylesheet" href="/libs/prism/prism.css">
|
||||
</noscript>
|
||||
<script defer src="/src/scripts/fa-check.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/fa-check.js?v=1790540575787"></script>
|
||||
<!-- This locale's dictionary, registered before anything can ask for a string.
|
||||
Only the default locale's is bundled (src/i18n/index.js imports it, because t()
|
||||
falls back to it); every other page loads its own here instead of all thirteen
|
||||
riding along inside dist/app.js. Absent on the default locale's own page. -->
|
||||
<script type="module" src="/src/i18n/dict/ko.js?v=1790225533889"></script>
|
||||
<script type="module" src="/src/i18n/dict/ko.js?v=1790540575787"></script>
|
||||
<!-- Update Manager - система принудительного обновления -->
|
||||
<script defer src="/src/utils/updateManager.js?v=1790225533889"></script>
|
||||
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script>
|
||||
<script defer src="/src/utils/updateManager.js?v=1790540575787"></script>
|
||||
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790540575787"></script>
|
||||
<!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the
|
||||
app has mounted and the browser is idle, so the 142 KB it weighs is off the
|
||||
first load. src/components/QRScanner.js used to be here too and registered
|
||||
@@ -487,12 +487,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="/dist/app-boot.js?v=1790225533889"></script>
|
||||
<script type="module" src="/dist/app.js?v=1790225533889"></script>
|
||||
<script type="module" src="/dist/app-boot.js?v=1790540575787"></script>
|
||||
<script type="module" src="/dist/app.js?v=1790540575787"></script>
|
||||
|
||||
<script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script>
|
||||
<script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script>
|
||||
<script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script>
|
||||
<script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/pwa-register.js?v=1790540575787"></script>
|
||||
<script src="/src/pwa/install-prompt.js?v=1790540575787" type="module"></script>
|
||||
<script src="/src/pwa/pwa-manager.js?v=1790540575787" type="module"></script>
|
||||
<script defer src="/src/scripts/pwa-offline-test.js?v=1790540575787"></script>
|
||||
</body>
|
||||
</html>
|
||||
+16
-12
@@ -125,9 +125,10 @@
|
||||
"intro.createDesc": "يولّد جهازك المفاتيح ودعوة تُستعمل مرّة واحدة. لا شيء يمرّ عبر خادم.",
|
||||
"intro.createCta": "توليد المفاتيح والدعوة",
|
||||
"intro.joinTitle": "انضمّ إلى قناة",
|
||||
"intro.joinDesc": "امسح رمز QR الخاص بالطرف الآخر بالكاميرا، أو الصق رمز الدعوة.",
|
||||
"intro.scanTitle": "امسح رمز QR بالكاميرا",
|
||||
"intro.scanSubtitle": "الأسرع - وجّهها إلى شاشة الطرف الآخر",
|
||||
"intro.orPasteCode": "أو ألصق الرمز",
|
||||
"intro.orPasteCode": "أو الصق الرمز",
|
||||
"intro.pastePlaceholder": "ألصق رمز الدعوة هنا…",
|
||||
"intro.connect": "اتّصل",
|
||||
"intro.connecting": "جارٍ المعالجة…",
|
||||
@@ -303,22 +304,26 @@
|
||||
"handshake.establish": "إنشاء الاتصال",
|
||||
"handshake.thenReceive": "ثمّ استقبل الردّ الذي يرسله الطرف الآخر",
|
||||
"handshake.pasteAnswerPlaceholder": "ألصق رمز ردّ الطرف الآخر…",
|
||||
"handshake.answerSentNote": "أرسل هذا الردّ إلى المُنشئ ثمّ انتظر - تُفتح الدردشة حالما يتّصل.",
|
||||
"handshake.answerSentNote": "بانتظار اتصال المُنشئ. ستُفتح المحادثة تلقائياً.",
|
||||
"handshake.qrHint": "أبقِ هذا مفتوحاً حتى يلتقط الطرف الآخر الرمز.",
|
||||
"handshake.qrHintFrames": "المصافحة موزّعة على {frames} إطارات - أبقِ هذا مفتوحاً حتى يلتقطها الطرف الآخر كلّها.",
|
||||
"verify.title": "التحقّق الأمني",
|
||||
"verify.desc": "قارِن رمز الأمان هذا مع الطرف الآخر عبر قناة منفصلة (مكالمة أو لقاء مباشر)، ثمّ اكتبه لفتح الدردشة.",
|
||||
"verify.enterLabel": "أدخل الرمز المُتحقَّق منه",
|
||||
"verify.placeholder": "اكتب الرمز هنا",
|
||||
"verify.confirm": "تأكيد الرمز",
|
||||
"verify.confirmed": "مؤكَّد",
|
||||
"verify.mismatch": "غير متطابقين",
|
||||
"verify.yours": "تأكيدك",
|
||||
"verify.peer": "تأكيد الطرف الآخر",
|
||||
"verify.pending": "قيد الانتظار",
|
||||
"verify.waiting": "بانتظار الرمز…",
|
||||
"verify.verified": "تمّ التحقّق من القناة",
|
||||
"verify.bothConfirmed": "أكّد الطرفان. جارٍ فتح الدردشة الآمنة…",
|
||||
"sasv.title": "تحقّق من الطرف الآخر",
|
||||
"sasv.titleDone": "تم التحقّق من الطرف الآخر",
|
||||
"sasv.desc": "اقرأ الرمز بصوت عالٍ عبر مكالمة أو وجهًا لوجه. لا تتابع إلا إذا رأى الطرف الآخر الأرقام نفسها.",
|
||||
"sasv.descDone": "أكّدتما كلاكما الرمز نفسه. الاتصال موثَّق.",
|
||||
"sasv.enter": "أدخل الرمز",
|
||||
"sasv.match": "متطابق",
|
||||
"sasv.noMatch": "غير متطابق",
|
||||
"sasv.you": "أنت",
|
||||
"sasv.peer": "الطرف الآخر",
|
||||
"sasv.waitingPeer": "بانتظار الطرف الآخر…",
|
||||
"sasv.codesDiffer": "الرموز غير متطابقة",
|
||||
"sasv.opening": "جارٍ فتح المحادثة الآمنة",
|
||||
"pwa.bannerTitle": "ثبّت SecureBit.chat",
|
||||
"pwa.bannerDesc": "احصل على تجربة تطبيق أصلي بأمان أعلى",
|
||||
"pwa.install": "تثبيت",
|
||||
@@ -353,9 +358,8 @@
|
||||
"action.scan": "مسح",
|
||||
"action.downloadDesktop": "نزّل تطبيق سطح المكتب",
|
||||
"action.advancedSettings": "إعدادات متقدّمة",
|
||||
"cred.offerTag": "عرض · أو انسخ النصّ",
|
||||
"cred.answerTag": "ردّ · أو انسخ النصّ",
|
||||
"cred.reveal": "اضغط للكشف - أبقِ هذا الرمز سرّياً",
|
||||
"cred.orCopy": "أو انسخ الرمز",
|
||||
"qr.title": "امسح رمز QR",
|
||||
"qr.subtitle": "وجّه كاميرتك إلى رمزهم",
|
||||
"qr.scanning": "جارٍ المسح…",
|
||||
|
||||
+16
-12
@@ -125,9 +125,10 @@
|
||||
"intro.createDesc": "Ihr Gerät erzeugt die Schlüssel und eine einmalige Einladung. Nichts davon berührt einen Server.",
|
||||
"intro.createCta": "Schlüssel & Einladung erzeugen",
|
||||
"intro.joinTitle": "Kanal beitreten",
|
||||
"intro.joinDesc": "Scannen Sie den QR-Code Ihres Gegenübers mit der Kamera oder fügen Sie seinen Einladungscode ein.",
|
||||
"intro.scanTitle": "QR-Code mit der Kamera scannen",
|
||||
"intro.scanSubtitle": "Am schnellsten - auf den Bildschirm Ihres Gegenübers halten",
|
||||
"intro.orPasteCode": "oder Code einfügen",
|
||||
"intro.orPasteCode": "Oder den Code einfügen",
|
||||
"intro.pastePlaceholder": "Einladungscode hier einfügen …",
|
||||
"intro.connect": "Verbinden",
|
||||
"intro.connecting": "Wird verarbeitet …",
|
||||
@@ -303,22 +304,26 @@
|
||||
"handshake.establish": "Verbindung herstellen",
|
||||
"handshake.thenReceive": "Nehmen Sie danach die Antwort Ihres Gegenübers entgegen",
|
||||
"handshake.pasteAnswerPlaceholder": "Antwortcode des Gegenübers einfügen …",
|
||||
"handshake.answerSentNote": "Senden Sie diese Antwort an den Ersteller und warten Sie - der Chat öffnet sich, sobald er sich verbindet.",
|
||||
"handshake.answerSentNote": "Warten, bis sich der Ersteller verbindet. Der Chat öffnet sich automatisch.",
|
||||
"handshake.qrHint": "Lassen Sie dies geöffnet, bis Ihr Gegenüber den Code erfasst hat.",
|
||||
"handshake.qrHintFrames": "Der Handschlag ist auf {frames} Einzelbilder verteilt - lassen Sie dies geöffnet, bis Ihr Gegenüber alle erfasst hat.",
|
||||
"verify.title": "Sicherheitsprüfung",
|
||||
"verify.desc": "Vergleichen Sie diesen Sicherheitscode über einen anderen Weg mit Ihrem Gegenüber (Anruf oder persönlich) und geben Sie ihn dann ein, um den Chat freizuschalten.",
|
||||
"verify.enterLabel": "Geprüften Code eingeben",
|
||||
"verify.placeholder": "Code hier eingeben",
|
||||
"verify.confirm": "Code bestätigen",
|
||||
"verify.confirmed": "Bestätigt",
|
||||
"verify.mismatch": "Stimmt nicht überein",
|
||||
"verify.yours": "Ihre Bestätigung",
|
||||
"verify.peer": "Bestätigung des Gegenübers",
|
||||
"verify.pending": "Ausstehend",
|
||||
"verify.waiting": "Warten auf den Code …",
|
||||
"verify.verified": "Kanal geprüft",
|
||||
"verify.bothConfirmed": "Beide Seiten haben bestätigt. Der sichere Chat wird geöffnet …",
|
||||
"sasv.title": "Gegenüber überprüfen",
|
||||
"sasv.titleDone": "Gegenüber verifiziert",
|
||||
"sasv.desc": "Lies den Code bei einem Anruf oder persönlich laut vor. Fahre nur fort, wenn dein Gegenüber dieselben Ziffern sieht.",
|
||||
"sasv.descDone": "Ihr habt beide denselben Code bestätigt. Die Verbindung ist authentifiziert.",
|
||||
"sasv.enter": "Code eingeben",
|
||||
"sasv.match": "Stimmt überein",
|
||||
"sasv.noMatch": "Stimmt nicht überein",
|
||||
"sasv.you": "Du",
|
||||
"sasv.peer": "Gegenüber",
|
||||
"sasv.waitingPeer": "Warte auf Gegenüber…",
|
||||
"sasv.codesDiffer": "Codes stimmen nicht überein",
|
||||
"sasv.opening": "Sicherer Chat wird geöffnet",
|
||||
"pwa.bannerTitle": "SecureBit.chat installieren",
|
||||
"pwa.bannerDesc": "Die App-Erfahrung mit zusätzlicher Sicherheit",
|
||||
"pwa.install": "Installieren",
|
||||
@@ -353,9 +358,8 @@
|
||||
"action.scan": "Scannen",
|
||||
"action.downloadDesktop": "Desktop-App laden",
|
||||
"action.advancedSettings": "Erweiterte Einstellungen",
|
||||
"cred.offerTag": "Angebot · oder Text kopieren",
|
||||
"cred.answerTag": "Antwort · oder Text kopieren",
|
||||
"cred.reveal": "Zum Anzeigen klicken - halten Sie diesen Code geheim",
|
||||
"cred.orCopy": "Oder den Code kopieren",
|
||||
"qr.title": "QR-Code scannen",
|
||||
"qr.subtitle": "Richten Sie die Kamera auf den QR-Code",
|
||||
"qr.scanning": "Wird gescannt …",
|
||||
|
||||
+16
-12
@@ -120,9 +120,10 @@
|
||||
"intro.createDesc": "Your device generates the keys and a one-time invitation. Nothing touches a server.",
|
||||
"intro.createCta": "Generate keys & invitation",
|
||||
"intro.joinTitle": "Join a channel",
|
||||
"intro.joinDesc": "Scan your peer's QR with your camera, or paste their invitation code.",
|
||||
"intro.scanTitle": "Scan QR with camera",
|
||||
"intro.scanSubtitle": "Fastest - point at your peer's screen",
|
||||
"intro.orPasteCode": "or paste code",
|
||||
"intro.orPasteCode": "Or paste the code",
|
||||
"intro.pastePlaceholder": "Paste invitation code here…",
|
||||
"intro.connect": "Connect",
|
||||
"intro.connecting": "Processing…",
|
||||
@@ -298,22 +299,26 @@
|
||||
"handshake.establish": "Establish connection",
|
||||
"handshake.thenReceive": "Then receive the answer your peer sends back",
|
||||
"handshake.pasteAnswerPlaceholder": "Paste peer's answer code…",
|
||||
"handshake.answerSentNote": "Send this answer to the creator, then wait - the chat opens once they connect.",
|
||||
"handshake.answerSentNote": "Waiting for the creator to connect. The chat opens automatically.",
|
||||
"handshake.qrHint": "Keep this open until your peer captures the code.",
|
||||
"handshake.qrHintFrames": "The handshake is split across {frames} frames - keep this open until your peer captures all of them.",
|
||||
"verify.title": "Security verification",
|
||||
"verify.desc": "Compare this safety code with your peer over a separate channel (voice / in person), then type it to unlock the chat.",
|
||||
"verify.enterLabel": "Enter the verified code",
|
||||
"verify.placeholder": "Type code here",
|
||||
"verify.confirm": "Confirm code",
|
||||
"verify.confirmed": "Confirmed",
|
||||
"verify.mismatch": "Don't match",
|
||||
"verify.yours": "Your confirmation",
|
||||
"verify.peer": "Peer confirmation",
|
||||
"verify.pending": "Pending",
|
||||
"verify.waiting": "Waiting for code…",
|
||||
"verify.verified": "Channel verified",
|
||||
"verify.bothConfirmed": "Both parties confirmed. Opening the secure chat…",
|
||||
"sasv.title": "Verify your peer",
|
||||
"sasv.titleDone": "Peer verified",
|
||||
"sasv.desc": "Read the code aloud over a call or in person. Continue only if your peer sees the same digits.",
|
||||
"sasv.descDone": "You both confirmed the same code. The connection is authenticated.",
|
||||
"sasv.enter": "Enter the code",
|
||||
"sasv.match": "Match",
|
||||
"sasv.noMatch": "Doesn't match",
|
||||
"sasv.you": "You",
|
||||
"sasv.peer": "Peer",
|
||||
"sasv.waitingPeer": "Waiting for peer…",
|
||||
"sasv.codesDiffer": "Codes don't match",
|
||||
"sasv.opening": "Opening secure chat",
|
||||
"pwa.bannerTitle": "Install SecureBit.chat",
|
||||
"pwa.bannerDesc": "Get the native app experience with enhanced security",
|
||||
"pwa.install": "Install",
|
||||
@@ -348,9 +353,8 @@
|
||||
"action.scan": "Scan",
|
||||
"action.downloadDesktop": "Download desktop app",
|
||||
"action.advancedSettings": "Advanced settings",
|
||||
"cred.offerTag": "offer · or copy text",
|
||||
"cred.answerTag": "answer · or copy text",
|
||||
"cred.reveal": "Click to reveal - keep this code private",
|
||||
"cred.orCopy": "Or copy the code",
|
||||
"qr.title": "Scan QR code",
|
||||
"qr.subtitle": "Point your camera at their QR",
|
||||
"qr.scanning": "Scanning…",
|
||||
|
||||
+16
-12
@@ -125,9 +125,10 @@
|
||||
"intro.createDesc": "Tu dispositivo genera las claves y una invitación de un solo uso. Nada de esto pasa por un servidor.",
|
||||
"intro.createCta": "Generar claves e invitación",
|
||||
"intro.joinTitle": "Unirse a un canal",
|
||||
"intro.joinDesc": "Escanea el QR de tu contacto con la cámara o pega su código de invitación.",
|
||||
"intro.scanTitle": "Escanear el QR con la cámara",
|
||||
"intro.scanSubtitle": "Lo más rápido: apunta a la pantalla de la otra persona",
|
||||
"intro.orPasteCode": "o pega el código",
|
||||
"intro.orPasteCode": "O pega el código",
|
||||
"intro.pastePlaceholder": "Pega aquí el código de invitación…",
|
||||
"intro.connect": "Conectar",
|
||||
"intro.connecting": "Procesando…",
|
||||
@@ -303,22 +304,26 @@
|
||||
"handshake.establish": "Establecer la conexión",
|
||||
"handshake.thenReceive": "Después recibe la respuesta que te devuelva la otra persona",
|
||||
"handshake.pasteAnswerPlaceholder": "Pega aquí el código de respuesta…",
|
||||
"handshake.answerSentNote": "Envía esta respuesta a quien creó el canal y espera: el chat se abrirá cuando conecte.",
|
||||
"handshake.answerSentNote": "Esperando a que el creador se conecte. El chat se abrirá automáticamente.",
|
||||
"handshake.qrHint": "Mantén esto abierto hasta que la otra persona capture el código.",
|
||||
"handshake.qrHintFrames": "El saludo se reparte en {frames} imágenes: mantén esto abierto hasta que las capture todas.",
|
||||
"verify.title": "Verificación de seguridad",
|
||||
"verify.desc": "Compara este código de seguridad con la otra persona por otra vía (voz o en persona) y escríbelo para desbloquear el chat.",
|
||||
"verify.enterLabel": "Introduce el código verificado",
|
||||
"verify.placeholder": "Escribe el código aquí",
|
||||
"verify.confirm": "Confirmar el código",
|
||||
"verify.confirmed": "Confirmado",
|
||||
"verify.mismatch": "No coinciden",
|
||||
"verify.yours": "Tu confirmación",
|
||||
"verify.peer": "Confirmación de la otra persona",
|
||||
"verify.pending": "Pendiente",
|
||||
"verify.waiting": "Esperando el código…",
|
||||
"verify.verified": "Canal verificado",
|
||||
"verify.bothConfirmed": "Ambas partes han confirmado. Abriendo el chat seguro…",
|
||||
"sasv.title": "Verifica a tu contacto",
|
||||
"sasv.titleDone": "Contacto verificado",
|
||||
"sasv.desc": "Lee el código en voz alta por llamada o en persona. Continúa solo si tu contacto ve los mismos dígitos.",
|
||||
"sasv.descDone": "Ambos confirmasteis el mismo código. La conexión está autenticada.",
|
||||
"sasv.enter": "Introduce el código",
|
||||
"sasv.match": "Coincide",
|
||||
"sasv.noMatch": "No coincide",
|
||||
"sasv.you": "Tú",
|
||||
"sasv.peer": "Contacto",
|
||||
"sasv.waitingPeer": "Esperando a tu contacto…",
|
||||
"sasv.codesDiffer": "Los códigos no coinciden",
|
||||
"sasv.opening": "Abriendo el chat seguro",
|
||||
"pwa.bannerTitle": "Instala SecureBit.chat",
|
||||
"pwa.bannerDesc": "Disfruta de la experiencia de app nativa con más seguridad",
|
||||
"pwa.install": "Instalar",
|
||||
@@ -353,9 +358,8 @@
|
||||
"action.scan": "Escanear",
|
||||
"action.downloadDesktop": "Descargar la app de escritorio",
|
||||
"action.advancedSettings": "Ajustes avanzados",
|
||||
"cred.offerTag": "oferta · o copia el texto",
|
||||
"cred.answerTag": "respuesta · o copia el texto",
|
||||
"cred.reveal": "Haz clic para mostrarlo: mantén este código en privado",
|
||||
"cred.orCopy": "O copia el código",
|
||||
"qr.title": "Escanear código QR",
|
||||
"qr.subtitle": "Apunta la cámara a su código QR",
|
||||
"qr.scanning": "Escaneando…",
|
||||
|
||||
+16
-12
@@ -125,9 +125,10 @@
|
||||
"intro.createDesc": "دستگاه شما کلیدها و یک دعوتنامهٔ یکبارمصرف میسازد. هیچچیز به سرور نمیرسد.",
|
||||
"intro.createCta": "ساخت کلید و دعوتنامه",
|
||||
"intro.joinTitle": "به کانالی بپیوندید",
|
||||
"intro.joinDesc": "QR طرف مقابل را با دوربین اسکن کنید یا کد دعوت او را جایگذاری کنید.",
|
||||
"intro.scanTitle": "QR را با دوربین بخوانید",
|
||||
"intro.scanSubtitle": "سریعترین راه - به صفحهٔ همتایتان بگیرید",
|
||||
"intro.orPasteCode": "یا کد را بچسبانید",
|
||||
"intro.orPasteCode": "یا کد را جایگذاری کنید",
|
||||
"intro.pastePlaceholder": "کد دعوت را اینجا بچسبانید…",
|
||||
"intro.connect": "اتصال",
|
||||
"intro.connecting": "در حال پردازش…",
|
||||
@@ -303,22 +304,26 @@
|
||||
"handshake.establish": "برقراری اتصال",
|
||||
"handshake.thenReceive": "سپس پاسخی را که همتایتان برمیگرداند دریافت کنید",
|
||||
"handshake.pasteAnswerPlaceholder": "کد پاسخ همتا را بچسبانید…",
|
||||
"handshake.answerSentNote": "این پاسخ را برای سازنده بفرستید و منتظر بمانید - گفتگو بهمحض اتصال او باز میشود.",
|
||||
"handshake.answerSentNote": "در انتظار اتصال سازنده. گفتگو بهطور خودکار باز میشود.",
|
||||
"handshake.qrHint": "این را باز نگه دارید تا همتایتان کد را بگیرد.",
|
||||
"handshake.qrHintFrames": "دستدادن میان {frames} فریم پخش شده است - این را باز نگه دارید تا همتایتان همهٔ آنها را بگیرد.",
|
||||
"verify.title": "تأیید امنیتی",
|
||||
"verify.desc": "این کد ایمنی را از راه کانالی جداگانه (تماس صوتی / حضوری) با همتایتان مقایسه کنید، سپس آن را بنویسید تا گفتگو باز شود.",
|
||||
"verify.enterLabel": "کد تأییدشده را وارد کنید",
|
||||
"verify.placeholder": "کد را اینجا بنویسید",
|
||||
"verify.confirm": "تأیید کد",
|
||||
"verify.confirmed": "تأیید شد",
|
||||
"verify.mismatch": "همخوان نیستند",
|
||||
"verify.yours": "تأیید شما",
|
||||
"verify.peer": "تأیید همتا",
|
||||
"verify.pending": "در انتظار",
|
||||
"verify.waiting": "در انتظار کد…",
|
||||
"verify.verified": "کانال تأیید شد",
|
||||
"verify.bothConfirmed": "هر دو طرف تأیید کردند. گفتگوی امن در حال باز شدن…",
|
||||
"sasv.title": "طرف مقابل را تأیید کنید",
|
||||
"sasv.titleDone": "طرف مقابل تأیید شد",
|
||||
"sasv.desc": "کد را در تماس یا حضوری با صدای بلند بخوانید. فقط وقتی ادامه دهید که طرف مقابل همان ارقام را ببیند.",
|
||||
"sasv.descDone": "هر دو یک کد را تأیید کردید. اتصال احراز هویت شد.",
|
||||
"sasv.enter": "کد را وارد کنید",
|
||||
"sasv.match": "مطابق است",
|
||||
"sasv.noMatch": "مطابق نیست",
|
||||
"sasv.you": "شما",
|
||||
"sasv.peer": "طرف مقابل",
|
||||
"sasv.waitingPeer": "در انتظار طرف مقابل…",
|
||||
"sasv.codesDiffer": "کدها مطابق نیستند",
|
||||
"sasv.opening": "در حال باز کردن گفتگوی امن",
|
||||
"pwa.bannerTitle": "SecureBit.chat را نصب کنید",
|
||||
"pwa.bannerDesc": "تجربهٔ برنامهٔ بومی با امنیت بیشتر",
|
||||
"pwa.install": "نصب",
|
||||
@@ -353,9 +358,8 @@
|
||||
"action.scan": "خواندن",
|
||||
"action.downloadDesktop": "دریافت برنامهٔ رومیزی",
|
||||
"action.advancedSettings": "تنظیمات پیشرفته",
|
||||
"cred.offerTag": "پیشنهاد · یا متن را رونوشت کنید",
|
||||
"cred.answerTag": "پاسخ · یا متن را رونوشت کنید",
|
||||
"cred.reveal": "برای نمایش کلیک کنید - این کد را خصوصی نگه دارید",
|
||||
"cred.orCopy": "یا کد را رونوشت کنید",
|
||||
"qr.title": "خواندن کد QR",
|
||||
"qr.subtitle": "دوربین را به QR آنها بگیرید",
|
||||
"qr.scanning": "در حال خواندن…",
|
||||
|
||||
+16
-12
@@ -120,9 +120,10 @@
|
||||
"intro.createDesc": "Votre appareil génère les clés et une invitation à usage unique. Rien de tout cela ne passe par un serveur.",
|
||||
"intro.createCta": "Générer les clés et l'invitation",
|
||||
"intro.joinTitle": "Rejoindre un canal",
|
||||
"intro.joinDesc": "Scannez le QR de votre correspondant avec l'appareil photo, ou collez son code d'invitation.",
|
||||
"intro.scanTitle": "Scanner le QR avec la caméra",
|
||||
"intro.scanSubtitle": "Le plus rapide : visez l'écran de votre correspondant",
|
||||
"intro.orPasteCode": "ou collez le code",
|
||||
"intro.orPasteCode": "Ou collez le code",
|
||||
"intro.pastePlaceholder": "Collez ici le code d'invitation…",
|
||||
"intro.connect": "Se connecter",
|
||||
"intro.connecting": "Traitement…",
|
||||
@@ -298,22 +299,26 @@
|
||||
"handshake.establish": "Établir la connexion",
|
||||
"handshake.thenReceive": "Recevez ensuite la réponse que votre correspondant renvoie",
|
||||
"handshake.pasteAnswerPlaceholder": "Collez le code de réponse…",
|
||||
"handshake.answerSentNote": "Envoyez cette réponse à la personne qui a créé le canal, puis patientez : la discussion s'ouvre dès qu'elle se connecte.",
|
||||
"handshake.answerSentNote": "En attente de la connexion du créateur. Le chat s'ouvrira automatiquement.",
|
||||
"handshake.qrHint": "Gardez ceci ouvert jusqu'à ce que votre correspondant ait capturé le code.",
|
||||
"handshake.qrHintFrames": "La poignée de main est répartie sur {frames} images : gardez ceci ouvert jusqu'à ce que votre correspondant les ait toutes capturées.",
|
||||
"verify.title": "Vérification de sécurité",
|
||||
"verify.desc": "Comparez ce code de sécurité avec votre correspondant par un autre canal (voix ou en personne), puis saisissez-le pour ouvrir la discussion.",
|
||||
"verify.enterLabel": "Saisissez le code vérifié",
|
||||
"verify.placeholder": "Tapez le code ici",
|
||||
"verify.confirm": "Confirmer le code",
|
||||
"verify.confirmed": "Confirmé",
|
||||
"verify.mismatch": "Ne correspond pas",
|
||||
"verify.yours": "Votre confirmation",
|
||||
"verify.peer": "Confirmation du correspondant",
|
||||
"verify.pending": "En attente",
|
||||
"verify.waiting": "En attente du code…",
|
||||
"verify.verified": "Canal vérifié",
|
||||
"verify.bothConfirmed": "Les deux parties ont confirmé. Ouverture de la discussion sécurisée…",
|
||||
"sasv.title": "Vérifiez votre correspondant",
|
||||
"sasv.titleDone": "Correspondant vérifié",
|
||||
"sasv.desc": "Lisez le code à voix haute par appel ou en personne. Ne continuez que si votre correspondant voit les mêmes chiffres.",
|
||||
"sasv.descDone": "Vous avez tous les deux confirmé le même code. La connexion est authentifiée.",
|
||||
"sasv.enter": "Saisissez le code",
|
||||
"sasv.match": "Correspond",
|
||||
"sasv.noMatch": "Ne correspond pas",
|
||||
"sasv.you": "Vous",
|
||||
"sasv.peer": "Correspondant",
|
||||
"sasv.waitingPeer": "En attente du correspondant…",
|
||||
"sasv.codesDiffer": "Les codes ne correspondent pas",
|
||||
"sasv.opening": "Ouverture du chat sécurisé",
|
||||
"pwa.bannerTitle": "Installer SecureBit.chat",
|
||||
"pwa.bannerDesc": "Profitez de l'expérience d'une application native, avec une sécurité renforcée",
|
||||
"pwa.install": "Installer",
|
||||
@@ -348,9 +353,8 @@
|
||||
"action.scan": "Scanner",
|
||||
"action.downloadDesktop": "Télécharger l'application",
|
||||
"action.advancedSettings": "Réglages avancés",
|
||||
"cred.offerTag": "offre · ou copiez le texte",
|
||||
"cred.answerTag": "réponse · ou copiez le texte",
|
||||
"cred.reveal": "Cliquez pour afficher - gardez ce code privé",
|
||||
"cred.orCopy": "Ou copiez le code",
|
||||
"qr.title": "Scanner le QR",
|
||||
"qr.subtitle": "Visez le QR de votre correspondant",
|
||||
"qr.scanning": "Lecture…",
|
||||
|
||||
+16
-12
@@ -125,9 +125,10 @@
|
||||
"intro.createDesc": "המכשיר שלכם יוצר את המפתחות והזמנה חד־פעמית. שום דבר לא נוגע בשרת.",
|
||||
"intro.createCta": "יצירת מפתחות והזמנה",
|
||||
"intro.joinTitle": "הצטרפו לערוץ",
|
||||
"intro.joinDesc": "סרקו את קוד ה-QR של הצד השני במצלמה, או הדביקו את קוד ההזמנה.",
|
||||
"intro.scanTitle": "סרקו QR עם המצלמה",
|
||||
"intro.scanSubtitle": "הכי מהיר - כוונו למסך של העמית",
|
||||
"intro.orPasteCode": "או הדביקו קוד",
|
||||
"intro.orPasteCode": "או הדביקו את הקוד",
|
||||
"intro.pastePlaceholder": "הדביקו כאן את קוד ההזמנה…",
|
||||
"intro.connect": "התחברו",
|
||||
"intro.connecting": "מעבד…",
|
||||
@@ -303,22 +304,26 @@
|
||||
"handshake.establish": "יצירת חיבור",
|
||||
"handshake.thenReceive": "ואז קבלו את התשובה שהעמית שולח בחזרה",
|
||||
"handshake.pasteAnswerPlaceholder": "הדביקו את קוד התשובה של העמית…",
|
||||
"handshake.answerSentNote": "שלחו את התשובה ליוצר ואז המתינו - הצ'אט נפתח ברגע שהם מתחברים.",
|
||||
"handshake.answerSentNote": "ממתינים שהיוצר יתחבר. הצ'אט ייפתח אוטומטית.",
|
||||
"handshake.qrHint": "השאירו פתוח עד שהעמית יקלוט את הקוד.",
|
||||
"handshake.qrHintFrames": "לחיצת היד מפוצלת ל-{frames} פריימים - השאירו פתוח עד שהעמית יקלוט את כולם.",
|
||||
"verify.title": "אימות אבטחה",
|
||||
"verify.desc": "השוו את קוד הבטיחות הזה עם העמית בערוץ נפרד (שיחה קולית / פנים אל פנים), ואז הקלידו אותו כדי לפתוח את הצ'אט.",
|
||||
"verify.enterLabel": "הזינו את הקוד המאומת",
|
||||
"verify.placeholder": "הקלידו כאן את הקוד",
|
||||
"verify.confirm": "אשרו את הקוד",
|
||||
"verify.confirmed": "אושר",
|
||||
"verify.mismatch": "לא תואמים",
|
||||
"verify.yours": "האישור שלכם",
|
||||
"verify.peer": "אישור העמית",
|
||||
"verify.pending": "ממתין",
|
||||
"verify.waiting": "ממתין לקוד…",
|
||||
"verify.verified": "הערוץ אומת",
|
||||
"verify.bothConfirmed": "שני הצדדים אישרו. פותח את הצ'אט המאובטח…",
|
||||
"sasv.title": "אמתו את הצד השני",
|
||||
"sasv.titleDone": "הצד השני אומת",
|
||||
"sasv.desc": "הקריאו את הקוד בקול בשיחה או פנים אל פנים. המשיכו רק אם הצד השני רואה את אותן ספרות.",
|
||||
"sasv.descDone": "שניכם אישרתם את אותו קוד. החיבור מאומת.",
|
||||
"sasv.enter": "הזינו את הקוד",
|
||||
"sasv.match": "תואם",
|
||||
"sasv.noMatch": "לא תואם",
|
||||
"sasv.you": "אתם",
|
||||
"sasv.peer": "הצד השני",
|
||||
"sasv.waitingPeer": "ממתינים לצד השני…",
|
||||
"sasv.codesDiffer": "הקודים לא תואמים",
|
||||
"sasv.opening": "פותחים צ'אט מאובטח",
|
||||
"pwa.bannerTitle": "התקינו את SecureBit.chat",
|
||||
"pwa.bannerDesc": "קבלו חוויית אפליקציה מקורית עם אבטחה משופרת",
|
||||
"pwa.install": "התקנה",
|
||||
@@ -353,9 +358,8 @@
|
||||
"action.scan": "סריקה",
|
||||
"action.downloadDesktop": "הורידו את אפליקציית שולחן העבודה",
|
||||
"action.advancedSettings": "הגדרות מתקדמות",
|
||||
"cred.offerTag": "הצעה · או העתיקו טקסט",
|
||||
"cred.answerTag": "תשובה · או העתיקו טקסט",
|
||||
"cred.reveal": "לחצו לחשיפה - שמרו את הקוד הזה פרטי",
|
||||
"cred.orCopy": "או העתיקו את הקוד",
|
||||
"qr.title": "סריקת קוד QR",
|
||||
"qr.subtitle": "כוונו את המצלמה ל-QR שלהם",
|
||||
"qr.scanning": "סורק…",
|
||||
|
||||
+16
-12
@@ -120,9 +120,10 @@
|
||||
"intro.createDesc": "आपका उपकरण कुंजियाँ और एक बार काम आने वाला निमंत्रण बनाता है। इनमें से कुछ भी किसी सर्वर तक नहीं जाता।",
|
||||
"intro.createCta": "कुंजियाँ और निमंत्रण बनाएँ",
|
||||
"intro.joinTitle": "चैनल में शामिल हों",
|
||||
"intro.joinDesc": "कैमरे से अपने साथी का QR स्कैन करें, या उनका आमंत्रण कोड पेस्ट करें।",
|
||||
"intro.scanTitle": "कैमरे से QR स्कैन करें",
|
||||
"intro.scanSubtitle": "सबसे तेज़ - सामने वाले की स्क्रीन पर कैमरा रखें",
|
||||
"intro.orPasteCode": "या कोड चिपकाएँ",
|
||||
"intro.orPasteCode": "या कोड पेस्ट करें",
|
||||
"intro.pastePlaceholder": "निमंत्रण कोड यहाँ चिपकाएँ…",
|
||||
"intro.connect": "जोड़ें",
|
||||
"intro.connecting": "प्रक्रिया जारी…",
|
||||
@@ -298,22 +299,26 @@
|
||||
"handshake.establish": "जुड़ाव बनाएँ",
|
||||
"handshake.thenReceive": "फिर सामने वाले का भेजा उत्तर लीजिए",
|
||||
"handshake.pasteAnswerPlaceholder": "सामने वाले का उत्तर कोड चिपकाएँ…",
|
||||
"handshake.answerSentNote": "यह उत्तर चैनल बनाने वाले को भेजिए और रुकिए - जैसे ही वे जुड़ेंगे, चैट खुल जाएगी।",
|
||||
"handshake.answerSentNote": "निर्माता के जुड़ने की प्रतीक्षा है। चैट अपने-आप खुल जाएगी।",
|
||||
"handshake.qrHint": "जब तक सामने वाला कोड पढ़ न ले, इसे खुला रखिए।",
|
||||
"handshake.qrHintFrames": "हाथ मिलाना {frames} फ़्रेमों में बँटा है - जब तक सामने वाला सब पढ़ न ले, इसे खुला रखिए।",
|
||||
"verify.title": "सुरक्षा सत्यापन",
|
||||
"verify.desc": "इस सुरक्षा कोड को किसी दूसरे रास्ते से (आवाज़ पर या आमने-सामने) मिलाइए, फिर उसे लिखकर चैट खोलिए।",
|
||||
"verify.enterLabel": "मिलाया हुआ कोड लिखें",
|
||||
"verify.placeholder": "कोड यहाँ लिखें",
|
||||
"verify.confirm": "कोड पक्का करें",
|
||||
"verify.confirmed": "पक्का हुआ",
|
||||
"verify.mismatch": "मेल नहीं खाता",
|
||||
"verify.yours": "आपकी पुष्टि",
|
||||
"verify.peer": "सामने वाले की पुष्टि",
|
||||
"verify.pending": "प्रतीक्षा में",
|
||||
"verify.waiting": "कोड की प्रतीक्षा…",
|
||||
"verify.verified": "चैनल सत्यापित",
|
||||
"verify.bothConfirmed": "दोनों पक्षों ने पुष्टि कर दी। सुरक्षित चैट खुल रही है…",
|
||||
"sasv.title": "अपने साथी की पुष्टि करें",
|
||||
"sasv.titleDone": "साथी की पुष्टि हो गई",
|
||||
"sasv.desc": "कॉल पर या आमने-सामने कोड ज़ोर से पढ़ें। तभी आगे बढ़ें जब आपके साथी को वही अंक दिखें।",
|
||||
"sasv.descDone": "आप दोनों ने एक ही कोड की पुष्टि की। कनेक्शन प्रमाणित है।",
|
||||
"sasv.enter": "कोड दर्ज करें",
|
||||
"sasv.match": "मेल खाता है",
|
||||
"sasv.noMatch": "मेल नहीं खाता",
|
||||
"sasv.you": "आप",
|
||||
"sasv.peer": "साथी",
|
||||
"sasv.waitingPeer": "साथी की प्रतीक्षा…",
|
||||
"sasv.codesDiffer": "कोड मेल नहीं खाते",
|
||||
"sasv.opening": "सुरक्षित चैट खुल रही है",
|
||||
"pwa.bannerTitle": "SecureBit.chat इंस्टॉल करें",
|
||||
"pwa.bannerDesc": "बेहतर सुरक्षा के साथ मूल ऐप जैसा अनुभव पाइए",
|
||||
"pwa.install": "इंस्टॉल करें",
|
||||
@@ -348,9 +353,8 @@
|
||||
"action.scan": "स्कैन करें",
|
||||
"action.downloadDesktop": "डेस्कटॉप ऐप डाउनलोड करें",
|
||||
"action.advancedSettings": "उन्नत सेटिंग्स",
|
||||
"cred.offerTag": "निमंत्रण · या पाठ कॉपी करें",
|
||||
"cred.answerTag": "उत्तर · या पाठ कॉपी करें",
|
||||
"cred.reveal": "देखने के लिए दबाएँ - यह कोड किसी को न बताएँ",
|
||||
"cred.orCopy": "या कोड कॉपी करें",
|
||||
"qr.title": "QR कोड स्कैन करें",
|
||||
"qr.subtitle": "कैमरा सामने वाले के QR पर रखें",
|
||||
"qr.scanning": "पढ़ा जा रहा है…",
|
||||
|
||||
+15
-11
@@ -120,6 +120,7 @@
|
||||
"intro.createDesc": "여러분의 기기가 키와 일회용 초대장을 만듭니다. 그 어느 것도 서버를 거치지 않습니다.",
|
||||
"intro.createCta": "키와 초대장 만들기",
|
||||
"intro.joinTitle": "채널 참여하기",
|
||||
"intro.joinDesc": "카메라로 상대방의 QR을 스캔하거나 초대 코드를 붙여넣으세요.",
|
||||
"intro.scanTitle": "카메라로 QR 스캔",
|
||||
"intro.scanSubtitle": "가장 빠릅니다 - 상대의 화면을 비추세요",
|
||||
"intro.orPasteCode": "또는 코드 붙여넣기",
|
||||
@@ -298,22 +299,26 @@
|
||||
"handshake.establish": "연결 맺기",
|
||||
"handshake.thenReceive": "그다음 상대가 보내는 응답을 받으세요",
|
||||
"handshake.pasteAnswerPlaceholder": "상대의 응답 코드를 붙여넣으세요…",
|
||||
"handshake.answerSentNote": "이 응답을 만든 사람에게 보내고 기다리세요 - 상대가 연결되면 대화가 열립니다.",
|
||||
"handshake.answerSentNote": "생성자의 연결을 기다리는 중입니다. 채팅이 자동으로 열립니다.",
|
||||
"handshake.qrHint": "상대가 코드를 다 읽을 때까지 이 화면을 열어 두세요.",
|
||||
"handshake.qrHintFrames": "악수가 {frames}개 화면으로 나뉘어 있습니다 - 상대가 전부 읽을 때까지 열어 두세요.",
|
||||
"verify.title": "보안 확인",
|
||||
"verify.desc": "다른 경로(통화나 대면)로 상대와 이 안전 코드를 대조한 뒤, 입력해서 대화를 여세요.",
|
||||
"verify.enterLabel": "대조한 코드를 입력하세요",
|
||||
"verify.placeholder": "여기에 코드 입력",
|
||||
"verify.confirm": "코드 확인",
|
||||
"verify.confirmed": "확인됨",
|
||||
"verify.mismatch": "일치하지 않음",
|
||||
"verify.yours": "내 확인",
|
||||
"verify.peer": "상대의 확인",
|
||||
"verify.pending": "대기 중",
|
||||
"verify.waiting": "코드 대기 중…",
|
||||
"verify.verified": "채널 확인 완료",
|
||||
"verify.bothConfirmed": "양쪽 모두 확인했습니다. 안전한 대화를 여는 중…",
|
||||
"sasv.title": "상대방 확인",
|
||||
"sasv.titleDone": "상대방 확인 완료",
|
||||
"sasv.desc": "통화나 대면으로 코드를 소리 내어 읽어 주세요. 상대방이 같은 숫자를 볼 때만 계속하세요.",
|
||||
"sasv.descDone": "두 사람 모두 같은 코드를 확인했습니다. 연결이 인증되었습니다.",
|
||||
"sasv.enter": "코드 입력",
|
||||
"sasv.match": "일치",
|
||||
"sasv.noMatch": "일치하지 않음",
|
||||
"sasv.you": "나",
|
||||
"sasv.peer": "상대방",
|
||||
"sasv.waitingPeer": "상대방을 기다리는 중…",
|
||||
"sasv.codesDiffer": "코드가 일치하지 않음",
|
||||
"sasv.opening": "보안 채팅을 여는 중",
|
||||
"pwa.bannerTitle": "SecureBit.chat 설치",
|
||||
"pwa.bannerDesc": "보안이 강화된 네이티브 앱 경험을 누리세요",
|
||||
"pwa.install": "설치",
|
||||
@@ -348,9 +353,8 @@
|
||||
"action.scan": "스캔",
|
||||
"action.downloadDesktop": "데스크톱 앱 내려받기",
|
||||
"action.advancedSettings": "고급 설정",
|
||||
"cred.offerTag": "초대 · 또는 텍스트 복사",
|
||||
"cred.answerTag": "응답 · 또는 텍스트 복사",
|
||||
"cred.reveal": "눌러서 보기 - 이 코드는 남에게 알리지 마세요",
|
||||
"cred.orCopy": "또는 코드 복사",
|
||||
"qr.title": "QR 코드 스캔",
|
||||
"qr.subtitle": "상대의 QR에 카메라를 맞추세요",
|
||||
"qr.scanning": "읽는 중…",
|
||||
|
||||
+16
-12
@@ -125,9 +125,10 @@
|
||||
"intro.createDesc": "Ваше устройство создаёт ключи и одноразовое приглашение. Ничто из этого не попадает на сервер.",
|
||||
"intro.createCta": "Создать ключи и приглашение",
|
||||
"intro.joinTitle": "Присоединиться к каналу",
|
||||
"intro.joinDesc": "Отсканируйте QR собеседника камерой или вставьте его код приглашения.",
|
||||
"intro.scanTitle": "Сканировать QR камерой",
|
||||
"intro.scanSubtitle": "Быстрее всего - наведите на экран собеседника",
|
||||
"intro.orPasteCode": "или вставьте код",
|
||||
"intro.orPasteCode": "Или вставьте код",
|
||||
"intro.pastePlaceholder": "Вставьте код приглашения сюда…",
|
||||
"intro.connect": "Соединиться",
|
||||
"intro.connecting": "Обработка…",
|
||||
@@ -303,22 +304,26 @@
|
||||
"handshake.establish": "Установить соединение",
|
||||
"handshake.thenReceive": "Затем получите ответ, который пришлёт собеседник",
|
||||
"handshake.pasteAnswerPlaceholder": "Вставьте код ответа собеседника…",
|
||||
"handshake.answerSentNote": "Отправьте этот ответ создателю и подождите - чат откроется, как только он подключится.",
|
||||
"handshake.answerSentNote": "Ждём, пока создатель подключится. Чат откроется автоматически.",
|
||||
"handshake.qrHint": "Держите это открытым, пока собеседник не считает код.",
|
||||
"handshake.qrHintFrames": "Рукопожатие разбито на {frames} кадров - держите это открытым, пока собеседник не считает все.",
|
||||
"verify.title": "Проверка безопасности",
|
||||
"verify.desc": "Сверьте этот код безопасности с собеседником по другому каналу (голосом или лично), затем введите его, чтобы открыть чат.",
|
||||
"verify.enterLabel": "Введите сверенный код",
|
||||
"verify.placeholder": "Введите код здесь",
|
||||
"verify.confirm": "Подтвердить код",
|
||||
"verify.confirmed": "Подтверждено",
|
||||
"verify.mismatch": "Не совпадают",
|
||||
"verify.yours": "Ваше подтверждение",
|
||||
"verify.peer": "Подтверждение собеседника",
|
||||
"verify.pending": "Ожидается",
|
||||
"verify.waiting": "Ожидание кода…",
|
||||
"verify.verified": "Канал проверен",
|
||||
"verify.bothConfirmed": "Обе стороны подтвердили. Открываем защищённый чат…",
|
||||
"sasv.title": "Проверьте собеседника",
|
||||
"sasv.titleDone": "Собеседник проверен",
|
||||
"sasv.desc": "Прочитайте код вслух по звонку или при встрече. Продолжайте, только если собеседник видит те же цифры.",
|
||||
"sasv.descDone": "Вы оба подтвердили один и тот же код. Соединение подлинное.",
|
||||
"sasv.enter": "Введите код",
|
||||
"sasv.match": "Совпадает",
|
||||
"sasv.noMatch": "Не совпадает",
|
||||
"sasv.you": "Вы",
|
||||
"sasv.peer": "Собеседник",
|
||||
"sasv.waitingPeer": "Ждём собеседника…",
|
||||
"sasv.codesDiffer": "Коды не совпадают",
|
||||
"sasv.opening": "Открываем защищённый чат",
|
||||
"pwa.bannerTitle": "Установите SecureBit.chat",
|
||||
"pwa.bannerDesc": "Опыт полноценного приложения с усиленной защитой",
|
||||
"pwa.install": "Установить",
|
||||
@@ -353,9 +358,8 @@
|
||||
"action.scan": "Сканировать",
|
||||
"action.downloadDesktop": "Скачать приложение",
|
||||
"action.advancedSettings": "Дополнительные настройки",
|
||||
"cred.offerTag": "предложение · или скопируйте текст",
|
||||
"cred.answerTag": "ответ · или скопируйте текст",
|
||||
"cred.reveal": "Нажмите, чтобы показать - держите этот код в тайне",
|
||||
"cred.orCopy": "Или скопируйте код",
|
||||
"qr.title": "Сканировать QR-код",
|
||||
"qr.subtitle": "Наведите камеру на их QR-код",
|
||||
"qr.scanning": "Сканирование…",
|
||||
|
||||
+16
-12
@@ -125,9 +125,10 @@
|
||||
"intro.createDesc": "Ваш пристрій створює ключі та одноразове запрошення. Ніщо з цього не потрапляє на сервер.",
|
||||
"intro.createCta": "Створити ключі та запрошення",
|
||||
"intro.joinTitle": "Приєднатися до каналу",
|
||||
"intro.joinDesc": "Відскануйте QR співрозмовника камерою або вставте його код запрошення.",
|
||||
"intro.scanTitle": "Сканувати QR камерою",
|
||||
"intro.scanSubtitle": "Найшвидше - наведіть на екран співрозмовника",
|
||||
"intro.orPasteCode": "або вставте код",
|
||||
"intro.orPasteCode": "Або вставте код",
|
||||
"intro.pastePlaceholder": "Вставте код запрошення сюди…",
|
||||
"intro.connect": "З'єднатися",
|
||||
"intro.connecting": "Обробка…",
|
||||
@@ -303,22 +304,26 @@
|
||||
"handshake.establish": "Встановити з'єднання",
|
||||
"handshake.thenReceive": "Потім отримайте відповідь, яку надішле співрозмовник",
|
||||
"handshake.pasteAnswerPlaceholder": "Вставте код відповіді співрозмовника…",
|
||||
"handshake.answerSentNote": "Надішліть цю відповідь створювачу й зачекайте - чат відкриється, щойно він приєднається.",
|
||||
"handshake.answerSentNote": "Чекаємо, поки творець під'єднається. Чат відкриється автоматично.",
|
||||
"handshake.qrHint": "Тримайте це відкритим, доки співрозмовник не зчитає код.",
|
||||
"handshake.qrHintFrames": "Рукостискання розбите на {frames} кадрів - тримайте це відкритим, доки співрозмовник не зчитає всі.",
|
||||
"verify.title": "Перевірка безпеки",
|
||||
"verify.desc": "Звірте цей код безпеки зі співрозмовником іншим каналом (голосом або особисто), потім введіть його, щоб відкрити чат.",
|
||||
"verify.enterLabel": "Введіть звірений код",
|
||||
"verify.placeholder": "Введіть код тут",
|
||||
"verify.confirm": "Підтвердити код",
|
||||
"verify.confirmed": "Підтверджено",
|
||||
"verify.mismatch": "Не збігаються",
|
||||
"verify.yours": "Ваше підтвердження",
|
||||
"verify.peer": "Підтвердження співрозмовника",
|
||||
"verify.pending": "Очікується",
|
||||
"verify.waiting": "Очікування коду…",
|
||||
"verify.verified": "Канал перевірено",
|
||||
"verify.bothConfirmed": "Обидві сторони підтвердили. Відкриваємо захищений чат…",
|
||||
"sasv.title": "Перевірте співрозмовника",
|
||||
"sasv.titleDone": "Співрозмовника перевірено",
|
||||
"sasv.desc": "Прочитайте код уголос під час дзвінка або зустрічі. Продовжуйте, лише якщо співрозмовник бачить ті самі цифри.",
|
||||
"sasv.descDone": "Ви обидва підтвердили той самий код. З'єднання справжнє.",
|
||||
"sasv.enter": "Введіть код",
|
||||
"sasv.match": "Збігається",
|
||||
"sasv.noMatch": "Не збігається",
|
||||
"sasv.you": "Ви",
|
||||
"sasv.peer": "Співрозмовник",
|
||||
"sasv.waitingPeer": "Чекаємо на співрозмовника…",
|
||||
"sasv.codesDiffer": "Коди не збігаються",
|
||||
"sasv.opening": "Відкриваємо захищений чат",
|
||||
"pwa.bannerTitle": "Встановіть SecureBit.chat",
|
||||
"pwa.bannerDesc": "Досвід власного застосунку з посиленим захистом",
|
||||
"pwa.install": "Встановити",
|
||||
@@ -353,9 +358,8 @@
|
||||
"action.scan": "Сканувати",
|
||||
"action.downloadDesktop": "Завантажити застосунок",
|
||||
"action.advancedSettings": "Додаткові налаштування",
|
||||
"cred.offerTag": "пропозиція · або скопіюйте текст",
|
||||
"cred.answerTag": "відповідь · або скопіюйте текст",
|
||||
"cred.reveal": "Натисніть, щоб показати - тримайте цей код у таємниці",
|
||||
"cred.orCopy": "Або скопіюйте код",
|
||||
"qr.title": "Сканувати QR-код",
|
||||
"qr.subtitle": "Наведіть камеру на їхній QR-код",
|
||||
"qr.scanning": "Сканування…",
|
||||
|
||||
+15
-11
@@ -125,6 +125,7 @@
|
||||
"intro.createDesc": "آپ کا آلہ کلیدیں اور ایک بار کی دعوت بناتا ہے۔ کچھ بھی سرور کو نہیں چھوتا۔",
|
||||
"intro.createCta": "کلیدیں اور دعوت بنائیں",
|
||||
"intro.joinTitle": "چینل میں شامل ہوں",
|
||||
"intro.joinDesc": "کیمرے سے اپنے ساتھی کا QR اسکین کریں، یا ان کا دعوتی کوڈ چسپاں کریں۔",
|
||||
"intro.scanTitle": "کیمرے سے QR پڑھیں",
|
||||
"intro.scanSubtitle": "سب سے تیز - ساتھی کی سکرین کی طرف کریں",
|
||||
"intro.orPasteCode": "یا کوڈ چسپاں کریں",
|
||||
@@ -303,22 +304,26 @@
|
||||
"handshake.establish": "اتصال قائم کریں",
|
||||
"handshake.thenReceive": "پھر وہ جواب وصول کریں جو آپ کا ساتھی واپس بھیجتا ہے",
|
||||
"handshake.pasteAnswerPlaceholder": "ساتھی کا جوابی کوڈ چسپاں کریں…",
|
||||
"handshake.answerSentNote": "یہ جواب بنانے والے کو بھیجیں، پھر انتظار کریں - ان کے جڑتے ہی گفتگو کھل جائے گی۔",
|
||||
"handshake.answerSentNote": "تخلیق کار کے جڑنے کا انتظار ہے۔ چیٹ خود بخود کھل جائے گی۔",
|
||||
"handshake.qrHint": "یہ اس وقت تک کھلا رکھیں جب تک ساتھی کوڈ نہ پکڑ لے۔",
|
||||
"handshake.qrHintFrames": "مصافحہ {frames} فریموں میں بٹا ہے - یہ اس وقت تک کھلا رکھیں جب تک ساتھی سب نہ پکڑ لے۔",
|
||||
"verify.title": "حفاظتی تصدیق",
|
||||
"verify.desc": "یہ حفاظتی کوڈ کسی الگ ذریعے (آواز / بالمشافہ) سے اپنے ساتھی کے ساتھ ملائیں، پھر گفتگو کھولنے کے لیے لکھیں۔",
|
||||
"verify.enterLabel": "تصدیق شدہ کوڈ درج کریں",
|
||||
"verify.placeholder": "کوڈ یہاں لکھیں",
|
||||
"verify.confirm": "کوڈ کی تصدیق",
|
||||
"verify.confirmed": "تصدیق ہو گئی",
|
||||
"verify.mismatch": "مطابقت نہیں",
|
||||
"verify.yours": "آپ کی تصدیق",
|
||||
"verify.peer": "ساتھی کی تصدیق",
|
||||
"verify.pending": "زیرِ التوا",
|
||||
"verify.waiting": "کوڈ کا انتظار…",
|
||||
"verify.verified": "چینل تصدیق شدہ",
|
||||
"verify.bothConfirmed": "دونوں فریقوں نے تصدیق کی۔ محفوظ گفتگو کھولی جا رہی ہے…",
|
||||
"sasv.title": "اپنے ساتھی کی تصدیق کریں",
|
||||
"sasv.titleDone": "ساتھی کی تصدیق ہو گئی",
|
||||
"sasv.desc": "کال پر یا آمنے سامنے کوڈ بلند آواز سے پڑھیں۔ صرف تب آگے بڑھیں جب آپ کے ساتھی کو وہی ہندسے نظر آئیں۔",
|
||||
"sasv.descDone": "آپ دونوں نے ایک ہی کوڈ کی تصدیق کی۔ کنکشن مستند ہے۔",
|
||||
"sasv.enter": "کوڈ درج کریں",
|
||||
"sasv.match": "مماثل ہے",
|
||||
"sasv.noMatch": "مماثل نہیں",
|
||||
"sasv.you": "آپ",
|
||||
"sasv.peer": "ساتھی",
|
||||
"sasv.waitingPeer": "ساتھی کا انتظار…",
|
||||
"sasv.codesDiffer": "کوڈ مماثل نہیں",
|
||||
"sasv.opening": "محفوظ چیٹ کھل رہی ہے",
|
||||
"pwa.bannerTitle": "SecureBit.chat نصب کریں",
|
||||
"pwa.bannerDesc": "بہتر سلامتی کے ساتھ مقامی ایپ کا تجربہ حاصل کریں",
|
||||
"pwa.install": "نصب",
|
||||
@@ -353,9 +358,8 @@
|
||||
"action.scan": "پڑھیں",
|
||||
"action.downloadDesktop": "ڈیسک ٹاپ ایپ ڈاؤن لوڈ کریں",
|
||||
"action.advancedSettings": "اعلیٰ ترتیبات",
|
||||
"cred.offerTag": "پیشکش · یا متن نقل کریں",
|
||||
"cred.answerTag": "جواب · یا متن نقل کریں",
|
||||
"cred.reveal": "دکھانے کے لیے دبائیں - یہ کوڈ نجی رکھیں",
|
||||
"cred.orCopy": "یا کوڈ نقل کریں",
|
||||
"qr.title": "QR کوڈ پڑھیں",
|
||||
"qr.subtitle": "اپنا کیمرہ ان کے QR کی طرف کریں",
|
||||
"qr.scanning": "پڑھا جا رہا ہے…",
|
||||
|
||||
+15
-11
@@ -120,6 +120,7 @@
|
||||
"intro.createDesc": "你的设备会生成密钥和一次性邀请。这些都不会经过任何服务器。",
|
||||
"intro.createCta": "生成密钥与邀请",
|
||||
"intro.joinTitle": "加入通道",
|
||||
"intro.joinDesc": "用相机扫描对方的二维码,或粘贴对方的邀请代码。",
|
||||
"intro.scanTitle": "用摄像头扫描二维码",
|
||||
"intro.scanSubtitle": "最快的方式 —— 对准对方的屏幕",
|
||||
"intro.orPasteCode": "或粘贴代码",
|
||||
@@ -298,22 +299,26 @@
|
||||
"handshake.establish": "建立连接",
|
||||
"handshake.thenReceive": "然后接收对方发回的应答",
|
||||
"handshake.pasteAnswerPlaceholder": "粘贴对方的应答码…",
|
||||
"handshake.answerSentNote": "把这段应答发给创建者,然后等待 —— 对方一连上,聊天就会打开。",
|
||||
"handshake.answerSentNote": "正在等待创建者连接。聊天将自动打开。",
|
||||
"handshake.qrHint": "在对方读取完代码之前,请保持此界面打开。",
|
||||
"handshake.qrHintFrames": "握手被拆成 {frames} 帧 —— 在对方读完全部帧之前,请保持此界面打开。",
|
||||
"verify.title": "安全校验",
|
||||
"verify.desc": "通过另一条渠道(通话或当面)与对方核对这个安全码,然后输入它以解锁聊天。",
|
||||
"verify.enterLabel": "输入核对过的安全码",
|
||||
"verify.placeholder": "在此输入安全码",
|
||||
"verify.confirm": "确认安全码",
|
||||
"verify.confirmed": "已确认",
|
||||
"verify.mismatch": "不一致",
|
||||
"verify.yours": "你的确认",
|
||||
"verify.peer": "对方的确认",
|
||||
"verify.pending": "等待中",
|
||||
"verify.waiting": "等待安全码…",
|
||||
"verify.verified": "通道已校验",
|
||||
"verify.bothConfirmed": "双方均已确认。正在打开安全聊天…",
|
||||
"sasv.title": "验证对方",
|
||||
"sasv.titleDone": "对方已验证",
|
||||
"sasv.desc": "通过通话或当面大声读出代码。只有对方看到相同的数字时才继续。",
|
||||
"sasv.descDone": "你们双方确认了相同的代码。连接已通过认证。",
|
||||
"sasv.enter": "输入代码",
|
||||
"sasv.match": "一致",
|
||||
"sasv.noMatch": "不一致",
|
||||
"sasv.you": "你",
|
||||
"sasv.peer": "对方",
|
||||
"sasv.waitingPeer": "正在等待对方…",
|
||||
"sasv.codesDiffer": "代码不一致",
|
||||
"sasv.opening": "正在打开安全聊天",
|
||||
"pwa.bannerTitle": "安装 SecureBit.chat",
|
||||
"pwa.bannerDesc": "获得原生应用般的体验,安全性更进一步",
|
||||
"pwa.install": "安装",
|
||||
@@ -348,9 +353,8 @@
|
||||
"action.scan": "扫描",
|
||||
"action.downloadDesktop": "下载桌面应用",
|
||||
"action.advancedSettings": "高级设置",
|
||||
"cred.offerTag": "邀请 · 或复制文本",
|
||||
"cred.answerTag": "应答 · 或复制文本",
|
||||
"cred.reveal": "点击显示 - 请勿泄露此代码",
|
||||
"cred.orCopy": "或复制代码",
|
||||
"qr.title": "扫描二维码",
|
||||
"qr.subtitle": "把摄像头对准对方的二维码",
|
||||
"qr.scanning": "扫描中…",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"version": "1790225533889",
|
||||
"buildVersion": "1790225533889",
|
||||
"appVersion": "6.8.8",
|
||||
"buildTime": "2026-09-24T04:52:13.935Z",
|
||||
"buildId": "1790225533889-eac3009",
|
||||
"gitHash": "eac3009",
|
||||
"version": "1790540575787",
|
||||
"buildVersion": "1790540575787",
|
||||
"appVersion": "6.8.12",
|
||||
"buildTime": "2026-09-27T20:22:55.824Z",
|
||||
"buildId": "1790540575787-cd9d78d",
|
||||
"gitHash": "cd9d78d",
|
||||
"generated": true,
|
||||
"generatedAt": "2026-09-24T04:52:13.936Z"
|
||||
"generatedAt": "2026-09-27T20:22:55.825Z"
|
||||
}
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "securebit-chat",
|
||||
"version": "6.8.8",
|
||||
"version": "6.8.12",
|
||||
"description": "Secure P2P Communication Application with End-to-End Encryption",
|
||||
"main": "index.html",
|
||||
"scripts": {
|
||||
|
||||
+19
-19
@@ -30,18 +30,18 @@
|
||||
|
||||
<!-- PWA Manifest -->
|
||||
<link rel="manifest" href="/ru/manifest.json">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
|
||||
|
||||
<!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html>
|
||||
from localStorage while the parser is still in <head>, so the page paints in the
|
||||
chosen theme on the first frame instead of flashing dark and correcting itself.
|
||||
Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). -->
|
||||
<script src="/src/scripts/theme-boot.js?v=1790225533889"></script>
|
||||
<script src="/src/scripts/theme-boot.js?v=1790540575787"></script>
|
||||
|
||||
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
|
||||
not replayed, so the listener has to exist before Chrome decides the page
|
||||
is installable. Deferred/module scripts are already too late. -->
|
||||
<script src="/src/scripts/pwa-install-capture.js?v=1790225533889"></script>
|
||||
<script src="/src/scripts/pwa-install-capture.js?v=1790540575787"></script>
|
||||
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
@@ -116,7 +116,7 @@
|
||||
<link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png">
|
||||
|
||||
<!-- Apple Touch Icons -->
|
||||
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790225533889">
|
||||
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790540575787">
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png">
|
||||
@@ -125,7 +125,7 @@
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790225533889">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790540575787">
|
||||
|
||||
<!-- Microsoft Tiles -->
|
||||
<meta name="msapplication-TileColor" content="#ff6b35">
|
||||
@@ -264,7 +264,7 @@
|
||||
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
|
||||
both execute in document order after parsing, so React still runs before the
|
||||
app modules below, but the parser / first paint is no longer blocked. -->
|
||||
<script defer src="/config/ice-servers.js?v=1790225533889"></script>
|
||||
<script defer src="/config/ice-servers.js?v=1790540575787"></script>
|
||||
<script defer src="/libs/react/react.production.min.js"></script>
|
||||
<script defer src="/libs/react-dom/react-dom.production.min.js"></script>
|
||||
<!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only —
|
||||
@@ -278,8 +278,8 @@
|
||||
the end of <body>: 21 KB of CSS in total, but eight round trips before the
|
||||
browser could paint, which GTmetrix measured at 441 ms on a throttled mobile
|
||||
connection. Bytes were never the problem; requests were. -->
|
||||
<link rel="stylesheet" href="/assets/app.css?v=1790225533889">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
|
||||
<link rel="stylesheet" href="/assets/app.css?v=1790540575787">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
|
||||
<!-- Preload only the fonts needed for first paint. Inter is one variable file that
|
||||
answers for every weight, so there is one to preload rather than the two of five
|
||||
copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and
|
||||
@@ -287,19 +287,19 @@
|
||||
<link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
|
||||
<script defer src="/src/scripts/load-async-css.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/load-async-css.js?v=1790540575787"></script>
|
||||
<noscript>
|
||||
<link rel="stylesheet" href="/libs/prism/prism.css">
|
||||
</noscript>
|
||||
<script defer src="/src/scripts/fa-check.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/fa-check.js?v=1790540575787"></script>
|
||||
<!-- This locale's dictionary, registered before anything can ask for a string.
|
||||
Only the default locale's is bundled (src/i18n/index.js imports it, because t()
|
||||
falls back to it); every other page loads its own here instead of all thirteen
|
||||
riding along inside dist/app.js. Absent on the default locale's own page. -->
|
||||
<script type="module" src="/src/i18n/dict/ru.js?v=1790225533889"></script>
|
||||
<script type="module" src="/src/i18n/dict/ru.js?v=1790540575787"></script>
|
||||
<!-- Update Manager - система принудительного обновления -->
|
||||
<script defer src="/src/utils/updateManager.js?v=1790225533889"></script>
|
||||
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script>
|
||||
<script defer src="/src/utils/updateManager.js?v=1790540575787"></script>
|
||||
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790540575787"></script>
|
||||
<!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the
|
||||
app has mounted and the browser is idle, so the 142 KB it weighs is off the
|
||||
first load. src/components/QRScanner.js used to be here too and registered
|
||||
@@ -487,12 +487,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="/dist/app-boot.js?v=1790225533889"></script>
|
||||
<script type="module" src="/dist/app.js?v=1790225533889"></script>
|
||||
<script type="module" src="/dist/app-boot.js?v=1790540575787"></script>
|
||||
<script type="module" src="/dist/app.js?v=1790540575787"></script>
|
||||
|
||||
<script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script>
|
||||
<script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script>
|
||||
<script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script>
|
||||
<script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/pwa-register.js?v=1790540575787"></script>
|
||||
<script src="/src/pwa/install-prompt.js?v=1790540575787" type="module"></script>
|
||||
<script src="/src/pwa/pwa-manager.js?v=1790540575787" type="module"></script>
|
||||
<script defer src="/src/scripts/pwa-offline-test.js?v=1790540575787"></script>
|
||||
</body>
|
||||
</html>
|
||||
+23
-23
@@ -17,7 +17,7 @@
|
||||
<xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/>
|
||||
<xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/>
|
||||
<lastmod>2026-09-24</lastmod>
|
||||
<lastmod>2026-09-27</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
@@ -37,7 +37,7 @@
|
||||
<xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/>
|
||||
<xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/>
|
||||
<lastmod>2026-09-24</lastmod>
|
||||
<lastmod>2026-09-27</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
@@ -57,7 +57,7 @@
|
||||
<xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/>
|
||||
<xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/>
|
||||
<lastmod>2026-09-24</lastmod>
|
||||
<lastmod>2026-09-27</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
@@ -77,7 +77,7 @@
|
||||
<xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/>
|
||||
<xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/>
|
||||
<lastmod>2026-09-24</lastmod>
|
||||
<lastmod>2026-09-27</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
@@ -97,7 +97,7 @@
|
||||
<xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/>
|
||||
<xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/>
|
||||
<lastmod>2026-09-24</lastmod>
|
||||
<lastmod>2026-09-27</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
@@ -117,7 +117,7 @@
|
||||
<xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/>
|
||||
<xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/>
|
||||
<lastmod>2026-09-24</lastmod>
|
||||
<lastmod>2026-09-27</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
@@ -137,7 +137,7 @@
|
||||
<xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/>
|
||||
<xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/>
|
||||
<lastmod>2026-09-24</lastmod>
|
||||
<lastmod>2026-09-27</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
@@ -157,7 +157,7 @@
|
||||
<xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/>
|
||||
<xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/>
|
||||
<lastmod>2026-09-24</lastmod>
|
||||
<lastmod>2026-09-27</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
@@ -177,7 +177,7 @@
|
||||
<xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/>
|
||||
<xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/>
|
||||
<lastmod>2026-09-24</lastmod>
|
||||
<lastmod>2026-09-27</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
@@ -197,7 +197,7 @@
|
||||
<xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/>
|
||||
<xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/>
|
||||
<lastmod>2026-09-24</lastmod>
|
||||
<lastmod>2026-09-27</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
@@ -217,7 +217,7 @@
|
||||
<xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/>
|
||||
<xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/>
|
||||
<lastmod>2026-09-24</lastmod>
|
||||
<lastmod>2026-09-27</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
@@ -237,7 +237,7 @@
|
||||
<xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/>
|
||||
<xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/>
|
||||
<lastmod>2026-09-24</lastmod>
|
||||
<lastmod>2026-09-27</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
@@ -257,67 +257,67 @@
|
||||
<xhtml:link rel="alternate" hreflang="fa" href="https://securebit.chat/fa/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ur" href="https://securebit.chat/ur/"/>
|
||||
<xhtml:link rel="alternate" hreflang="x-default" href="https://securebit.chat/"/>
|
||||
<lastmod>2026-09-24</lastmod>
|
||||
<lastmod>2026-09-27</lastmod>
|
||||
<changefreq>weekly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://securebit.chat/docs/</loc>
|
||||
<lastmod>2026-09-24</lastmod>
|
||||
<lastmod>2026-09-27</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://securebit.chat/docs/faq/</loc>
|
||||
<lastmod>2026-09-24</lastmod>
|
||||
<lastmod>2026-09-27</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://securebit.chat/docs/architecture/</loc>
|
||||
<lastmod>2026-09-24</lastmod>
|
||||
<lastmod>2026-09-27</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://securebit.chat/docs/cryptography/</loc>
|
||||
<lastmod>2026-09-24</lastmod>
|
||||
<lastmod>2026-09-27</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://securebit.chat/docs/descriptor-sbq2/</loc>
|
||||
<lastmod>2026-09-24</lastmod>
|
||||
<lastmod>2026-09-27</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://securebit.chat/docs/configuration/</loc>
|
||||
<lastmod>2026-09-24</lastmod>
|
||||
<lastmod>2026-09-27</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://securebit.chat/docs/calls/</loc>
|
||||
<lastmod>2026-09-24</lastmod>
|
||||
<lastmod>2026-09-27</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://securebit.chat/docs/api/</loc>
|
||||
<lastmod>2026-09-24</lastmod>
|
||||
<lastmod>2026-09-27</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://securebit.chat/docs/contributing/</loc>
|
||||
<lastmod>2026-09-24</lastmod>
|
||||
<lastmod>2026-09-27</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://securebit.chat/docs/use-policy/</loc>
|
||||
<lastmod>2026-09-24</lastmod>
|
||||
<lastmod>2026-09-27</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.7</priority>
|
||||
</url>
|
||||
|
||||
+192
-84
@@ -1170,9 +1170,23 @@ import { GroupCallMedia, mediaErrorCode } from './group/groupCallMedia.js';
|
||||
const [copied, setCopied] = React.useState(false);
|
||||
const [sasInput, setSasInput] = React.useState('');
|
||||
const [sasError, setSasError] = React.useState('');
|
||||
const [sasFocused, setSasFocused] = React.useState(false);
|
||||
const [sasShake, setSasShake] = React.useState(0);
|
||||
const [platformsOpen, setPlatformsOpen] = React.useState(false);
|
||||
const [codeRevealed, setCodeRevealed] = React.useState(false);
|
||||
const [genProgress, setGenProgress] = React.useState(0);
|
||||
// Countdown on the invitation: the joining side refuses an offer older
|
||||
// than 30 minutes (MAX_OFFER_AGE in EnhancedSecureWebRTCManager), measured
|
||||
// from the offer's own timestamp.
|
||||
const OFFER_TTL_MS = 30 * 60 * 1000;
|
||||
const offerTs = (offerData && typeof offerData === 'object' && Number(offerData.ts || offerData.timestamp)) || null;
|
||||
const [nowTick, setNowTick] = React.useState(() => Date.now());
|
||||
React.useEffect(() => {
|
||||
if (!showOfferStep || !offerTs) return undefined;
|
||||
setNowTick(Date.now());
|
||||
const id = setInterval(() => setNowTick(Date.now()), 1000);
|
||||
return () => clearInterval(id);
|
||||
}, [showOfferStep, offerTs]);
|
||||
|
||||
// Reset the typed SAS whenever a fresh verification code arrives
|
||||
React.useEffect(() => { setSasInput(''); setSasError(''); }, [verificationCode]);
|
||||
@@ -1549,72 +1563,151 @@ import { GroupCallMedia, mediaErrorCode } from './group/groupCallMedia.js';
|
||||
|
||||
const backButton = (key) => h('button', { key: key || 'back', className: 'sb-soft-btn', onClick: resetToSelect, style: { display: 'inline-flex', alignItems: 'center', gap: '6px', marginBottom: '14px', paddingBlock: '6px', paddingInlineStart: '8px', paddingInlineEnd: '11px', borderRadius: '8px', border: '1px solid rgba(var(--sb-ink), 0.08)', background: 'transparent', color: 'var(--sb-text-6)', fontFamily: 'inherit', fontSize: '12.5px', fontWeight: 600, cursor: 'pointer' } }, [fa('fa-chevron-left', { key: 'i' }), t('action.back')]);
|
||||
|
||||
// credential code block (offer/answer text fallback + copy)
|
||||
const credBlock = h('div', { key: 'codeblock', style: { borderRadius: '13px', border: '1px solid rgba(var(--sb-ink), 0.08)', background: 'var(--sb-surface)', overflow: 'hidden', marginBottom: '16px' } }, [
|
||||
h('div', { key: 'bar', style: { display: 'flex', alignItems: 'center', gap: '8px', padding: '9px 12px', borderBottom: '1px solid rgba(var(--sb-ink), 0.06)', background: 'rgba(0,0,0,0.2)' } }, [
|
||||
h('span', { key: 'dot', style: { width: '7px', height: '7px', borderRadius: '50%', background: accent } }),
|
||||
h('span', { key: 'tag', style: { fontFamily: MONO, fontSize: '10.5px', fontWeight: 600, color: 'var(--sb-text-7)' } }, isCreate ? t('cred.offerTag') : t('cred.answerTag')),
|
||||
h('button', { key: 'copy', onClick: copyCred, style: { marginInlineStart: 'auto', padding: '4px 9px', borderRadius: '6px', border: `1px solid ${copied ? 'rgba(var(--sb-green-rgb), 0.4)' : 'rgba(var(--sb-ink), 0.1)'}`, background: copied ? 'rgba(var(--sb-green-rgb), 0.1)' : 'rgba(var(--sb-ink), 0.04)', color: copied ? C_GREEN : 'var(--sb-text-5)', fontFamily: 'inherit', fontSize: '11px', fontWeight: 600, cursor: 'pointer', transition: 'all .14s' } }, copied ? t('action.copied') : t('action.copy'))
|
||||
// Invitation card (Share Invitation v3 design): QR preview row on top,
|
||||
// the text code with copy + countdown below, one bordered surface.
|
||||
const offerLeft = (isCreate && offerTs) ? Math.max(0, Math.ceil((offerTs + OFFER_TTL_MS - nowTick) / 1000)) : null;
|
||||
const offerExpires = offerLeft === null ? null : `${Math.floor(offerLeft / 60)}:${String(offerLeft % 60).padStart(2, '0')}`;
|
||||
const credCard = h('div', { key: 'card', style: { borderRadius: '16px', border: '1px solid rgba(var(--sb-ink), 0.07)', background: 'var(--sb-surface)', overflow: 'hidden', marginBottom: isCreate ? '26px' : '16px' } }, [
|
||||
qrCodeUrl && h('button', { key: 'showqr', className: 'sb-qr-row', onClick: () => setQrModalOpen(true), style: { width: '100%', display: 'flex', alignItems: 'center', gap: '14px', padding: '14px 16px', border: 'none', background: 'transparent', color: 'inherit', fontFamily: 'inherit', cursor: 'pointer', textAlign: 'start', transition: 'background .15s' } }, [
|
||||
h('span', { key: 'ic', style: { flex: 'none', width: '48px', height: '48px', padding: '5px', borderRadius: '9px', background: '#fff' } },
|
||||
h('img', { src: qrCodeUrl, alt: '', style: { width: '100%', height: '100%', objectFit: 'contain', display: 'block', imageRendering: 'pixelated' } })),
|
||||
h('span', { key: 'tx', style: { flex: 1, minWidth: 0 } }, [
|
||||
h('span', { key: 't', style: { display: 'block', fontSize: '15px', fontWeight: 700, color: 'var(--sb-text-1)' } }, t('qr.showTitle')),
|
||||
h('span', { key: 's', style: { display: 'block', fontSize: '12.5px', color: 'var(--sb-text-7)', marginTop: '2px' } }, (qrFramesTotal || 0) > 1 ? t('qr.showSubtitleFrames', { frames: qrFramesTotal }) : t('qr.showSubtitle'))
|
||||
]),
|
||||
fa('fa-chevron-right', { color: 'var(--sb-text-9)' })
|
||||
]),
|
||||
// The handshake code is sensitive — keep it blurred until the
|
||||
// user deliberately reveals it, underscoring that it must be
|
||||
// shared only over a channel they trust.
|
||||
h('div', { key: 'codewrap', style: { position: 'relative' } }, [
|
||||
h('div', { key: 'code', className: 'sb-sc', style: { fontFamily: MONO, fontSize: '11px', lineHeight: 1.55, color: 'var(--sb-text-code)', wordBreak: 'break-all', padding: '11px 12px', maxHeight: '72px', overflowY: 'auto', filter: codeRevealed ? 'none' : 'blur(6px)', userSelect: codeRevealed ? 'text' : 'none', transition: 'filter .2s' } }, credCode),
|
||||
!codeRevealed && h('button', { key: 'reveal', onClick: () => setCodeRevealed(true), style: { position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '8px', border: 'none', background: 'rgba(var(--sb-surface-rgb), 0.25)', color: 'var(--sb-text-4)', fontFamily: 'inherit', fontSize: '12px', fontWeight: 600, cursor: 'pointer' } }, [
|
||||
fa('fa-eye', { key: 'i', fontSize: '15px' }),
|
||||
t('cred.reveal')
|
||||
qrCodeUrl && h('div', { key: 'div', style: { height: '1px', background: 'rgba(var(--sb-ink), 0.06)' } }),
|
||||
h('div', { key: 'code', style: { padding: '12px 16px 16px' } }, [
|
||||
h('div', { key: 'bar', style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: '10px', marginBottom: '10px' } }, [
|
||||
h('span', { key: 'l', style: { fontSize: '12.5px', fontWeight: 600, color: 'var(--sb-text-6)' } }, t('cred.orCopy')),
|
||||
h('span', { key: 'r', style: { display: 'flex', alignItems: 'center', gap: '12px' } }, [
|
||||
offerExpires && h('span', { key: 'exp', dir: 'ltr', style: { fontFamily: MONO, fontSize: '11px', color: offerLeft < 60 ? 'var(--sb-red)' : 'var(--sb-text-9)' } }, offerExpires),
|
||||
h('button', { key: 'copy', className: 'sb-soft-btn', onClick: copyCred, style: { display: 'inline-flex', alignItems: 'center', gap: '6px', padding: '6px 11px', borderRadius: '8px', border: '1px solid rgba(var(--sb-ink), 0.1)', background: 'rgba(var(--sb-ink), 0.03)', color: 'var(--sb-text-2)', fontFamily: 'inherit', fontSize: '12.5px', fontWeight: 700, cursor: 'pointer' } }, [
|
||||
copied && fa('fa-check', { key: 'i', color: C_GREEN_SOLID }),
|
||||
copied ? t('action.copied') : t('action.copy')
|
||||
])
|
||||
])
|
||||
]),
|
||||
// The handshake code is sensitive — keep it blurred until the
|
||||
// user deliberately reveals it, underscoring that it must be
|
||||
// shared only over a channel they trust.
|
||||
h('div', { key: 'codewrap', onClick: () => setCodeRevealed((v) => !v), style: { position: 'relative', borderRadius: '11px', border: '1px solid rgba(var(--sb-ink), 0.05)', background: 'var(--sb-bg)', overflow: 'hidden', cursor: 'pointer' } }, [
|
||||
h('div', { key: 'code', dir: 'ltr', className: 'sb-sc', style: { fontFamily: MONO, fontSize: '11.5px', lineHeight: 1.6, color: 'var(--sb-text-code)', wordBreak: 'break-all', padding: '11px 13px', maxHeight: '78px', overflowY: codeRevealed ? 'auto' : 'hidden', filter: codeRevealed ? 'none' : 'blur(5px)', userSelect: codeRevealed ? 'text' : 'none', transition: 'filter .25s' } }, credCode),
|
||||
!codeRevealed && h('span', { key: 'reveal', style: { position: 'absolute', inset: 0, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '8px', padding: '0 12px', textAlign: 'center', color: 'var(--sb-text-3)', fontSize: '12.5px', fontWeight: 600 } }, [
|
||||
fa('fa-eye', { key: 'i' }),
|
||||
t('cred.reveal')
|
||||
])
|
||||
])
|
||||
])
|
||||
]);
|
||||
|
||||
const showQrButton = qrCodeUrl && h('button', { key: 'showqr', onClick: () => setQrModalOpen(true), style: { width: '100%', display: 'flex', alignItems: 'center', gap: '13px', padding: '15px 16px', borderRadius: '14px', border: `1px solid ${isCreate ? 'rgba(var(--sb-orange-rgb), 0.3)' : 'rgba(var(--sb-green-rgb), 0.3)'}`, background: isCreate ? 'rgba(var(--sb-orange-rgb), 0.06)' : 'rgba(var(--sb-green-rgb), 0.06)', color: 'inherit', fontFamily: 'inherit', cursor: 'pointer', textAlign: 'start', marginBottom: '14px' } }, [
|
||||
h('span', { key: 'ic', style: { flex: 'none', width: '42px', height: '42px', borderRadius: '12px', display: 'grid', placeItems: 'center', background: isCreate ? 'rgba(var(--sb-orange-rgb), 0.12)' : 'rgba(var(--sb-green-rgb), 0.12)', border: `1px solid ${isCreate ? 'rgba(var(--sb-orange-rgb), 0.28)' : 'rgba(var(--sb-green-rgb), 0.28)'}` } }, fa('fa-qrcode', { color: accentSolid, fontSize: '18px' })),
|
||||
h('span', { key: 'tx', style: { flex: 1 } }, [
|
||||
h('span', { key: 't', style: { display: 'block', fontSize: '14.5px', fontWeight: 700, color: 'var(--sb-text-1)' } }, t('qr.showTitle')),
|
||||
h('span', { key: 's', style: { display: 'block', fontSize: '12.5px', color: 'var(--sb-text-7)', marginTop: '1px' } }, (qrFramesTotal || 0) > 1 ? t('qr.showSubtitleFrames', { frames: qrFramesTotal }) : t('qr.showSubtitle'))
|
||||
]),
|
||||
fa('fa-chevron-right', { color: 'var(--sb-text-9)' })
|
||||
]);
|
||||
|
||||
let inner;
|
||||
if (showVerification) {
|
||||
// Transcribed from the Claude Design component (SAS Verification.dc.html),
|
||||
// without its card: it sits straight in the setup column like the other steps.
|
||||
const verified = bothVerificationsConfirmed;
|
||||
const cells = (verificationCode || '').split('').map((ch, i) =>
|
||||
h('div', { key: i, style: { flex: 1, maxWidth: '46px', aspectRatio: '0.82', display: 'grid', placeItems: 'center', borderRadius: '10px', border: '1px solid rgba(var(--sb-green-rgb), 0.25)', background: 'rgba(var(--sb-green-rgb), 0.05)', fontFamily: MONO, fontSize: '22px', fontWeight: 700, color: C_GREEN } }, ch));
|
||||
const code = (verificationCode || '').replace(/[-\s]/g, '');
|
||||
const n = code.length;
|
||||
const typed = sasInput.replace(/[-\s]/g, '');
|
||||
const full = n > 0 && typed.length === n;
|
||||
// Comparing against the code already on this screen reveals nothing; it
|
||||
// only spares a typo one of the limited real attempts in onVerifyConnection.
|
||||
const match = full && typed === code;
|
||||
const wrong = full && !match;
|
||||
const youOk = localVerificationConfirmed;
|
||||
const peerOk = remoteVerificationConfirmed;
|
||||
const waiting = youOk && !peerOk;
|
||||
const split = Math.min(3, n);
|
||||
const confirmable = canConfirm && match;
|
||||
const check = (k) => h('svg', { key: k, width: 12, height: 12, viewBox: '0 0 24 24', fill: 'none', stroke: 'var(--sb-on-green-2)', strokeWidth: 3.2, strokeLinecap: 'round', strokeLinejoin: 'round' },
|
||||
h('path', { d: 'M5 12.5l4.2 4.2L19 7', strokeDasharray: 24, style: { animation: 'svDraw .35s ease-out .1s both' } }));
|
||||
const ring = (ok, soft) => ({
|
||||
position: 'relative', width: '22px', height: '22px', flex: 'none', borderRadius: '50%', display: 'grid', placeItems: 'center', transition: 'all .3s',
|
||||
border: `1.5px solid ${ok ? C_GREEN_SOLID : (soft ? 'rgba(var(--sb-green-rgb), 0.5)' : 'rgba(var(--sb-ink), 0.18)')}`,
|
||||
background: ok ? C_GREEN_SOLID : 'transparent'
|
||||
});
|
||||
const who = (k, label) => h('span', { key: k, style: { fontSize: '13px', fontWeight: 600, color: 'var(--sb-text-4)' } }, label);
|
||||
|
||||
const cells = code.split('').map((_, i) => {
|
||||
const v = typed[i] || '';
|
||||
const active = sasFocused && !youOk && i === typed.length && !full;
|
||||
let border = 'rgba(var(--sb-ink), 0.09)';
|
||||
let color = 'var(--sb-text-1)';
|
||||
let anim = 'none';
|
||||
if (v) { border = 'rgba(var(--sb-ink), 0.2)'; anim = 'otpPop .28s cubic-bezier(.2,.8,.3,1.2) both'; }
|
||||
if (active) border = 'rgba(var(--sb-orange-rgb), 0.65)';
|
||||
if (wrong) { border = 'rgba(var(--sb-red-rgb), 0.6)'; color = 'var(--sb-red)'; }
|
||||
if (match) anim = `otpOk .45s cubic-bezier(.2,.8,.3,1) ${i * 55}ms both`;
|
||||
return h('span', { key: i, style: {
|
||||
position: 'relative', flex: 1, minWidth: 0, height: '56px', display: 'grid', placeItems: 'center', borderRadius: '12px',
|
||||
border: `1px solid ${border}`, background: 'var(--sb-surface)', fontFamily: MONO, fontSize: '22px', fontWeight: 600, color,
|
||||
transition: 'border-color .15s, color .15s, box-shadow .15s', animation: anim,
|
||||
marginInlineStart: i === split && n > split ? '10px' : 0,
|
||||
boxShadow: active ? '0 0 0 3px rgba(var(--sb-orange-rgb), 0.12)' : 'none'
|
||||
} }, [
|
||||
v,
|
||||
active && h('span', { key: 'caret', style: { position: 'absolute', left: '50%', top: '50%', width: '2px', height: '22px', margin: '-11px 0 0 -1px', borderRadius: '1px', background: C_ORANGE_SOLID, animation: 'otpCaret 1s steps(1) infinite' } })
|
||||
]);
|
||||
});
|
||||
|
||||
inner = h('div', { key: 'verify', style: { animation: 'sbUp .3s ease' } }, [
|
||||
!verified && backButton('vback'),
|
||||
h('div', { key: 'head', style: { display: 'flex', alignItems: 'center', gap: '11px', marginBottom: '8px' } }, [
|
||||
h('div', { key: 'i', style: { width: '34px', height: '34px', flex: 'none', borderRadius: '10px', display: 'grid', placeItems: 'center', background: 'rgba(var(--sb-green-rgb), 0.1)', border: '1px solid rgba(var(--sb-green-rgb), 0.25)' } }, fa('fa-shield-alt', { color: C_GREEN_SOLID })),
|
||||
h('h2', { key: 't', style: { margin: 0, fontSize: '21px', fontWeight: 800, letterSpacing: '-0.4px', color: 'var(--sb-text-1)' } }, t('verify.title'))
|
||||
h('h2', { key: 't', style: { margin: '0 0 8px', fontSize: '22px', fontWeight: 800, letterSpacing: '-0.5px', color: 'var(--sb-text-1)' } }, verified ? t('sasv.titleDone') : t('sasv.title')),
|
||||
h('p', { key: 'sub', style: { margin: '0 0 24px', fontSize: '13.5px', lineHeight: 1.55, color: 'var(--sb-text-7)', textWrap: 'pretty' } }, verified ? t('sasv.descDone') : t('sasv.desc')),
|
||||
h('div', { key: 'code', style: { padding: '20px 0 22px', borderTop: '1px solid rgba(var(--sb-ink), 0.06)', borderBottom: '1px solid rgba(var(--sb-ink), 0.06)', marginBottom: '22px', textAlign: 'center' } },
|
||||
h('div', { dir: 'ltr', style: { display: 'inline-flex', alignItems: 'center', gap: '18px', fontFamily: MONO, fontSize: '34px', fontWeight: 600, letterSpacing: '8px', lineHeight: 1, color: verified ? C_GREEN : 'var(--sb-text-1)', transition: 'color .4s' } },
|
||||
n ? [h('span', { key: 'a' }, code.slice(0, split)), h('span', { key: 'b' }, code.slice(split))]
|
||||
: h('span', { style: { fontSize: '14px', letterSpacing: 0, color: 'var(--sb-text-8)' } }, t('verify.waiting')))),
|
||||
!verified && h('div', { key: 'form' }, [
|
||||
h('div', { key: 'lbl', style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '10px' } }, [
|
||||
h('span', { key: 'l', style: { fontSize: '12.5px', fontWeight: 600, color: 'var(--sb-text-6)' } }, t('sasv.enter')),
|
||||
h('span', { key: 'h', style: { fontFamily: MONO, fontSize: '11px', transition: 'color .2s', color: full ? (match ? C_GREEN : 'var(--sb-red)') : 'var(--sb-text-faint)' } },
|
||||
full ? (match ? t('sasv.match') : t('sasv.noMatch')) : `${typed.length} / ${n || SAS_CODE_LENGTH}`)
|
||||
]),
|
||||
h('label', { key: 'cells', dir: 'ltr', style: { position: 'relative', display: 'flex', gap: '6px', marginBottom: sasError ? '8px' : '18px', cursor: 'text', animation: wrong ? `otpShake .4s ease ${sasShake % 2 ? '0s' : '0.001s'}` : 'none' } }, [
|
||||
...cells,
|
||||
h('input', { key: 'in', dir: 'ltr', value: sasInput, disabled: youOk || !n, autoFocus: true, autoComplete: 'one-time-code', spellCheck: false, type: 'text', inputMode: 'numeric', pattern: '[0-9]*', maxLength: n || SAS_CODE_LENGTH, 'aria-label': t('sasv.enter'),
|
||||
onChange: (e) => {
|
||||
const v = e.target.value.replace(/\D/g, '').slice(0, n || SAS_CODE_LENGTH);
|
||||
if (n && v.length === n && v !== code) setSasShake((k) => k + 1);
|
||||
setSasInput(v);
|
||||
if (sasError) setSasError('');
|
||||
},
|
||||
onFocus: () => setSasFocused(true), onBlur: () => setSasFocused(false),
|
||||
style: { position: 'absolute', inset: 0, width: '100%', height: '100%', opacity: 0, border: 'none', outline: 'none', background: 'transparent', color: 'transparent', caretColor: 'transparent', fontSize: '16px' } })
|
||||
]),
|
||||
sasError && h('p', { key: 'err', style: { color: 'var(--sb-red)', fontSize: '12.5px', margin: '0 0 16px' } }, sasError)
|
||||
]),
|
||||
h('p', { key: 'sub', style: { margin: '0 0 18px', fontSize: '13.5px', lineHeight: 1.55, color: 'var(--sb-text-7)' } }, t('verify.desc')),
|
||||
h('div', { key: 'cells', dir: 'ltr', style: { display: 'flex', gap: '6px', justifyContent: 'center', marginBottom: '20px', flexWrap: 'wrap' } }, cells),
|
||||
verified
|
||||
? h('div', { key: 'ok', style: { display: 'flex', flexDirection: 'column', alignItems: 'center', textAlign: 'center', padding: '24px 16px', borderRadius: '16px', border: '1px solid rgba(var(--sb-green-rgb), 0.25)', background: 'rgba(var(--sb-green-rgb), 0.06)', animation: 'sbUp .3s ease' } }, [
|
||||
h('div', { key: 'i', style: { width: '54px', height: '54px', borderRadius: '16px', display: 'grid', placeItems: 'center', background: 'rgba(var(--sb-green-rgb), 0.14)', border: '1px solid rgba(var(--sb-green-rgb), 0.35)', marginBottom: '14px' } }, fa('fa-check', { color: C_GREEN_SOLID, fontSize: '24px' })),
|
||||
h('div', { key: 't', style: { fontSize: '18px', fontWeight: 800, color: 'var(--sb-text-1)' } }, t('verify.verified')),
|
||||
h('div', { key: 's', style: { fontSize: '13.5px', color: 'var(--sb-text-7)', marginTop: '5px' } }, t('verify.bothConfirmed'))
|
||||
h('div', { key: 'link', style: { display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '20px' } }, [
|
||||
h('div', { key: 'you', style: { display: 'flex', alignItems: 'center', gap: '9px', flex: 'none' } }, [
|
||||
h('span', { key: 'r', style: ring(youOk, false) }, youOk && check('c')),
|
||||
who('l', t('sasv.you'))
|
||||
]),
|
||||
h('div', { key: 'bar', style: { position: 'relative', flex: 1, height: '2px', borderRadius: '2px', background: 'rgba(var(--sb-ink), 0.08)', overflow: 'hidden' } }, [
|
||||
h('div', { key: 'fill', style: { position: 'absolute', insetInlineStart: 0, top: 0, bottom: 0, width: peerOk ? '100%' : (youOk ? '50%' : '0%'), background: C_GREEN_SOLID, transition: 'width .6s cubic-bezier(.4,0,.2,1)' } }),
|
||||
waiting && h('div', { key: 'scan', style: { position: 'absolute', top: 0, bottom: 0, width: '30%', background: 'linear-gradient(90deg, transparent, rgba(var(--sb-green-rgb), 0.9), transparent)', animation: 'svScan 1.1s ease-in-out infinite' } })
|
||||
]),
|
||||
h('div', { key: 'peer', style: { display: 'flex', alignItems: 'center', gap: '9px', flex: 'none' } }, [
|
||||
who('l', t('sasv.peer')),
|
||||
h('span', { key: 'r', style: ring(peerOk, youOk) }, peerOk && check('c'))
|
||||
])
|
||||
: h('div', { key: 'form' }, [
|
||||
h('div', { key: 'lbl', style: { fontSize: '12.5px', fontWeight: 600, color: 'var(--sb-text-6)', marginBottom: '8px' } }, t('verify.enterLabel')),
|
||||
h('input', { key: 'in', dir: 'ltr', value: sasInput, onChange: (e) => { setSasInput(e.target.value.replace(/\D/g, '').slice(0, SAS_CODE_LENGTH)); if (sasError) setSasError(''); }, disabled: localVerificationConfirmed, autoFocus: true, autoComplete: 'one-time-code', spellCheck: false, type: 'text', inputMode: 'numeric', pattern: '[0-9]*', maxLength: SAS_CODE_LENGTH, placeholder: verificationCode ? t('verify.placeholder') : t('verify.waiting'), style: { width: '100%', textAlign: 'center', letterSpacing: '6px', borderRadius: '12px', border: `1px solid ${sasInput.length ? (canConfirm || localVerificationConfirmed ? 'rgba(var(--sb-green-rgb), 0.5)' : 'rgba(var(--sb-ink), 0.14)') : 'rgba(var(--sb-ink), 0.08)'}`, background: 'var(--sb-surface)', color: 'var(--sb-text-1)', fontFamily: MONO, fontSize: '20px', fontWeight: 700, padding: '14px', outline: 'none', marginBottom: sasError ? '8px' : '16px' } }),
|
||||
sasError && h('p', { key: 'err', style: { color: 'var(--sb-red)', fontSize: '12.5px', margin: '0 0 16px' } }, sasError),
|
||||
h('div', { key: 'status', style: { display: 'flex', flexDirection: 'column', gap: '8px', marginBottom: '16px' } }, [
|
||||
h('div', { key: 'you', style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '11px 14px', borderRadius: '11px', border: '1px solid rgba(var(--sb-ink), 0.06)', background: 'var(--sb-surface)' } }, [
|
||||
h('span', { key: 'l', style: { fontSize: '13px', color: 'var(--sb-text-4)', fontWeight: 600 } }, t('verify.yours')),
|
||||
h('span', { key: 'v', style: { display: 'inline-flex', alignItems: 'center', gap: '6px', fontSize: '12.5px', fontWeight: 600, color: localVerificationConfirmed ? C_GREEN : 'var(--sb-text-8)' } }, [fa(localVerificationConfirmed ? 'fa-check-circle' : 'fa-clock', { key: 'i' }), localVerificationConfirmed ? t('verify.confirmed') : t('verify.pending')])
|
||||
]),
|
||||
h('div', { key: 'peer', style: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '11px 14px', borderRadius: '11px', border: '1px solid rgba(var(--sb-ink), 0.06)', background: 'var(--sb-surface)' } }, [
|
||||
h('span', { key: 'l', style: { fontSize: '13px', color: 'var(--sb-text-4)', fontWeight: 600 } }, t('verify.peer')),
|
||||
h('span', { key: 'v', style: { display: 'inline-flex', alignItems: 'center', gap: '6px', fontSize: '12.5px', fontWeight: 600, color: remoteVerificationConfirmed ? C_GREEN : 'var(--sb-text-8)' } }, [fa(remoteVerificationConfirmed ? 'fa-check-circle' : 'fa-clock', { key: 'i' }), remoteVerificationConfirmed ? t('verify.confirmed') : t('verify.pending')])
|
||||
])
|
||||
]),
|
||||
h('div', { key: 'btns', style: { display: 'flex', gap: '10px' } }, [
|
||||
h('button', { key: 'ok', onClick: handleSasConfirm, disabled: !canConfirm, style: { flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '8px', padding: '14px', borderRadius: '13px', border: 'none', background: canConfirm ? C_GREEN_SOLID : 'rgba(var(--sb-ink), 0.05)', color: canConfirm ? 'var(--sb-on-green-2)' : 'var(--sb-text-faint)', fontFamily: 'inherit', fontSize: '14.5px', fontWeight: 700, cursor: canConfirm ? 'pointer' : 'not-allowed', boxShadow: canConfirm ? '0 8px 24px rgba(var(--sb-green-rgb), 0.25)' : 'none' } }, [fa(localVerificationConfirmed ? 'fa-check-circle' : 'fa-check', { key: 'i' }), localVerificationConfirmed ? t('verify.confirmed') : t('verify.confirm')]),
|
||||
h('button', { key: 'no', onClick: handleVerificationReject, style: { flex: 'none', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '7px', padding: '14px 16px', borderRadius: '13px', border: '1px solid rgba(var(--sb-red-rgb), 0.3)', background: 'transparent', color: 'var(--sb-red)', fontFamily: 'inherit', fontSize: '13.5px', fontWeight: 600, cursor: 'pointer' } }, [fa('fa-times', { key: 'i' }), t('verify.mismatch')])
|
||||
])
|
||||
]),
|
||||
!verified
|
||||
? h('div', { key: 'btns' }, [
|
||||
h('button', { key: 'ok', onClick: handleSasConfirm, disabled: !confirmable, style: { width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '8px', padding: '14px', borderRadius: '13px', border: 'none', fontFamily: 'inherit', fontSize: '14.5px', fontWeight: 700, transition: 'background .25s, color .25s',
|
||||
background: youOk ? 'rgba(var(--sb-green-rgb), 0.12)' : (confirmable ? C_GREEN_SOLID : 'rgba(var(--sb-ink), 0.05)'),
|
||||
color: youOk ? C_GREEN : (confirmable ? 'var(--sb-on-green-2)' : 'var(--sb-text-faint)'),
|
||||
cursor: confirmable ? 'pointer' : 'default' } }, youOk ? t('sasv.waitingPeer') : t('verify.confirm')),
|
||||
h('button', { key: 'no', onClick: handleVerificationReject, className: 'sb-sas-mismatch', style: { display: 'flex', alignItems: 'center', justifyContent: 'center', width: '100%', marginTop: '10px', padding: '8px', border: 'none', background: 'transparent', color: 'var(--sb-text-7)', fontFamily: 'inherit', fontSize: '13px', fontWeight: 600, cursor: 'pointer', transition: 'color .15s' },
|
||||
onMouseEnter: (e) => { e.currentTarget.style.color = 'var(--sb-red)'; }, onMouseLeave: (e) => { e.currentTarget.style.color = 'var(--sb-text-7)'; } }, t('sasv.codesDiffer'))
|
||||
])
|
||||
: h('div', { key: 'opening', style: { animation: 'svFade .35s ease .3s both' } },
|
||||
h('div', { style: { display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '10px', padding: '14px 0 4px', fontSize: '13.5px', fontWeight: 600, color: 'var(--sb-text-4)' } }, [
|
||||
t('sasv.opening'),
|
||||
h('span', { key: 'd', style: { display: 'inline-flex', gap: '4px' } }, [0, 0.15, 0.3].map((d, i) =>
|
||||
h('span', { key: i, style: { width: '4px', height: '4px', borderRadius: '50%', background: C_GREEN_SOLID, animation: `svDot 1.2s ease-in-out ${d}s infinite` } })))
|
||||
]))
|
||||
]);
|
||||
} else if (isGenerating) {
|
||||
const genSteps = [t('handshake.step1'), t('handshake.step2'), t('handshake.step3')];
|
||||
@@ -1643,22 +1736,24 @@ import { GroupCallMedia, mediaErrorCode } from './group/groupCallMedia.js';
|
||||
} else if (isOfferCred || isAnswerCred) {
|
||||
inner = h('div', { key: 'cred', style: { animation: 'sbUp .3s ease' } }, [
|
||||
backButton('cback'),
|
||||
h('h2', { key: 'h', style: { margin: '0 0 6px', fontSize: '23px', fontWeight: 800, letterSpacing: '-0.5px', color: 'var(--sb-text-1)' } }, isCreate ? t('handshake.shareTitle') : t('handshake.sendAnswerTitle')),
|
||||
h('p', { key: 'p', style: { margin: '0 0 18px', fontSize: '14px', lineHeight: 1.55, color: 'var(--sb-text-7)' } }, isCreate ? t('handshake.shareDesc') : t('handshake.sendAnswerDesc')),
|
||||
showQrButton,
|
||||
credBlock,
|
||||
isOfferCred && h('div', { key: 'offerextra', style: { marginTop: '4px' } }, [
|
||||
h('div', { key: 'lbl', style: { fontSize: '12.5px', fontWeight: 600, color: 'var(--sb-text-6)', marginBottom: '8px' } }, t('handshake.thenReceive')),
|
||||
h('div', { key: 'ta', style: { borderRadius: '12px', border: `1px solid ${hasAnswer ? 'rgba(var(--sb-ink), 0.18)' : 'rgba(var(--sb-ink), 0.07)'}`, background: 'var(--sb-surface)', padding: '11px 14px', marginBottom: '10px' } },
|
||||
h('textarea', { dir: 'ltr', value: answerInput, onChange: (e) => { setAnswerInput(e.target.value); if (e.target.value.trim().length > 0 && typeof markAnswerCreated === 'function') markAnswerCreated(); }, rows: 2, placeholder: t('handshake.pasteAnswerPlaceholder'), style: { width: '100%', resize: 'none', border: 'none', outline: 'none', background: 'transparent', color: 'var(--sb-text-3)', fontFamily: MONO, fontSize: '12px', lineHeight: 1.55, minHeight: '44px' } })),
|
||||
h('h2', { key: 'h', style: { margin: '0 0 8px', fontSize: '26px', fontWeight: 800, letterSpacing: '-0.7px', color: 'var(--sb-text-1)' } }, isCreate ? t('handshake.shareTitle') : t('handshake.sendAnswerTitle')),
|
||||
h('p', { key: 'p', style: { margin: '0 0 24px', fontSize: '14px', lineHeight: 1.55, color: 'var(--sb-text-7)' } }, isCreate ? t('handshake.shareDesc') : t('handshake.sendAnswerDesc')),
|
||||
credCard,
|
||||
isOfferCred && h('div', { key: 'offerextra' }, [
|
||||
h('div', { key: 'lbl', style: { fontSize: '13.5px', fontWeight: 600, color: 'var(--sb-text-3)', marginBottom: '10px' } }, t('handshake.thenReceive')),
|
||||
h('textarea', { key: 'ta', dir: 'ltr', value: answerInput, onChange: (e) => { setAnswerInput(e.target.value); if (e.target.value.trim().length > 0 && typeof markAnswerCreated === 'function') markAnswerCreated(); }, rows: 3, placeholder: t('handshake.pasteAnswerPlaceholder'), style: { display: 'block', width: '100%', resize: 'none', borderRadius: '14px', border: `1px solid ${hasAnswer ? 'rgba(var(--sb-orange-rgb), 0.45)' : 'rgba(var(--sb-ink), 0.08)'}`, background: 'var(--sb-surface)', outline: 'none', color: 'var(--sb-text-2)', fontFamily: MONO, fontSize: '12.5px', lineHeight: 1.5, padding: '13px 15px', marginBottom: '12px', transition: 'border-color .15s' } }),
|
||||
h('div', { key: 'btns', style: { display: 'flex', gap: '10px' } }, [
|
||||
h('button', { key: 'scan', className: 'sb-scan-btn', onClick: () => setShowQRScannerModal(true), style: { flex: 'none', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: '8px', padding: '14px 16px', borderRadius: '13px', border: '1px solid rgba(var(--sb-ink), 0.1)', background: 'rgba(var(--sb-ink), 0.04)', color: 'var(--sb-text-4)', fontFamily: 'inherit', fontSize: '14px', fontWeight: 700, cursor: 'pointer' } }, [fa('fa-camera', { key: 'i' }), t('action.scan')]),
|
||||
h('button', { key: 'est', onClick: onConnect, disabled: !hasAnswer, style: { flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '9px', padding: '14px', borderRadius: '13px', border: 'none', background: hasAnswer ? C_ORANGE_SOLID : 'rgba(var(--sb-ink), 0.05)', color: hasAnswer ? 'var(--sb-on-accent)' : 'var(--sb-text-faint)', fontFamily: 'inherit', fontSize: '14.5px', fontWeight: 700, cursor: hasAnswer ? 'pointer' : 'not-allowed', boxShadow: hasAnswer ? '0 8px 24px rgba(var(--sb-orange-rgb), 0.28)' : 'none' } }, t('handshake.establish'))
|
||||
h('button', { key: 'scan', className: 'sb-scan-btn', onClick: () => setShowQRScannerModal(true), style: { flex: 'none', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: '8px', padding: '0 18px', height: '50px', borderRadius: '13px', border: '1px solid rgba(var(--sb-ink), 0.1)', background: 'transparent', color: 'var(--sb-text-2)', fontFamily: 'inherit', fontSize: '14px', fontWeight: 700, cursor: 'pointer' } }, [
|
||||
h('svg', { key: 'i', width: 16, height: 16, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': true },
|
||||
h('path', { d: 'M4 8V5a1 1 0 0 1 1-1h3M16 4h3a1 1 0 0 1 1 1v3M20 16v3a1 1 0 0 1-1 1h-3M8 20H5a1 1 0 0 1-1-1v-3M7 12h10' })),
|
||||
t('action.scan')
|
||||
]),
|
||||
h('button', { key: 'est', onClick: onConnect, disabled: !hasAnswer, style: { flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '9px', height: '50px', borderRadius: '13px', border: 'none', background: hasAnswer ? C_ORANGE_SOLID : 'rgba(var(--sb-ink), 0.05)', color: hasAnswer ? 'var(--sb-on-accent)' : 'var(--sb-text-faint)', fontFamily: 'inherit', fontSize: '14.5px', fontWeight: 700, cursor: hasAnswer ? 'pointer' : 'not-allowed', transition: 'background .2s, color .2s' } }, t('handshake.establish'))
|
||||
])
|
||||
]),
|
||||
isAnswerCred && h('div', { key: 'answerextra', style: { marginTop: '4px', display: 'flex', alignItems: 'center', gap: '10px', padding: '12px 14px', borderRadius: '12px', border: '1px solid rgba(var(--sb-green-rgb), 0.18)', background: 'rgba(var(--sb-green-rgb), 0.05)' } }, [
|
||||
fa('fa-circle-notch', { key: 'i', color: C_GREEN_SOLID, animation: 'sbSpin 1.4s linear infinite' }),
|
||||
h('span', { key: 't', style: { fontSize: '13px', color: 'var(--sb-text-4)', fontWeight: 500 } }, t('handshake.answerSentNote'))
|
||||
isAnswerCred && h('div', { key: 'answerextra', style: { display: 'flex', alignItems: 'center', gap: '12px', padding: '4px 2px' } }, [
|
||||
h('span', { key: 'i', style: { flex: 'none', width: '16px', height: '16px', borderRadius: '50%', border: '2px solid rgba(var(--sb-ink), 0.1)', borderTopColor: C_ORANGE_SOLID, animation: 'sbSpin .9s linear infinite' } }),
|
||||
h('span', { key: 't', style: { fontSize: '13px', lineHeight: 1.5, color: 'var(--sb-text-7)' } }, t('handshake.answerSentNote'))
|
||||
])
|
||||
]);
|
||||
} else if (isCreate) {
|
||||
@@ -1669,26 +1764,30 @@ import { GroupCallMedia, mediaErrorCode } from './group/groupCallMedia.js';
|
||||
h('button', { key: 'gen', className: 'sb-gen-btn', onClick: () => { requestNotificationPermissionOnInteraction(); if (webrtcManagerRef.current) handleCreateOffer(); }, style: { width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '9px', padding: '15px', borderRadius: '13px', border: 'none', background: C_ORANGE_SOLID, color: 'var(--sb-on-accent)', fontFamily: 'inherit', fontSize: '15px', fontWeight: 700, cursor: 'pointer', boxShadow: '0 8px 24px rgba(var(--sb-orange-rgb), 0.28)' } }, [fa('fa-bolt', { key: 'i' }), t('intro.createCta')])
|
||||
]);
|
||||
} else {
|
||||
// JOIN intro
|
||||
// JOIN intro — same card language as the invitation/answer screens:
|
||||
// scan row on top, the paste field below, one bordered surface.
|
||||
const canJoin = hasInvite && connectionStatus !== 'connecting';
|
||||
inner = h('div', { key: 'introJ', style: { animation: 'sbUp .28s ease' } }, [
|
||||
h('h2', { key: 'h', style: { margin: '0 0 6px', fontSize: '23px', fontWeight: 800, letterSpacing: '-0.5px', color: 'var(--sb-text-1)' } }, t('intro.joinTitle')),
|
||||
h('p', { key: 'p', style: { margin: '0 0 16px', fontSize: '14px', lineHeight: 1.55, color: 'var(--sb-text-7)' } }, "Scan your peer's QR with your camera, or paste their invitation code."),
|
||||
h('button', { key: 'scan', className: 'sb-scan-btn', onClick: () => { requestNotificationPermissionOnInteraction(); setShowQRScannerModal(true); }, style: { width: '100%', display: 'flex', alignItems: 'center', gap: '13px', padding: '15px 16px', borderRadius: '14px', border: '1px solid rgba(var(--sb-green-rgb), 0.3)', background: 'rgba(var(--sb-green-rgb), 0.06)', color: 'inherit', fontFamily: 'inherit', cursor: 'pointer', textAlign: 'start', marginBottom: '14px' } }, [
|
||||
h('span', { key: 'ic', style: { flex: 'none', width: '42px', height: '42px', borderRadius: '12px', display: 'grid', placeItems: 'center', background: 'rgba(var(--sb-green-rgb), 0.12)', border: '1px solid rgba(var(--sb-green-rgb), 0.28)' } }, fa('fa-camera', { color: C_GREEN_SOLID, fontSize: '18px' })),
|
||||
h('span', { key: 'tx', style: { flex: 1 } }, [
|
||||
h('span', { key: 't', style: { display: 'block', fontSize: '14.5px', fontWeight: 700, color: 'var(--sb-text-1)' } }, t('intro.scanTitle')),
|
||||
h('span', { key: 's', style: { display: 'block', fontSize: '12.5px', color: 'var(--sb-text-7)', marginTop: '1px' } }, t('intro.scanSubtitle'))
|
||||
h('h2', { key: 'h', style: { margin: '0 0 8px', fontSize: '26px', fontWeight: 800, letterSpacing: '-0.7px', color: 'var(--sb-text-1)' } }, t('intro.joinTitle')),
|
||||
h('p', { key: 'p', style: { margin: '0 0 24px', fontSize: '14px', lineHeight: 1.55, color: 'var(--sb-text-7)' } }, t('intro.joinDesc')),
|
||||
h('div', { key: 'card', style: { borderRadius: '16px', border: '1px solid rgba(var(--sb-ink), 0.07)', background: 'var(--sb-surface)', overflow: 'hidden', marginBottom: '12px' } }, [
|
||||
h('button', { key: 'scan', className: 'sb-qr-row', onClick: () => { requestNotificationPermissionOnInteraction(); setShowQRScannerModal(true); }, style: { width: '100%', display: 'flex', alignItems: 'center', gap: '14px', padding: '14px 16px', border: 'none', background: 'transparent', color: 'inherit', fontFamily: 'inherit', cursor: 'pointer', textAlign: 'start', transition: 'background .15s' } }, [
|
||||
h('span', { key: 'ic', style: { flex: 'none', width: '48px', height: '48px', borderRadius: '9px', display: 'grid', placeItems: 'center', background: 'rgba(var(--sb-ink), 0.05)', border: '1px solid rgba(var(--sb-ink), 0.08)', color: 'var(--sb-text-2)' } },
|
||||
h('svg', { width: 22, height: 22, viewBox: '0 0 24 24', fill: 'none', stroke: 'currentColor', strokeWidth: 2, strokeLinecap: 'round', strokeLinejoin: 'round', 'aria-hidden': true },
|
||||
h('path', { d: 'M4 8V5a1 1 0 0 1 1-1h3M16 4h3a1 1 0 0 1 1 1v3M20 16v3a1 1 0 0 1-1 1h-3M8 20H5a1 1 0 0 1-1-1v-3M7 12h10' }))),
|
||||
h('span', { key: 'tx', style: { flex: 1, minWidth: 0 } }, [
|
||||
h('span', { key: 't', style: { display: 'block', fontSize: '15px', fontWeight: 700, color: 'var(--sb-text-1)' } }, t('intro.scanTitle')),
|
||||
h('span', { key: 's', style: { display: 'block', fontSize: '12.5px', color: 'var(--sb-text-7)', marginTop: '2px' } }, t('intro.scanSubtitle'))
|
||||
]),
|
||||
fa('fa-chevron-right', { color: 'var(--sb-text-9)' })
|
||||
]),
|
||||
fa('fa-chevron-right', { color: 'var(--sb-text-9)' })
|
||||
h('div', { key: 'div', style: { height: '1px', background: 'rgba(var(--sb-ink), 0.06)' } }),
|
||||
h('div', { key: 'paste', style: { padding: '12px 16px 16px' } }, [
|
||||
h('div', { key: 'l', style: { fontSize: '12.5px', fontWeight: 600, color: 'var(--sb-text-6)', marginBottom: '10px' } }, t('intro.orPasteCode')),
|
||||
h('textarea', { key: 'ta', dir: 'ltr', value: offerInput, onChange: (e) => { setOfferInput(e.target.value); if (e.target.value.trim().length > 0 && typeof markAnswerCreated === 'function') markAnswerCreated(); }, rows: 3, placeholder: t('intro.pastePlaceholder'), style: { display: 'block', width: '100%', resize: 'none', borderRadius: '11px', border: `1px solid ${hasInvite ? 'rgba(var(--sb-orange-rgb), 0.45)' : 'rgba(var(--sb-ink), 0.05)'}`, background: 'var(--sb-bg)', outline: 'none', color: 'var(--sb-text-2)', fontFamily: MONO, fontSize: '12.5px', lineHeight: 1.5, padding: '11px 13px', transition: 'border-color .15s' } })
|
||||
])
|
||||
]),
|
||||
h('div', { key: 'or', style: { display: 'flex', alignItems: 'center', gap: '12px', marginBottom: '14px' } }, [
|
||||
h('span', { key: 'a', style: { flex: 1, height: '1px', background: 'rgba(var(--sb-ink), 0.07)' } }),
|
||||
h('span', { key: 'm', style: { fontSize: '11px', fontWeight: 600, color: 'var(--sb-text-faint)', textTransform: 'uppercase', letterSpacing: '0.7px' } }, t('intro.orPasteCode')),
|
||||
h('span', { key: 'b', style: { flex: 1, height: '1px', background: 'rgba(var(--sb-ink), 0.07)' } })
|
||||
]),
|
||||
h('div', { key: 'ta', style: { borderRadius: '13px', border: `1px solid ${hasInvite ? 'rgba(var(--sb-ink), 0.18)' : 'rgba(var(--sb-ink), 0.07)'}`, background: 'var(--sb-surface)', padding: '13px 15px', marginBottom: '12px' } },
|
||||
h('textarea', { dir: 'ltr', value: offerInput, onChange: (e) => { setOfferInput(e.target.value); if (e.target.value.trim().length > 0 && typeof markAnswerCreated === 'function') markAnswerCreated(); }, rows: 3, placeholder: t('intro.pastePlaceholder'), style: { width: '100%', resize: 'none', border: 'none', outline: 'none', background: 'transparent', color: 'var(--sb-text-3)', fontFamily: MONO, fontSize: '12.5px', lineHeight: 1.6, minHeight: '66px' } })),
|
||||
h('button', { key: 'connect', onClick: () => { requestNotificationPermissionOnInteraction(); onCreateAnswer(); }, disabled: !hasInvite || connectionStatus === 'connecting', style: { width: '100%', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: '9px', padding: '14px', borderRadius: '13px', border: 'none', background: (hasInvite && connectionStatus !== 'connecting') ? C_ORANGE_SOLID : 'rgba(var(--sb-ink), 0.05)', color: (hasInvite && connectionStatus !== 'connecting') ? 'var(--sb-on-accent)' : 'var(--sb-text-faint)', fontFamily: 'inherit', fontSize: '15px', fontWeight: 700, cursor: (hasInvite && connectionStatus !== 'connecting') ? 'pointer' : 'not-allowed', boxShadow: (hasInvite && connectionStatus !== 'connecting') ? '0 8px 24px rgba(var(--sb-orange-rgb), 0.28)' : 'none' } }, connectionStatus === 'connecting' ? t('intro.connecting') : t('intro.connect'))
|
||||
h('button', { key: 'connect', onClick: () => { requestNotificationPermissionOnInteraction(); onCreateAnswer(); }, disabled: !canJoin, style: { width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '9px', height: '50px', borderRadius: '13px', border: 'none', background: canJoin ? C_ORANGE_SOLID : 'rgba(var(--sb-ink), 0.05)', color: canJoin ? 'var(--sb-on-accent)' : 'var(--sb-text-faint)', fontFamily: 'inherit', fontSize: '14.5px', fontWeight: 700, cursor: canJoin ? 'pointer' : 'not-allowed', transition: 'background .2s, color .2s' } }, connectionStatus === 'connecting' ? t('intro.connecting') : t('intro.connect'))
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -1704,7 +1803,7 @@ import { GroupCallMedia, mediaErrorCode } from './group/groupCallMedia.js';
|
||||
// /download/v0.3.0/SecureBit.Chat_0.1.0_x64-setup.exe, a file that never
|
||||
// existed, and the download 404s. A pinned tag keeps serving a real
|
||||
// installer instead.
|
||||
const SB_DESKTOP_VERSION = '1.0.4';
|
||||
const SB_DESKTOP_VERSION = '1.0.5';
|
||||
const SB_DESKTOP_RELEASE = `https://github.com/SecureBitChat/securebit-desktop/releases/download/v${SB_DESKTOP_VERSION}`;
|
||||
const DOWNLOADS = {
|
||||
mac: { name: 'macOS', format: '.dmg · Apple Silicon & Intel', icon: 'fab fa-apple', url: `${SB_DESKTOP_RELEASE}/SecureBit.Chat_${SB_DESKTOP_VERSION}_x64.dmg` },
|
||||
@@ -1847,6 +1946,15 @@ import { GroupCallMedia, mediaErrorCode } from './group/groupCallMedia.js';
|
||||
'@keyframes sbNode{0%,100%{box-shadow:0 0 0 0 rgba(var(--sb-green-rgb), 0)}50%{box-shadow:0 0 0 6px rgba(var(--sb-green-rgb), .06)}}' +
|
||||
'@keyframes sbScan{0%{top:8%}100%{top:88%}}' +
|
||||
'@keyframes sbBlink{0%,100%{opacity:1}50%{opacity:.35}}' +
|
||||
// SAS verification step (SAS Verification.dc.html).
|
||||
'@keyframes otpPop{0%{transform:translateY(6px) scale(.9);opacity:0}60%{transform:translateY(-1px) scale(1.04);opacity:1}100%{transform:none;opacity:1}}' +
|
||||
'@keyframes otpOk{0%{transform:none}45%{transform:translateY(-4px)}100%{transform:none;background:rgba(var(--sb-green-rgb),.07);border-color:rgba(var(--sb-green-rgb),.55);color:var(--sb-green)}}' +
|
||||
'@keyframes otpShake{0%,100%{transform:translateX(0)}20%{transform:translateX(-7px)}40%{transform:translateX(6px)}60%{transform:translateX(-4px)}80%{transform:translateX(2px)}}' +
|
||||
'@keyframes otpCaret{0%,100%{opacity:1}50%{opacity:0}}' +
|
||||
'@keyframes svDot{0%,80%,100%{opacity:.25;transform:translateY(0)}40%{opacity:1;transform:translateY(-3px)}}' +
|
||||
'@keyframes svDraw{from{stroke-dashoffset:24}to{stroke-dashoffset:0}}' +
|
||||
'@keyframes svScan{from{left:-30%}to{left:100%}}' +
|
||||
'@keyframes svFade{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}' +
|
||||
// P2P / mesh animation (left panel). One 14s loop: the direct line is
|
||||
// already up, then two peers join it. `sbTrav` moves a dot along the
|
||||
// same geometry the line is drawn from, via offset-path, so the packet
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
// The tag is pinned rather than using /releases/latest/download/: the filenames
|
||||
// carry the version, so a `latest` link 404s the moment a newer release exists,
|
||||
// whereas a pinned tag keeps serving a working (if older) installer.
|
||||
const DESKTOP_VERSION = '1.0.4';
|
||||
const DESKTOP_VERSION = '1.0.5';
|
||||
const DESKTOP_RELEASE = `https://github.com/SecureBitChat/securebit-desktop/releases/download/v${DESKTOP_VERSION}`;
|
||||
|
||||
const DownloadApps = () => {
|
||||
|
||||
+16
-12
@@ -93,9 +93,10 @@ export const DICTIONARY = {
|
||||
"intro.createDesc": "يولّد جهازك المفاتيح ودعوة تُستعمل مرّة واحدة. لا شيء يمرّ عبر خادم.",
|
||||
"intro.createCta": "توليد المفاتيح والدعوة",
|
||||
"intro.joinTitle": "انضمّ إلى قناة",
|
||||
"intro.joinDesc": "امسح رمز QR الخاص بالطرف الآخر بالكاميرا، أو الصق رمز الدعوة.",
|
||||
"intro.scanTitle": "امسح رمز QR بالكاميرا",
|
||||
"intro.scanSubtitle": "الأسرع - وجّهها إلى شاشة الطرف الآخر",
|
||||
"intro.orPasteCode": "أو ألصق الرمز",
|
||||
"intro.orPasteCode": "أو الصق الرمز",
|
||||
"intro.pastePlaceholder": "ألصق رمز الدعوة هنا…",
|
||||
"intro.connect": "اتّصل",
|
||||
"intro.connecting": "جارٍ المعالجة…",
|
||||
@@ -271,22 +272,26 @@ export const DICTIONARY = {
|
||||
"handshake.establish": "إنشاء الاتصال",
|
||||
"handshake.thenReceive": "ثمّ استقبل الردّ الذي يرسله الطرف الآخر",
|
||||
"handshake.pasteAnswerPlaceholder": "ألصق رمز ردّ الطرف الآخر…",
|
||||
"handshake.answerSentNote": "أرسل هذا الردّ إلى المُنشئ ثمّ انتظر - تُفتح الدردشة حالما يتّصل.",
|
||||
"handshake.answerSentNote": "بانتظار اتصال المُنشئ. ستُفتح المحادثة تلقائياً.",
|
||||
"handshake.qrHint": "أبقِ هذا مفتوحاً حتى يلتقط الطرف الآخر الرمز.",
|
||||
"handshake.qrHintFrames": "المصافحة موزّعة على {frames} إطارات - أبقِ هذا مفتوحاً حتى يلتقطها الطرف الآخر كلّها.",
|
||||
"verify.title": "التحقّق الأمني",
|
||||
"verify.desc": "قارِن رمز الأمان هذا مع الطرف الآخر عبر قناة منفصلة (مكالمة أو لقاء مباشر)، ثمّ اكتبه لفتح الدردشة.",
|
||||
"verify.enterLabel": "أدخل الرمز المُتحقَّق منه",
|
||||
"verify.placeholder": "اكتب الرمز هنا",
|
||||
"verify.confirm": "تأكيد الرمز",
|
||||
"verify.confirmed": "مؤكَّد",
|
||||
"verify.mismatch": "غير متطابقين",
|
||||
"verify.yours": "تأكيدك",
|
||||
"verify.peer": "تأكيد الطرف الآخر",
|
||||
"verify.pending": "قيد الانتظار",
|
||||
"verify.waiting": "بانتظار الرمز…",
|
||||
"verify.verified": "تمّ التحقّق من القناة",
|
||||
"verify.bothConfirmed": "أكّد الطرفان. جارٍ فتح الدردشة الآمنة…",
|
||||
"sasv.title": "تحقّق من الطرف الآخر",
|
||||
"sasv.titleDone": "تم التحقّق من الطرف الآخر",
|
||||
"sasv.desc": "اقرأ الرمز بصوت عالٍ عبر مكالمة أو وجهًا لوجه. لا تتابع إلا إذا رأى الطرف الآخر الأرقام نفسها.",
|
||||
"sasv.descDone": "أكّدتما كلاكما الرمز نفسه. الاتصال موثَّق.",
|
||||
"sasv.enter": "أدخل الرمز",
|
||||
"sasv.match": "متطابق",
|
||||
"sasv.noMatch": "غير متطابق",
|
||||
"sasv.you": "أنت",
|
||||
"sasv.peer": "الطرف الآخر",
|
||||
"sasv.waitingPeer": "بانتظار الطرف الآخر…",
|
||||
"sasv.codesDiffer": "الرموز غير متطابقة",
|
||||
"sasv.opening": "جارٍ فتح المحادثة الآمنة",
|
||||
"pwa.bannerTitle": "ثبّت SecureBit.chat",
|
||||
"pwa.bannerDesc": "احصل على تجربة تطبيق أصلي بأمان أعلى",
|
||||
"pwa.install": "تثبيت",
|
||||
@@ -321,9 +326,8 @@ export const DICTIONARY = {
|
||||
"action.scan": "مسح",
|
||||
"action.downloadDesktop": "نزّل تطبيق سطح المكتب",
|
||||
"action.advancedSettings": "إعدادات متقدّمة",
|
||||
"cred.offerTag": "عرض · أو انسخ النصّ",
|
||||
"cred.answerTag": "ردّ · أو انسخ النصّ",
|
||||
"cred.reveal": "اضغط للكشف - أبقِ هذا الرمز سرّياً",
|
||||
"cred.orCopy": "أو انسخ الرمز",
|
||||
"qr.title": "امسح رمز QR",
|
||||
"qr.subtitle": "وجّه كاميرتك إلى رمزهم",
|
||||
"qr.scanning": "جارٍ المسح…",
|
||||
|
||||
+16
-12
@@ -93,9 +93,10 @@ export const DICTIONARY = {
|
||||
"intro.createDesc": "Ihr Gerät erzeugt die Schlüssel und eine einmalige Einladung. Nichts davon berührt einen Server.",
|
||||
"intro.createCta": "Schlüssel & Einladung erzeugen",
|
||||
"intro.joinTitle": "Kanal beitreten",
|
||||
"intro.joinDesc": "Scannen Sie den QR-Code Ihres Gegenübers mit der Kamera oder fügen Sie seinen Einladungscode ein.",
|
||||
"intro.scanTitle": "QR-Code mit der Kamera scannen",
|
||||
"intro.scanSubtitle": "Am schnellsten - auf den Bildschirm Ihres Gegenübers halten",
|
||||
"intro.orPasteCode": "oder Code einfügen",
|
||||
"intro.orPasteCode": "Oder den Code einfügen",
|
||||
"intro.pastePlaceholder": "Einladungscode hier einfügen …",
|
||||
"intro.connect": "Verbinden",
|
||||
"intro.connecting": "Wird verarbeitet …",
|
||||
@@ -271,22 +272,26 @@ export const DICTIONARY = {
|
||||
"handshake.establish": "Verbindung herstellen",
|
||||
"handshake.thenReceive": "Nehmen Sie danach die Antwort Ihres Gegenübers entgegen",
|
||||
"handshake.pasteAnswerPlaceholder": "Antwortcode des Gegenübers einfügen …",
|
||||
"handshake.answerSentNote": "Senden Sie diese Antwort an den Ersteller und warten Sie - der Chat öffnet sich, sobald er sich verbindet.",
|
||||
"handshake.answerSentNote": "Warten, bis sich der Ersteller verbindet. Der Chat öffnet sich automatisch.",
|
||||
"handshake.qrHint": "Lassen Sie dies geöffnet, bis Ihr Gegenüber den Code erfasst hat.",
|
||||
"handshake.qrHintFrames": "Der Handschlag ist auf {frames} Einzelbilder verteilt - lassen Sie dies geöffnet, bis Ihr Gegenüber alle erfasst hat.",
|
||||
"verify.title": "Sicherheitsprüfung",
|
||||
"verify.desc": "Vergleichen Sie diesen Sicherheitscode über einen anderen Weg mit Ihrem Gegenüber (Anruf oder persönlich) und geben Sie ihn dann ein, um den Chat freizuschalten.",
|
||||
"verify.enterLabel": "Geprüften Code eingeben",
|
||||
"verify.placeholder": "Code hier eingeben",
|
||||
"verify.confirm": "Code bestätigen",
|
||||
"verify.confirmed": "Bestätigt",
|
||||
"verify.mismatch": "Stimmt nicht überein",
|
||||
"verify.yours": "Ihre Bestätigung",
|
||||
"verify.peer": "Bestätigung des Gegenübers",
|
||||
"verify.pending": "Ausstehend",
|
||||
"verify.waiting": "Warten auf den Code …",
|
||||
"verify.verified": "Kanal geprüft",
|
||||
"verify.bothConfirmed": "Beide Seiten haben bestätigt. Der sichere Chat wird geöffnet …",
|
||||
"sasv.title": "Gegenüber überprüfen",
|
||||
"sasv.titleDone": "Gegenüber verifiziert",
|
||||
"sasv.desc": "Lies den Code bei einem Anruf oder persönlich laut vor. Fahre nur fort, wenn dein Gegenüber dieselben Ziffern sieht.",
|
||||
"sasv.descDone": "Ihr habt beide denselben Code bestätigt. Die Verbindung ist authentifiziert.",
|
||||
"sasv.enter": "Code eingeben",
|
||||
"sasv.match": "Stimmt überein",
|
||||
"sasv.noMatch": "Stimmt nicht überein",
|
||||
"sasv.you": "Du",
|
||||
"sasv.peer": "Gegenüber",
|
||||
"sasv.waitingPeer": "Warte auf Gegenüber…",
|
||||
"sasv.codesDiffer": "Codes stimmen nicht überein",
|
||||
"sasv.opening": "Sicherer Chat wird geöffnet",
|
||||
"pwa.bannerTitle": "SecureBit.chat installieren",
|
||||
"pwa.bannerDesc": "Die App-Erfahrung mit zusätzlicher Sicherheit",
|
||||
"pwa.install": "Installieren",
|
||||
@@ -321,9 +326,8 @@ export const DICTIONARY = {
|
||||
"action.scan": "Scannen",
|
||||
"action.downloadDesktop": "Desktop-App laden",
|
||||
"action.advancedSettings": "Erweiterte Einstellungen",
|
||||
"cred.offerTag": "Angebot · oder Text kopieren",
|
||||
"cred.answerTag": "Antwort · oder Text kopieren",
|
||||
"cred.reveal": "Zum Anzeigen klicken - halten Sie diesen Code geheim",
|
||||
"cred.orCopy": "Oder den Code kopieren",
|
||||
"qr.title": "QR-Code scannen",
|
||||
"qr.subtitle": "Richten Sie die Kamera auf den QR-Code",
|
||||
"qr.scanning": "Wird gescannt …",
|
||||
|
||||
+16
-12
@@ -93,9 +93,10 @@ export const DICTIONARY = {
|
||||
"intro.createDesc": "Your device generates the keys and a one-time invitation. Nothing touches a server.",
|
||||
"intro.createCta": "Generate keys & invitation",
|
||||
"intro.joinTitle": "Join a channel",
|
||||
"intro.joinDesc": "Scan your peer's QR with your camera, or paste their invitation code.",
|
||||
"intro.scanTitle": "Scan QR with camera",
|
||||
"intro.scanSubtitle": "Fastest - point at your peer's screen",
|
||||
"intro.orPasteCode": "or paste code",
|
||||
"intro.orPasteCode": "Or paste the code",
|
||||
"intro.pastePlaceholder": "Paste invitation code here…",
|
||||
"intro.connect": "Connect",
|
||||
"intro.connecting": "Processing…",
|
||||
@@ -271,22 +272,26 @@ export const DICTIONARY = {
|
||||
"handshake.establish": "Establish connection",
|
||||
"handshake.thenReceive": "Then receive the answer your peer sends back",
|
||||
"handshake.pasteAnswerPlaceholder": "Paste peer's answer code…",
|
||||
"handshake.answerSentNote": "Send this answer to the creator, then wait - the chat opens once they connect.",
|
||||
"handshake.answerSentNote": "Waiting for the creator to connect. The chat opens automatically.",
|
||||
"handshake.qrHint": "Keep this open until your peer captures the code.",
|
||||
"handshake.qrHintFrames": "The handshake is split across {frames} frames - keep this open until your peer captures all of them.",
|
||||
"verify.title": "Security verification",
|
||||
"verify.desc": "Compare this safety code with your peer over a separate channel (voice / in person), then type it to unlock the chat.",
|
||||
"verify.enterLabel": "Enter the verified code",
|
||||
"verify.placeholder": "Type code here",
|
||||
"verify.confirm": "Confirm code",
|
||||
"verify.confirmed": "Confirmed",
|
||||
"verify.mismatch": "Don't match",
|
||||
"verify.yours": "Your confirmation",
|
||||
"verify.peer": "Peer confirmation",
|
||||
"verify.pending": "Pending",
|
||||
"verify.waiting": "Waiting for code…",
|
||||
"verify.verified": "Channel verified",
|
||||
"verify.bothConfirmed": "Both parties confirmed. Opening the secure chat…",
|
||||
"sasv.title": "Verify your peer",
|
||||
"sasv.titleDone": "Peer verified",
|
||||
"sasv.desc": "Read the code aloud over a call or in person. Continue only if your peer sees the same digits.",
|
||||
"sasv.descDone": "You both confirmed the same code. The connection is authenticated.",
|
||||
"sasv.enter": "Enter the code",
|
||||
"sasv.match": "Match",
|
||||
"sasv.noMatch": "Doesn't match",
|
||||
"sasv.you": "You",
|
||||
"sasv.peer": "Peer",
|
||||
"sasv.waitingPeer": "Waiting for peer…",
|
||||
"sasv.codesDiffer": "Codes don't match",
|
||||
"sasv.opening": "Opening secure chat",
|
||||
"pwa.bannerTitle": "Install SecureBit.chat",
|
||||
"pwa.bannerDesc": "Get the native app experience with enhanced security",
|
||||
"pwa.install": "Install",
|
||||
@@ -321,9 +326,8 @@ export const DICTIONARY = {
|
||||
"action.scan": "Scan",
|
||||
"action.downloadDesktop": "Download desktop app",
|
||||
"action.advancedSettings": "Advanced settings",
|
||||
"cred.offerTag": "offer · or copy text",
|
||||
"cred.answerTag": "answer · or copy text",
|
||||
"cred.reveal": "Click to reveal - keep this code private",
|
||||
"cred.orCopy": "Or copy the code",
|
||||
"qr.title": "Scan QR code",
|
||||
"qr.subtitle": "Point your camera at their QR",
|
||||
"qr.scanning": "Scanning…",
|
||||
|
||||
+16
-12
@@ -93,9 +93,10 @@ export const DICTIONARY = {
|
||||
"intro.createDesc": "Tu dispositivo genera las claves y una invitación de un solo uso. Nada de esto pasa por un servidor.",
|
||||
"intro.createCta": "Generar claves e invitación",
|
||||
"intro.joinTitle": "Unirse a un canal",
|
||||
"intro.joinDesc": "Escanea el QR de tu contacto con la cámara o pega su código de invitación.",
|
||||
"intro.scanTitle": "Escanear el QR con la cámara",
|
||||
"intro.scanSubtitle": "Lo más rápido: apunta a la pantalla de la otra persona",
|
||||
"intro.orPasteCode": "o pega el código",
|
||||
"intro.orPasteCode": "O pega el código",
|
||||
"intro.pastePlaceholder": "Pega aquí el código de invitación…",
|
||||
"intro.connect": "Conectar",
|
||||
"intro.connecting": "Procesando…",
|
||||
@@ -271,22 +272,26 @@ export const DICTIONARY = {
|
||||
"handshake.establish": "Establecer la conexión",
|
||||
"handshake.thenReceive": "Después recibe la respuesta que te devuelva la otra persona",
|
||||
"handshake.pasteAnswerPlaceholder": "Pega aquí el código de respuesta…",
|
||||
"handshake.answerSentNote": "Envía esta respuesta a quien creó el canal y espera: el chat se abrirá cuando conecte.",
|
||||
"handshake.answerSentNote": "Esperando a que el creador se conecte. El chat se abrirá automáticamente.",
|
||||
"handshake.qrHint": "Mantén esto abierto hasta que la otra persona capture el código.",
|
||||
"handshake.qrHintFrames": "El saludo se reparte en {frames} imágenes: mantén esto abierto hasta que las capture todas.",
|
||||
"verify.title": "Verificación de seguridad",
|
||||
"verify.desc": "Compara este código de seguridad con la otra persona por otra vía (voz o en persona) y escríbelo para desbloquear el chat.",
|
||||
"verify.enterLabel": "Introduce el código verificado",
|
||||
"verify.placeholder": "Escribe el código aquí",
|
||||
"verify.confirm": "Confirmar el código",
|
||||
"verify.confirmed": "Confirmado",
|
||||
"verify.mismatch": "No coinciden",
|
||||
"verify.yours": "Tu confirmación",
|
||||
"verify.peer": "Confirmación de la otra persona",
|
||||
"verify.pending": "Pendiente",
|
||||
"verify.waiting": "Esperando el código…",
|
||||
"verify.verified": "Canal verificado",
|
||||
"verify.bothConfirmed": "Ambas partes han confirmado. Abriendo el chat seguro…",
|
||||
"sasv.title": "Verifica a tu contacto",
|
||||
"sasv.titleDone": "Contacto verificado",
|
||||
"sasv.desc": "Lee el código en voz alta por llamada o en persona. Continúa solo si tu contacto ve los mismos dígitos.",
|
||||
"sasv.descDone": "Ambos confirmasteis el mismo código. La conexión está autenticada.",
|
||||
"sasv.enter": "Introduce el código",
|
||||
"sasv.match": "Coincide",
|
||||
"sasv.noMatch": "No coincide",
|
||||
"sasv.you": "Tú",
|
||||
"sasv.peer": "Contacto",
|
||||
"sasv.waitingPeer": "Esperando a tu contacto…",
|
||||
"sasv.codesDiffer": "Los códigos no coinciden",
|
||||
"sasv.opening": "Abriendo el chat seguro",
|
||||
"pwa.bannerTitle": "Instala SecureBit.chat",
|
||||
"pwa.bannerDesc": "Disfruta de la experiencia de app nativa con más seguridad",
|
||||
"pwa.install": "Instalar",
|
||||
@@ -321,9 +326,8 @@ export const DICTIONARY = {
|
||||
"action.scan": "Escanear",
|
||||
"action.downloadDesktop": "Descargar la app de escritorio",
|
||||
"action.advancedSettings": "Ajustes avanzados",
|
||||
"cred.offerTag": "oferta · o copia el texto",
|
||||
"cred.answerTag": "respuesta · o copia el texto",
|
||||
"cred.reveal": "Haz clic para mostrarlo: mantén este código en privado",
|
||||
"cred.orCopy": "O copia el código",
|
||||
"qr.title": "Escanear código QR",
|
||||
"qr.subtitle": "Apunta la cámara a su código QR",
|
||||
"qr.scanning": "Escaneando…",
|
||||
|
||||
+16
-12
@@ -93,9 +93,10 @@ export const DICTIONARY = {
|
||||
"intro.createDesc": "دستگاه شما کلیدها و یک دعوتنامهٔ یکبارمصرف میسازد. هیچچیز به سرور نمیرسد.",
|
||||
"intro.createCta": "ساخت کلید و دعوتنامه",
|
||||
"intro.joinTitle": "به کانالی بپیوندید",
|
||||
"intro.joinDesc": "QR طرف مقابل را با دوربین اسکن کنید یا کد دعوت او را جایگذاری کنید.",
|
||||
"intro.scanTitle": "QR را با دوربین بخوانید",
|
||||
"intro.scanSubtitle": "سریعترین راه - به صفحهٔ همتایتان بگیرید",
|
||||
"intro.orPasteCode": "یا کد را بچسبانید",
|
||||
"intro.orPasteCode": "یا کد را جایگذاری کنید",
|
||||
"intro.pastePlaceholder": "کد دعوت را اینجا بچسبانید…",
|
||||
"intro.connect": "اتصال",
|
||||
"intro.connecting": "در حال پردازش…",
|
||||
@@ -271,22 +272,26 @@ export const DICTIONARY = {
|
||||
"handshake.establish": "برقراری اتصال",
|
||||
"handshake.thenReceive": "سپس پاسخی را که همتایتان برمیگرداند دریافت کنید",
|
||||
"handshake.pasteAnswerPlaceholder": "کد پاسخ همتا را بچسبانید…",
|
||||
"handshake.answerSentNote": "این پاسخ را برای سازنده بفرستید و منتظر بمانید - گفتگو بهمحض اتصال او باز میشود.",
|
||||
"handshake.answerSentNote": "در انتظار اتصال سازنده. گفتگو بهطور خودکار باز میشود.",
|
||||
"handshake.qrHint": "این را باز نگه دارید تا همتایتان کد را بگیرد.",
|
||||
"handshake.qrHintFrames": "دستدادن میان {frames} فریم پخش شده است - این را باز نگه دارید تا همتایتان همهٔ آنها را بگیرد.",
|
||||
"verify.title": "تأیید امنیتی",
|
||||
"verify.desc": "این کد ایمنی را از راه کانالی جداگانه (تماس صوتی / حضوری) با همتایتان مقایسه کنید، سپس آن را بنویسید تا گفتگو باز شود.",
|
||||
"verify.enterLabel": "کد تأییدشده را وارد کنید",
|
||||
"verify.placeholder": "کد را اینجا بنویسید",
|
||||
"verify.confirm": "تأیید کد",
|
||||
"verify.confirmed": "تأیید شد",
|
||||
"verify.mismatch": "همخوان نیستند",
|
||||
"verify.yours": "تأیید شما",
|
||||
"verify.peer": "تأیید همتا",
|
||||
"verify.pending": "در انتظار",
|
||||
"verify.waiting": "در انتظار کد…",
|
||||
"verify.verified": "کانال تأیید شد",
|
||||
"verify.bothConfirmed": "هر دو طرف تأیید کردند. گفتگوی امن در حال باز شدن…",
|
||||
"sasv.title": "طرف مقابل را تأیید کنید",
|
||||
"sasv.titleDone": "طرف مقابل تأیید شد",
|
||||
"sasv.desc": "کد را در تماس یا حضوری با صدای بلند بخوانید. فقط وقتی ادامه دهید که طرف مقابل همان ارقام را ببیند.",
|
||||
"sasv.descDone": "هر دو یک کد را تأیید کردید. اتصال احراز هویت شد.",
|
||||
"sasv.enter": "کد را وارد کنید",
|
||||
"sasv.match": "مطابق است",
|
||||
"sasv.noMatch": "مطابق نیست",
|
||||
"sasv.you": "شما",
|
||||
"sasv.peer": "طرف مقابل",
|
||||
"sasv.waitingPeer": "در انتظار طرف مقابل…",
|
||||
"sasv.codesDiffer": "کدها مطابق نیستند",
|
||||
"sasv.opening": "در حال باز کردن گفتگوی امن",
|
||||
"pwa.bannerTitle": "SecureBit.chat را نصب کنید",
|
||||
"pwa.bannerDesc": "تجربهٔ برنامهٔ بومی با امنیت بیشتر",
|
||||
"pwa.install": "نصب",
|
||||
@@ -321,9 +326,8 @@ export const DICTIONARY = {
|
||||
"action.scan": "خواندن",
|
||||
"action.downloadDesktop": "دریافت برنامهٔ رومیزی",
|
||||
"action.advancedSettings": "تنظیمات پیشرفته",
|
||||
"cred.offerTag": "پیشنهاد · یا متن را رونوشت کنید",
|
||||
"cred.answerTag": "پاسخ · یا متن را رونوشت کنید",
|
||||
"cred.reveal": "برای نمایش کلیک کنید - این کد را خصوصی نگه دارید",
|
||||
"cred.orCopy": "یا کد را رونوشت کنید",
|
||||
"qr.title": "خواندن کد QR",
|
||||
"qr.subtitle": "دوربین را به QR آنها بگیرید",
|
||||
"qr.scanning": "در حال خواندن…",
|
||||
|
||||
+16
-12
@@ -93,9 +93,10 @@ export const DICTIONARY = {
|
||||
"intro.createDesc": "Votre appareil génère les clés et une invitation à usage unique. Rien de tout cela ne passe par un serveur.",
|
||||
"intro.createCta": "Générer les clés et l'invitation",
|
||||
"intro.joinTitle": "Rejoindre un canal",
|
||||
"intro.joinDesc": "Scannez le QR de votre correspondant avec l'appareil photo, ou collez son code d'invitation.",
|
||||
"intro.scanTitle": "Scanner le QR avec la caméra",
|
||||
"intro.scanSubtitle": "Le plus rapide : visez l'écran de votre correspondant",
|
||||
"intro.orPasteCode": "ou collez le code",
|
||||
"intro.orPasteCode": "Ou collez le code",
|
||||
"intro.pastePlaceholder": "Collez ici le code d'invitation…",
|
||||
"intro.connect": "Se connecter",
|
||||
"intro.connecting": "Traitement…",
|
||||
@@ -271,22 +272,26 @@ export const DICTIONARY = {
|
||||
"handshake.establish": "Établir la connexion",
|
||||
"handshake.thenReceive": "Recevez ensuite la réponse que votre correspondant renvoie",
|
||||
"handshake.pasteAnswerPlaceholder": "Collez le code de réponse…",
|
||||
"handshake.answerSentNote": "Envoyez cette réponse à la personne qui a créé le canal, puis patientez : la discussion s'ouvre dès qu'elle se connecte.",
|
||||
"handshake.answerSentNote": "En attente de la connexion du créateur. Le chat s'ouvrira automatiquement.",
|
||||
"handshake.qrHint": "Gardez ceci ouvert jusqu'à ce que votre correspondant ait capturé le code.",
|
||||
"handshake.qrHintFrames": "La poignée de main est répartie sur {frames} images : gardez ceci ouvert jusqu'à ce que votre correspondant les ait toutes capturées.",
|
||||
"verify.title": "Vérification de sécurité",
|
||||
"verify.desc": "Comparez ce code de sécurité avec votre correspondant par un autre canal (voix ou en personne), puis saisissez-le pour ouvrir la discussion.",
|
||||
"verify.enterLabel": "Saisissez le code vérifié",
|
||||
"verify.placeholder": "Tapez le code ici",
|
||||
"verify.confirm": "Confirmer le code",
|
||||
"verify.confirmed": "Confirmé",
|
||||
"verify.mismatch": "Ne correspond pas",
|
||||
"verify.yours": "Votre confirmation",
|
||||
"verify.peer": "Confirmation du correspondant",
|
||||
"verify.pending": "En attente",
|
||||
"verify.waiting": "En attente du code…",
|
||||
"verify.verified": "Canal vérifié",
|
||||
"verify.bothConfirmed": "Les deux parties ont confirmé. Ouverture de la discussion sécurisée…",
|
||||
"sasv.title": "Vérifiez votre correspondant",
|
||||
"sasv.titleDone": "Correspondant vérifié",
|
||||
"sasv.desc": "Lisez le code à voix haute par appel ou en personne. Ne continuez que si votre correspondant voit les mêmes chiffres.",
|
||||
"sasv.descDone": "Vous avez tous les deux confirmé le même code. La connexion est authentifiée.",
|
||||
"sasv.enter": "Saisissez le code",
|
||||
"sasv.match": "Correspond",
|
||||
"sasv.noMatch": "Ne correspond pas",
|
||||
"sasv.you": "Vous",
|
||||
"sasv.peer": "Correspondant",
|
||||
"sasv.waitingPeer": "En attente du correspondant…",
|
||||
"sasv.codesDiffer": "Les codes ne correspondent pas",
|
||||
"sasv.opening": "Ouverture du chat sécurisé",
|
||||
"pwa.bannerTitle": "Installer SecureBit.chat",
|
||||
"pwa.bannerDesc": "Profitez de l'expérience d'une application native, avec une sécurité renforcée",
|
||||
"pwa.install": "Installer",
|
||||
@@ -321,9 +326,8 @@ export const DICTIONARY = {
|
||||
"action.scan": "Scanner",
|
||||
"action.downloadDesktop": "Télécharger l'application",
|
||||
"action.advancedSettings": "Réglages avancés",
|
||||
"cred.offerTag": "offre · ou copiez le texte",
|
||||
"cred.answerTag": "réponse · ou copiez le texte",
|
||||
"cred.reveal": "Cliquez pour afficher - gardez ce code privé",
|
||||
"cred.orCopy": "Ou copiez le code",
|
||||
"qr.title": "Scanner le QR",
|
||||
"qr.subtitle": "Visez le QR de votre correspondant",
|
||||
"qr.scanning": "Lecture…",
|
||||
|
||||
+16
-12
@@ -93,9 +93,10 @@ export const DICTIONARY = {
|
||||
"intro.createDesc": "המכשיר שלכם יוצר את המפתחות והזמנה חד־פעמית. שום דבר לא נוגע בשרת.",
|
||||
"intro.createCta": "יצירת מפתחות והזמנה",
|
||||
"intro.joinTitle": "הצטרפו לערוץ",
|
||||
"intro.joinDesc": "סרקו את קוד ה-QR של הצד השני במצלמה, או הדביקו את קוד ההזמנה.",
|
||||
"intro.scanTitle": "סרקו QR עם המצלמה",
|
||||
"intro.scanSubtitle": "הכי מהיר - כוונו למסך של העמית",
|
||||
"intro.orPasteCode": "או הדביקו קוד",
|
||||
"intro.orPasteCode": "או הדביקו את הקוד",
|
||||
"intro.pastePlaceholder": "הדביקו כאן את קוד ההזמנה…",
|
||||
"intro.connect": "התחברו",
|
||||
"intro.connecting": "מעבד…",
|
||||
@@ -271,22 +272,26 @@ export const DICTIONARY = {
|
||||
"handshake.establish": "יצירת חיבור",
|
||||
"handshake.thenReceive": "ואז קבלו את התשובה שהעמית שולח בחזרה",
|
||||
"handshake.pasteAnswerPlaceholder": "הדביקו את קוד התשובה של העמית…",
|
||||
"handshake.answerSentNote": "שלחו את התשובה ליוצר ואז המתינו - הצ'אט נפתח ברגע שהם מתחברים.",
|
||||
"handshake.answerSentNote": "ממתינים שהיוצר יתחבר. הצ'אט ייפתח אוטומטית.",
|
||||
"handshake.qrHint": "השאירו פתוח עד שהעמית יקלוט את הקוד.",
|
||||
"handshake.qrHintFrames": "לחיצת היד מפוצלת ל-{frames} פריימים - השאירו פתוח עד שהעמית יקלוט את כולם.",
|
||||
"verify.title": "אימות אבטחה",
|
||||
"verify.desc": "השוו את קוד הבטיחות הזה עם העמית בערוץ נפרד (שיחה קולית / פנים אל פנים), ואז הקלידו אותו כדי לפתוח את הצ'אט.",
|
||||
"verify.enterLabel": "הזינו את הקוד המאומת",
|
||||
"verify.placeholder": "הקלידו כאן את הקוד",
|
||||
"verify.confirm": "אשרו את הקוד",
|
||||
"verify.confirmed": "אושר",
|
||||
"verify.mismatch": "לא תואמים",
|
||||
"verify.yours": "האישור שלכם",
|
||||
"verify.peer": "אישור העמית",
|
||||
"verify.pending": "ממתין",
|
||||
"verify.waiting": "ממתין לקוד…",
|
||||
"verify.verified": "הערוץ אומת",
|
||||
"verify.bothConfirmed": "שני הצדדים אישרו. פותח את הצ'אט המאובטח…",
|
||||
"sasv.title": "אמתו את הצד השני",
|
||||
"sasv.titleDone": "הצד השני אומת",
|
||||
"sasv.desc": "הקריאו את הקוד בקול בשיחה או פנים אל פנים. המשיכו רק אם הצד השני רואה את אותן ספרות.",
|
||||
"sasv.descDone": "שניכם אישרתם את אותו קוד. החיבור מאומת.",
|
||||
"sasv.enter": "הזינו את הקוד",
|
||||
"sasv.match": "תואם",
|
||||
"sasv.noMatch": "לא תואם",
|
||||
"sasv.you": "אתם",
|
||||
"sasv.peer": "הצד השני",
|
||||
"sasv.waitingPeer": "ממתינים לצד השני…",
|
||||
"sasv.codesDiffer": "הקודים לא תואמים",
|
||||
"sasv.opening": "פותחים צ'אט מאובטח",
|
||||
"pwa.bannerTitle": "התקינו את SecureBit.chat",
|
||||
"pwa.bannerDesc": "קבלו חוויית אפליקציה מקורית עם אבטחה משופרת",
|
||||
"pwa.install": "התקנה",
|
||||
@@ -321,9 +326,8 @@ export const DICTIONARY = {
|
||||
"action.scan": "סריקה",
|
||||
"action.downloadDesktop": "הורידו את אפליקציית שולחן העבודה",
|
||||
"action.advancedSettings": "הגדרות מתקדמות",
|
||||
"cred.offerTag": "הצעה · או העתיקו טקסט",
|
||||
"cred.answerTag": "תשובה · או העתיקו טקסט",
|
||||
"cred.reveal": "לחצו לחשיפה - שמרו את הקוד הזה פרטי",
|
||||
"cred.orCopy": "או העתיקו את הקוד",
|
||||
"qr.title": "סריקת קוד QR",
|
||||
"qr.subtitle": "כוונו את המצלמה ל-QR שלהם",
|
||||
"qr.scanning": "סורק…",
|
||||
|
||||
+16
-12
@@ -93,9 +93,10 @@ export const DICTIONARY = {
|
||||
"intro.createDesc": "आपका उपकरण कुंजियाँ और एक बार काम आने वाला निमंत्रण बनाता है। इनमें से कुछ भी किसी सर्वर तक नहीं जाता।",
|
||||
"intro.createCta": "कुंजियाँ और निमंत्रण बनाएँ",
|
||||
"intro.joinTitle": "चैनल में शामिल हों",
|
||||
"intro.joinDesc": "कैमरे से अपने साथी का QR स्कैन करें, या उनका आमंत्रण कोड पेस्ट करें।",
|
||||
"intro.scanTitle": "कैमरे से QR स्कैन करें",
|
||||
"intro.scanSubtitle": "सबसे तेज़ - सामने वाले की स्क्रीन पर कैमरा रखें",
|
||||
"intro.orPasteCode": "या कोड चिपकाएँ",
|
||||
"intro.orPasteCode": "या कोड पेस्ट करें",
|
||||
"intro.pastePlaceholder": "निमंत्रण कोड यहाँ चिपकाएँ…",
|
||||
"intro.connect": "जोड़ें",
|
||||
"intro.connecting": "प्रक्रिया जारी…",
|
||||
@@ -271,22 +272,26 @@ export const DICTIONARY = {
|
||||
"handshake.establish": "जुड़ाव बनाएँ",
|
||||
"handshake.thenReceive": "फिर सामने वाले का भेजा उत्तर लीजिए",
|
||||
"handshake.pasteAnswerPlaceholder": "सामने वाले का उत्तर कोड चिपकाएँ…",
|
||||
"handshake.answerSentNote": "यह उत्तर चैनल बनाने वाले को भेजिए और रुकिए - जैसे ही वे जुड़ेंगे, चैट खुल जाएगी।",
|
||||
"handshake.answerSentNote": "निर्माता के जुड़ने की प्रतीक्षा है। चैट अपने-आप खुल जाएगी।",
|
||||
"handshake.qrHint": "जब तक सामने वाला कोड पढ़ न ले, इसे खुला रखिए।",
|
||||
"handshake.qrHintFrames": "हाथ मिलाना {frames} फ़्रेमों में बँटा है - जब तक सामने वाला सब पढ़ न ले, इसे खुला रखिए।",
|
||||
"verify.title": "सुरक्षा सत्यापन",
|
||||
"verify.desc": "इस सुरक्षा कोड को किसी दूसरे रास्ते से (आवाज़ पर या आमने-सामने) मिलाइए, फिर उसे लिखकर चैट खोलिए।",
|
||||
"verify.enterLabel": "मिलाया हुआ कोड लिखें",
|
||||
"verify.placeholder": "कोड यहाँ लिखें",
|
||||
"verify.confirm": "कोड पक्का करें",
|
||||
"verify.confirmed": "पक्का हुआ",
|
||||
"verify.mismatch": "मेल नहीं खाता",
|
||||
"verify.yours": "आपकी पुष्टि",
|
||||
"verify.peer": "सामने वाले की पुष्टि",
|
||||
"verify.pending": "प्रतीक्षा में",
|
||||
"verify.waiting": "कोड की प्रतीक्षा…",
|
||||
"verify.verified": "चैनल सत्यापित",
|
||||
"verify.bothConfirmed": "दोनों पक्षों ने पुष्टि कर दी। सुरक्षित चैट खुल रही है…",
|
||||
"sasv.title": "अपने साथी की पुष्टि करें",
|
||||
"sasv.titleDone": "साथी की पुष्टि हो गई",
|
||||
"sasv.desc": "कॉल पर या आमने-सामने कोड ज़ोर से पढ़ें। तभी आगे बढ़ें जब आपके साथी को वही अंक दिखें।",
|
||||
"sasv.descDone": "आप दोनों ने एक ही कोड की पुष्टि की। कनेक्शन प्रमाणित है।",
|
||||
"sasv.enter": "कोड दर्ज करें",
|
||||
"sasv.match": "मेल खाता है",
|
||||
"sasv.noMatch": "मेल नहीं खाता",
|
||||
"sasv.you": "आप",
|
||||
"sasv.peer": "साथी",
|
||||
"sasv.waitingPeer": "साथी की प्रतीक्षा…",
|
||||
"sasv.codesDiffer": "कोड मेल नहीं खाते",
|
||||
"sasv.opening": "सुरक्षित चैट खुल रही है",
|
||||
"pwa.bannerTitle": "SecureBit.chat इंस्टॉल करें",
|
||||
"pwa.bannerDesc": "बेहतर सुरक्षा के साथ मूल ऐप जैसा अनुभव पाइए",
|
||||
"pwa.install": "इंस्टॉल करें",
|
||||
@@ -321,9 +326,8 @@ export const DICTIONARY = {
|
||||
"action.scan": "स्कैन करें",
|
||||
"action.downloadDesktop": "डेस्कटॉप ऐप डाउनलोड करें",
|
||||
"action.advancedSettings": "उन्नत सेटिंग्स",
|
||||
"cred.offerTag": "निमंत्रण · या पाठ कॉपी करें",
|
||||
"cred.answerTag": "उत्तर · या पाठ कॉपी करें",
|
||||
"cred.reveal": "देखने के लिए दबाएँ - यह कोड किसी को न बताएँ",
|
||||
"cred.orCopy": "या कोड कॉपी करें",
|
||||
"qr.title": "QR कोड स्कैन करें",
|
||||
"qr.subtitle": "कैमरा सामने वाले के QR पर रखें",
|
||||
"qr.scanning": "पढ़ा जा रहा है…",
|
||||
|
||||
+15
-11
@@ -93,6 +93,7 @@ export const DICTIONARY = {
|
||||
"intro.createDesc": "여러분의 기기가 키와 일회용 초대장을 만듭니다. 그 어느 것도 서버를 거치지 않습니다.",
|
||||
"intro.createCta": "키와 초대장 만들기",
|
||||
"intro.joinTitle": "채널 참여하기",
|
||||
"intro.joinDesc": "카메라로 상대방의 QR을 스캔하거나 초대 코드를 붙여넣으세요.",
|
||||
"intro.scanTitle": "카메라로 QR 스캔",
|
||||
"intro.scanSubtitle": "가장 빠릅니다 - 상대의 화면을 비추세요",
|
||||
"intro.orPasteCode": "또는 코드 붙여넣기",
|
||||
@@ -271,22 +272,26 @@ export const DICTIONARY = {
|
||||
"handshake.establish": "연결 맺기",
|
||||
"handshake.thenReceive": "그다음 상대가 보내는 응답을 받으세요",
|
||||
"handshake.pasteAnswerPlaceholder": "상대의 응답 코드를 붙여넣으세요…",
|
||||
"handshake.answerSentNote": "이 응답을 만든 사람에게 보내고 기다리세요 - 상대가 연결되면 대화가 열립니다.",
|
||||
"handshake.answerSentNote": "생성자의 연결을 기다리는 중입니다. 채팅이 자동으로 열립니다.",
|
||||
"handshake.qrHint": "상대가 코드를 다 읽을 때까지 이 화면을 열어 두세요.",
|
||||
"handshake.qrHintFrames": "악수가 {frames}개 화면으로 나뉘어 있습니다 - 상대가 전부 읽을 때까지 열어 두세요.",
|
||||
"verify.title": "보안 확인",
|
||||
"verify.desc": "다른 경로(통화나 대면)로 상대와 이 안전 코드를 대조한 뒤, 입력해서 대화를 여세요.",
|
||||
"verify.enterLabel": "대조한 코드를 입력하세요",
|
||||
"verify.placeholder": "여기에 코드 입력",
|
||||
"verify.confirm": "코드 확인",
|
||||
"verify.confirmed": "확인됨",
|
||||
"verify.mismatch": "일치하지 않음",
|
||||
"verify.yours": "내 확인",
|
||||
"verify.peer": "상대의 확인",
|
||||
"verify.pending": "대기 중",
|
||||
"verify.waiting": "코드 대기 중…",
|
||||
"verify.verified": "채널 확인 완료",
|
||||
"verify.bothConfirmed": "양쪽 모두 확인했습니다. 안전한 대화를 여는 중…",
|
||||
"sasv.title": "상대방 확인",
|
||||
"sasv.titleDone": "상대방 확인 완료",
|
||||
"sasv.desc": "통화나 대면으로 코드를 소리 내어 읽어 주세요. 상대방이 같은 숫자를 볼 때만 계속하세요.",
|
||||
"sasv.descDone": "두 사람 모두 같은 코드를 확인했습니다. 연결이 인증되었습니다.",
|
||||
"sasv.enter": "코드 입력",
|
||||
"sasv.match": "일치",
|
||||
"sasv.noMatch": "일치하지 않음",
|
||||
"sasv.you": "나",
|
||||
"sasv.peer": "상대방",
|
||||
"sasv.waitingPeer": "상대방을 기다리는 중…",
|
||||
"sasv.codesDiffer": "코드가 일치하지 않음",
|
||||
"sasv.opening": "보안 채팅을 여는 중",
|
||||
"pwa.bannerTitle": "SecureBit.chat 설치",
|
||||
"pwa.bannerDesc": "보안이 강화된 네이티브 앱 경험을 누리세요",
|
||||
"pwa.install": "설치",
|
||||
@@ -321,9 +326,8 @@ export const DICTIONARY = {
|
||||
"action.scan": "스캔",
|
||||
"action.downloadDesktop": "데스크톱 앱 내려받기",
|
||||
"action.advancedSettings": "고급 설정",
|
||||
"cred.offerTag": "초대 · 또는 텍스트 복사",
|
||||
"cred.answerTag": "응답 · 또는 텍스트 복사",
|
||||
"cred.reveal": "눌러서 보기 - 이 코드는 남에게 알리지 마세요",
|
||||
"cred.orCopy": "또는 코드 복사",
|
||||
"qr.title": "QR 코드 스캔",
|
||||
"qr.subtitle": "상대의 QR에 카메라를 맞추세요",
|
||||
"qr.scanning": "읽는 중…",
|
||||
|
||||
+16
-12
@@ -93,9 +93,10 @@ export const DICTIONARY = {
|
||||
"intro.createDesc": "Ваше устройство создаёт ключи и одноразовое приглашение. Ничто из этого не попадает на сервер.",
|
||||
"intro.createCta": "Создать ключи и приглашение",
|
||||
"intro.joinTitle": "Присоединиться к каналу",
|
||||
"intro.joinDesc": "Отсканируйте QR собеседника камерой или вставьте его код приглашения.",
|
||||
"intro.scanTitle": "Сканировать QR камерой",
|
||||
"intro.scanSubtitle": "Быстрее всего - наведите на экран собеседника",
|
||||
"intro.orPasteCode": "или вставьте код",
|
||||
"intro.orPasteCode": "Или вставьте код",
|
||||
"intro.pastePlaceholder": "Вставьте код приглашения сюда…",
|
||||
"intro.connect": "Соединиться",
|
||||
"intro.connecting": "Обработка…",
|
||||
@@ -271,22 +272,26 @@ export const DICTIONARY = {
|
||||
"handshake.establish": "Установить соединение",
|
||||
"handshake.thenReceive": "Затем получите ответ, который пришлёт собеседник",
|
||||
"handshake.pasteAnswerPlaceholder": "Вставьте код ответа собеседника…",
|
||||
"handshake.answerSentNote": "Отправьте этот ответ создателю и подождите - чат откроется, как только он подключится.",
|
||||
"handshake.answerSentNote": "Ждём, пока создатель подключится. Чат откроется автоматически.",
|
||||
"handshake.qrHint": "Держите это открытым, пока собеседник не считает код.",
|
||||
"handshake.qrHintFrames": "Рукопожатие разбито на {frames} кадров - держите это открытым, пока собеседник не считает все.",
|
||||
"verify.title": "Проверка безопасности",
|
||||
"verify.desc": "Сверьте этот код безопасности с собеседником по другому каналу (голосом или лично), затем введите его, чтобы открыть чат.",
|
||||
"verify.enterLabel": "Введите сверенный код",
|
||||
"verify.placeholder": "Введите код здесь",
|
||||
"verify.confirm": "Подтвердить код",
|
||||
"verify.confirmed": "Подтверждено",
|
||||
"verify.mismatch": "Не совпадают",
|
||||
"verify.yours": "Ваше подтверждение",
|
||||
"verify.peer": "Подтверждение собеседника",
|
||||
"verify.pending": "Ожидается",
|
||||
"verify.waiting": "Ожидание кода…",
|
||||
"verify.verified": "Канал проверен",
|
||||
"verify.bothConfirmed": "Обе стороны подтвердили. Открываем защищённый чат…",
|
||||
"sasv.title": "Проверьте собеседника",
|
||||
"sasv.titleDone": "Собеседник проверен",
|
||||
"sasv.desc": "Прочитайте код вслух по звонку или при встрече. Продолжайте, только если собеседник видит те же цифры.",
|
||||
"sasv.descDone": "Вы оба подтвердили один и тот же код. Соединение подлинное.",
|
||||
"sasv.enter": "Введите код",
|
||||
"sasv.match": "Совпадает",
|
||||
"sasv.noMatch": "Не совпадает",
|
||||
"sasv.you": "Вы",
|
||||
"sasv.peer": "Собеседник",
|
||||
"sasv.waitingPeer": "Ждём собеседника…",
|
||||
"sasv.codesDiffer": "Коды не совпадают",
|
||||
"sasv.opening": "Открываем защищённый чат",
|
||||
"pwa.bannerTitle": "Установите SecureBit.chat",
|
||||
"pwa.bannerDesc": "Опыт полноценного приложения с усиленной защитой",
|
||||
"pwa.install": "Установить",
|
||||
@@ -321,9 +326,8 @@ export const DICTIONARY = {
|
||||
"action.scan": "Сканировать",
|
||||
"action.downloadDesktop": "Скачать приложение",
|
||||
"action.advancedSettings": "Дополнительные настройки",
|
||||
"cred.offerTag": "предложение · или скопируйте текст",
|
||||
"cred.answerTag": "ответ · или скопируйте текст",
|
||||
"cred.reveal": "Нажмите, чтобы показать - держите этот код в тайне",
|
||||
"cred.orCopy": "Или скопируйте код",
|
||||
"qr.title": "Сканировать QR-код",
|
||||
"qr.subtitle": "Наведите камеру на их QR-код",
|
||||
"qr.scanning": "Сканирование…",
|
||||
|
||||
+16
-12
@@ -93,9 +93,10 @@ export const DICTIONARY = {
|
||||
"intro.createDesc": "Ваш пристрій створює ключі та одноразове запрошення. Ніщо з цього не потрапляє на сервер.",
|
||||
"intro.createCta": "Створити ключі та запрошення",
|
||||
"intro.joinTitle": "Приєднатися до каналу",
|
||||
"intro.joinDesc": "Відскануйте QR співрозмовника камерою або вставте його код запрошення.",
|
||||
"intro.scanTitle": "Сканувати QR камерою",
|
||||
"intro.scanSubtitle": "Найшвидше - наведіть на екран співрозмовника",
|
||||
"intro.orPasteCode": "або вставте код",
|
||||
"intro.orPasteCode": "Або вставте код",
|
||||
"intro.pastePlaceholder": "Вставте код запрошення сюди…",
|
||||
"intro.connect": "З'єднатися",
|
||||
"intro.connecting": "Обробка…",
|
||||
@@ -271,22 +272,26 @@ export const DICTIONARY = {
|
||||
"handshake.establish": "Встановити з'єднання",
|
||||
"handshake.thenReceive": "Потім отримайте відповідь, яку надішле співрозмовник",
|
||||
"handshake.pasteAnswerPlaceholder": "Вставте код відповіді співрозмовника…",
|
||||
"handshake.answerSentNote": "Надішліть цю відповідь створювачу й зачекайте - чат відкриється, щойно він приєднається.",
|
||||
"handshake.answerSentNote": "Чекаємо, поки творець під'єднається. Чат відкриється автоматично.",
|
||||
"handshake.qrHint": "Тримайте це відкритим, доки співрозмовник не зчитає код.",
|
||||
"handshake.qrHintFrames": "Рукостискання розбите на {frames} кадрів - тримайте це відкритим, доки співрозмовник не зчитає всі.",
|
||||
"verify.title": "Перевірка безпеки",
|
||||
"verify.desc": "Звірте цей код безпеки зі співрозмовником іншим каналом (голосом або особисто), потім введіть його, щоб відкрити чат.",
|
||||
"verify.enterLabel": "Введіть звірений код",
|
||||
"verify.placeholder": "Введіть код тут",
|
||||
"verify.confirm": "Підтвердити код",
|
||||
"verify.confirmed": "Підтверджено",
|
||||
"verify.mismatch": "Не збігаються",
|
||||
"verify.yours": "Ваше підтвердження",
|
||||
"verify.peer": "Підтвердження співрозмовника",
|
||||
"verify.pending": "Очікується",
|
||||
"verify.waiting": "Очікування коду…",
|
||||
"verify.verified": "Канал перевірено",
|
||||
"verify.bothConfirmed": "Обидві сторони підтвердили. Відкриваємо захищений чат…",
|
||||
"sasv.title": "Перевірте співрозмовника",
|
||||
"sasv.titleDone": "Співрозмовника перевірено",
|
||||
"sasv.desc": "Прочитайте код уголос під час дзвінка або зустрічі. Продовжуйте, лише якщо співрозмовник бачить ті самі цифри.",
|
||||
"sasv.descDone": "Ви обидва підтвердили той самий код. З'єднання справжнє.",
|
||||
"sasv.enter": "Введіть код",
|
||||
"sasv.match": "Збігається",
|
||||
"sasv.noMatch": "Не збігається",
|
||||
"sasv.you": "Ви",
|
||||
"sasv.peer": "Співрозмовник",
|
||||
"sasv.waitingPeer": "Чекаємо на співрозмовника…",
|
||||
"sasv.codesDiffer": "Коди не збігаються",
|
||||
"sasv.opening": "Відкриваємо захищений чат",
|
||||
"pwa.bannerTitle": "Встановіть SecureBit.chat",
|
||||
"pwa.bannerDesc": "Досвід власного застосунку з посиленим захистом",
|
||||
"pwa.install": "Встановити",
|
||||
@@ -321,9 +326,8 @@ export const DICTIONARY = {
|
||||
"action.scan": "Сканувати",
|
||||
"action.downloadDesktop": "Завантажити застосунок",
|
||||
"action.advancedSettings": "Додаткові налаштування",
|
||||
"cred.offerTag": "пропозиція · або скопіюйте текст",
|
||||
"cred.answerTag": "відповідь · або скопіюйте текст",
|
||||
"cred.reveal": "Натисніть, щоб показати - тримайте цей код у таємниці",
|
||||
"cred.orCopy": "Або скопіюйте код",
|
||||
"qr.title": "Сканувати QR-код",
|
||||
"qr.subtitle": "Наведіть камеру на їхній QR-код",
|
||||
"qr.scanning": "Сканування…",
|
||||
|
||||
+15
-11
@@ -93,6 +93,7 @@ export const DICTIONARY = {
|
||||
"intro.createDesc": "آپ کا آلہ کلیدیں اور ایک بار کی دعوت بناتا ہے۔ کچھ بھی سرور کو نہیں چھوتا۔",
|
||||
"intro.createCta": "کلیدیں اور دعوت بنائیں",
|
||||
"intro.joinTitle": "چینل میں شامل ہوں",
|
||||
"intro.joinDesc": "کیمرے سے اپنے ساتھی کا QR اسکین کریں، یا ان کا دعوتی کوڈ چسپاں کریں۔",
|
||||
"intro.scanTitle": "کیمرے سے QR پڑھیں",
|
||||
"intro.scanSubtitle": "سب سے تیز - ساتھی کی سکرین کی طرف کریں",
|
||||
"intro.orPasteCode": "یا کوڈ چسپاں کریں",
|
||||
@@ -271,22 +272,26 @@ export const DICTIONARY = {
|
||||
"handshake.establish": "اتصال قائم کریں",
|
||||
"handshake.thenReceive": "پھر وہ جواب وصول کریں جو آپ کا ساتھی واپس بھیجتا ہے",
|
||||
"handshake.pasteAnswerPlaceholder": "ساتھی کا جوابی کوڈ چسپاں کریں…",
|
||||
"handshake.answerSentNote": "یہ جواب بنانے والے کو بھیجیں، پھر انتظار کریں - ان کے جڑتے ہی گفتگو کھل جائے گی۔",
|
||||
"handshake.answerSentNote": "تخلیق کار کے جڑنے کا انتظار ہے۔ چیٹ خود بخود کھل جائے گی۔",
|
||||
"handshake.qrHint": "یہ اس وقت تک کھلا رکھیں جب تک ساتھی کوڈ نہ پکڑ لے۔",
|
||||
"handshake.qrHintFrames": "مصافحہ {frames} فریموں میں بٹا ہے - یہ اس وقت تک کھلا رکھیں جب تک ساتھی سب نہ پکڑ لے۔",
|
||||
"verify.title": "حفاظتی تصدیق",
|
||||
"verify.desc": "یہ حفاظتی کوڈ کسی الگ ذریعے (آواز / بالمشافہ) سے اپنے ساتھی کے ساتھ ملائیں، پھر گفتگو کھولنے کے لیے لکھیں۔",
|
||||
"verify.enterLabel": "تصدیق شدہ کوڈ درج کریں",
|
||||
"verify.placeholder": "کوڈ یہاں لکھیں",
|
||||
"verify.confirm": "کوڈ کی تصدیق",
|
||||
"verify.confirmed": "تصدیق ہو گئی",
|
||||
"verify.mismatch": "مطابقت نہیں",
|
||||
"verify.yours": "آپ کی تصدیق",
|
||||
"verify.peer": "ساتھی کی تصدیق",
|
||||
"verify.pending": "زیرِ التوا",
|
||||
"verify.waiting": "کوڈ کا انتظار…",
|
||||
"verify.verified": "چینل تصدیق شدہ",
|
||||
"verify.bothConfirmed": "دونوں فریقوں نے تصدیق کی۔ محفوظ گفتگو کھولی جا رہی ہے…",
|
||||
"sasv.title": "اپنے ساتھی کی تصدیق کریں",
|
||||
"sasv.titleDone": "ساتھی کی تصدیق ہو گئی",
|
||||
"sasv.desc": "کال پر یا آمنے سامنے کوڈ بلند آواز سے پڑھیں۔ صرف تب آگے بڑھیں جب آپ کے ساتھی کو وہی ہندسے نظر آئیں۔",
|
||||
"sasv.descDone": "آپ دونوں نے ایک ہی کوڈ کی تصدیق کی۔ کنکشن مستند ہے۔",
|
||||
"sasv.enter": "کوڈ درج کریں",
|
||||
"sasv.match": "مماثل ہے",
|
||||
"sasv.noMatch": "مماثل نہیں",
|
||||
"sasv.you": "آپ",
|
||||
"sasv.peer": "ساتھی",
|
||||
"sasv.waitingPeer": "ساتھی کا انتظار…",
|
||||
"sasv.codesDiffer": "کوڈ مماثل نہیں",
|
||||
"sasv.opening": "محفوظ چیٹ کھل رہی ہے",
|
||||
"pwa.bannerTitle": "SecureBit.chat نصب کریں",
|
||||
"pwa.bannerDesc": "بہتر سلامتی کے ساتھ مقامی ایپ کا تجربہ حاصل کریں",
|
||||
"pwa.install": "نصب",
|
||||
@@ -321,9 +326,8 @@ export const DICTIONARY = {
|
||||
"action.scan": "پڑھیں",
|
||||
"action.downloadDesktop": "ڈیسک ٹاپ ایپ ڈاؤن لوڈ کریں",
|
||||
"action.advancedSettings": "اعلیٰ ترتیبات",
|
||||
"cred.offerTag": "پیشکش · یا متن نقل کریں",
|
||||
"cred.answerTag": "جواب · یا متن نقل کریں",
|
||||
"cred.reveal": "دکھانے کے لیے دبائیں - یہ کوڈ نجی رکھیں",
|
||||
"cred.orCopy": "یا کوڈ نقل کریں",
|
||||
"qr.title": "QR کوڈ پڑھیں",
|
||||
"qr.subtitle": "اپنا کیمرہ ان کے QR کی طرف کریں",
|
||||
"qr.scanning": "پڑھا جا رہا ہے…",
|
||||
|
||||
+15
-11
@@ -93,6 +93,7 @@ export const DICTIONARY = {
|
||||
"intro.createDesc": "你的设备会生成密钥和一次性邀请。这些都不会经过任何服务器。",
|
||||
"intro.createCta": "生成密钥与邀请",
|
||||
"intro.joinTitle": "加入通道",
|
||||
"intro.joinDesc": "用相机扫描对方的二维码,或粘贴对方的邀请代码。",
|
||||
"intro.scanTitle": "用摄像头扫描二维码",
|
||||
"intro.scanSubtitle": "最快的方式 —— 对准对方的屏幕",
|
||||
"intro.orPasteCode": "或粘贴代码",
|
||||
@@ -271,22 +272,26 @@ export const DICTIONARY = {
|
||||
"handshake.establish": "建立连接",
|
||||
"handshake.thenReceive": "然后接收对方发回的应答",
|
||||
"handshake.pasteAnswerPlaceholder": "粘贴对方的应答码…",
|
||||
"handshake.answerSentNote": "把这段应答发给创建者,然后等待 —— 对方一连上,聊天就会打开。",
|
||||
"handshake.answerSentNote": "正在等待创建者连接。聊天将自动打开。",
|
||||
"handshake.qrHint": "在对方读取完代码之前,请保持此界面打开。",
|
||||
"handshake.qrHintFrames": "握手被拆成 {frames} 帧 —— 在对方读完全部帧之前,请保持此界面打开。",
|
||||
"verify.title": "安全校验",
|
||||
"verify.desc": "通过另一条渠道(通话或当面)与对方核对这个安全码,然后输入它以解锁聊天。",
|
||||
"verify.enterLabel": "输入核对过的安全码",
|
||||
"verify.placeholder": "在此输入安全码",
|
||||
"verify.confirm": "确认安全码",
|
||||
"verify.confirmed": "已确认",
|
||||
"verify.mismatch": "不一致",
|
||||
"verify.yours": "你的确认",
|
||||
"verify.peer": "对方的确认",
|
||||
"verify.pending": "等待中",
|
||||
"verify.waiting": "等待安全码…",
|
||||
"verify.verified": "通道已校验",
|
||||
"verify.bothConfirmed": "双方均已确认。正在打开安全聊天…",
|
||||
"sasv.title": "验证对方",
|
||||
"sasv.titleDone": "对方已验证",
|
||||
"sasv.desc": "通过通话或当面大声读出代码。只有对方看到相同的数字时才继续。",
|
||||
"sasv.descDone": "你们双方确认了相同的代码。连接已通过认证。",
|
||||
"sasv.enter": "输入代码",
|
||||
"sasv.match": "一致",
|
||||
"sasv.noMatch": "不一致",
|
||||
"sasv.you": "你",
|
||||
"sasv.peer": "对方",
|
||||
"sasv.waitingPeer": "正在等待对方…",
|
||||
"sasv.codesDiffer": "代码不一致",
|
||||
"sasv.opening": "正在打开安全聊天",
|
||||
"pwa.bannerTitle": "安装 SecureBit.chat",
|
||||
"pwa.bannerDesc": "获得原生应用般的体验,安全性更进一步",
|
||||
"pwa.install": "安装",
|
||||
@@ -321,9 +326,8 @@ export const DICTIONARY = {
|
||||
"action.scan": "扫描",
|
||||
"action.downloadDesktop": "下载桌面应用",
|
||||
"action.advancedSettings": "高级设置",
|
||||
"cred.offerTag": "邀请 · 或复制文本",
|
||||
"cred.answerTag": "应答 · 或复制文本",
|
||||
"cred.reveal": "点击显示 - 请勿泄露此代码",
|
||||
"cred.orCopy": "或复制代码",
|
||||
"qr.title": "扫描二维码",
|
||||
"qr.subtitle": "把摄像头对准对方的二维码",
|
||||
"qr.scanning": "扫描中…",
|
||||
|
||||
@@ -798,6 +798,7 @@ button i {
|
||||
.sb-start .sb-sc::-webkit-scrollbar-thumb { background: rgba(var(--sb-ink), 0.1); border-radius: 99px; }
|
||||
.sb-seg-btn { transition: color .2s; }
|
||||
.sb-soft-btn { transition: all .15s; }
|
||||
.sb-qr-row:hover { background: rgba(var(--sb-ink), 0.025) !important; }
|
||||
.sb-soft-btn:hover { border-color: rgba(var(--sb-ink), 0.18) !important; color: var(--sb-text-2) !important; }
|
||||
.sb-scan-btn:hover { border-color: rgba(var(--sb-green-rgb), 0.5) !important; background: rgba(var(--sb-green-rgb), 0.1) !important; }
|
||||
.sb-gen-btn:hover { background: var(--sb-orange-hi) !important; }
|
||||
|
||||
@@ -11,7 +11,7 @@ let DYNAMIC_CACHE = 'securebit-pwa-dynamic-v4.7.56';
|
||||
// Build stamp — rewritten by scripts/post-build.js on every release so this file's
|
||||
// bytes change each deploy. That is what makes the browser detect a new Service Worker,
|
||||
// reinstall it, drop stale caches and (via controllerchange) prompt the page to update.
|
||||
const SW_BUILD_VERSION = '1790225533889';
|
||||
const SW_BUILD_VERSION = '1790540575787';
|
||||
|
||||
// Locale subdirectories, rewritten by scripts/build-i18n.js. Each localized page is a
|
||||
// separate document at its own URL, so the shell has to be cached and served per
|
||||
|
||||
@@ -176,6 +176,13 @@ function sources() {
|
||||
assert.ok(app.slice(cells, cells + 60).includes("dir: 'ltr'"),
|
||||
'the safety-code tiles sit in a flex row, so they must be pinned left-to-right');
|
||||
|
||||
// The same code printed large above the tiles, in two groups side by side — another
|
||||
// flex row, so in a right-to-left locale the groups would swap places.
|
||||
const big = app.indexOf("key: 'code', style: { padding: '20px 0 22px'");
|
||||
assert.notEqual(big, -1, 'the large safety-code display is gone — this guard now protects nothing');
|
||||
assert.ok(app.slice(big, big + 400).includes("dir: 'ltr'"),
|
||||
'the large safety-code display must be pinned left-to-right');
|
||||
|
||||
// The group code, wherever it is put on screen. A plain `group.sasCode` also appears
|
||||
// in guard conditions, which render nothing — so the window looks both ways and only
|
||||
// the sites that are actually inside an element have to carry the direction.
|
||||
|
||||
+19
-19
@@ -30,18 +30,18 @@
|
||||
|
||||
<!-- PWA Manifest -->
|
||||
<link rel="manifest" href="/uk/manifest.json">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
|
||||
|
||||
<!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html>
|
||||
from localStorage while the parser is still in <head>, so the page paints in the
|
||||
chosen theme on the first frame instead of flashing dark and correcting itself.
|
||||
Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). -->
|
||||
<script src="/src/scripts/theme-boot.js?v=1790225533889"></script>
|
||||
<script src="/src/scripts/theme-boot.js?v=1790540575787"></script>
|
||||
|
||||
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
|
||||
not replayed, so the listener has to exist before Chrome decides the page
|
||||
is installable. Deferred/module scripts are already too late. -->
|
||||
<script src="/src/scripts/pwa-install-capture.js?v=1790225533889"></script>
|
||||
<script src="/src/scripts/pwa-install-capture.js?v=1790540575787"></script>
|
||||
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
@@ -116,7 +116,7 @@
|
||||
<link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png">
|
||||
|
||||
<!-- Apple Touch Icons -->
|
||||
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790225533889">
|
||||
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790540575787">
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png">
|
||||
@@ -125,7 +125,7 @@
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790225533889">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790540575787">
|
||||
|
||||
<!-- Microsoft Tiles -->
|
||||
<meta name="msapplication-TileColor" content="#ff6b35">
|
||||
@@ -264,7 +264,7 @@
|
||||
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
|
||||
both execute in document order after parsing, so React still runs before the
|
||||
app modules below, but the parser / first paint is no longer blocked. -->
|
||||
<script defer src="/config/ice-servers.js?v=1790225533889"></script>
|
||||
<script defer src="/config/ice-servers.js?v=1790540575787"></script>
|
||||
<script defer src="/libs/react/react.production.min.js"></script>
|
||||
<script defer src="/libs/react-dom/react-dom.production.min.js"></script>
|
||||
<!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only —
|
||||
@@ -278,8 +278,8 @@
|
||||
the end of <body>: 21 KB of CSS in total, but eight round trips before the
|
||||
browser could paint, which GTmetrix measured at 441 ms on a throttled mobile
|
||||
connection. Bytes were never the problem; requests were. -->
|
||||
<link rel="stylesheet" href="/assets/app.css?v=1790225533889">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
|
||||
<link rel="stylesheet" href="/assets/app.css?v=1790540575787">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
|
||||
<!-- Preload only the fonts needed for first paint. Inter is one variable file that
|
||||
answers for every weight, so there is one to preload rather than the two of five
|
||||
copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and
|
||||
@@ -287,19 +287,19 @@
|
||||
<link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
|
||||
<script defer src="/src/scripts/load-async-css.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/load-async-css.js?v=1790540575787"></script>
|
||||
<noscript>
|
||||
<link rel="stylesheet" href="/libs/prism/prism.css">
|
||||
</noscript>
|
||||
<script defer src="/src/scripts/fa-check.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/fa-check.js?v=1790540575787"></script>
|
||||
<!-- This locale's dictionary, registered before anything can ask for a string.
|
||||
Only the default locale's is bundled (src/i18n/index.js imports it, because t()
|
||||
falls back to it); every other page loads its own here instead of all thirteen
|
||||
riding along inside dist/app.js. Absent on the default locale's own page. -->
|
||||
<script type="module" src="/src/i18n/dict/uk.js?v=1790225533889"></script>
|
||||
<script type="module" src="/src/i18n/dict/uk.js?v=1790540575787"></script>
|
||||
<!-- Update Manager - система принудительного обновления -->
|
||||
<script defer src="/src/utils/updateManager.js?v=1790225533889"></script>
|
||||
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script>
|
||||
<script defer src="/src/utils/updateManager.js?v=1790540575787"></script>
|
||||
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790540575787"></script>
|
||||
<!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the
|
||||
app has mounted and the browser is idle, so the 142 KB it weighs is off the
|
||||
first load. src/components/QRScanner.js used to be here too and registered
|
||||
@@ -487,12 +487,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="/dist/app-boot.js?v=1790225533889"></script>
|
||||
<script type="module" src="/dist/app.js?v=1790225533889"></script>
|
||||
<script type="module" src="/dist/app-boot.js?v=1790540575787"></script>
|
||||
<script type="module" src="/dist/app.js?v=1790540575787"></script>
|
||||
|
||||
<script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script>
|
||||
<script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script>
|
||||
<script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script>
|
||||
<script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/pwa-register.js?v=1790540575787"></script>
|
||||
<script src="/src/pwa/install-prompt.js?v=1790540575787" type="module"></script>
|
||||
<script src="/src/pwa/pwa-manager.js?v=1790540575787" type="module"></script>
|
||||
<script defer src="/src/scripts/pwa-offline-test.js?v=1790540575787"></script>
|
||||
</body>
|
||||
</html>
|
||||
+19
-19
@@ -30,18 +30,18 @@
|
||||
|
||||
<!-- PWA Manifest -->
|
||||
<link rel="manifest" href="/ur/manifest.json">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
|
||||
|
||||
<!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html>
|
||||
from localStorage while the parser is still in <head>, so the page paints in the
|
||||
chosen theme on the first frame instead of flashing dark and correcting itself.
|
||||
Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). -->
|
||||
<script src="/src/scripts/theme-boot.js?v=1790225533889"></script>
|
||||
<script src="/src/scripts/theme-boot.js?v=1790540575787"></script>
|
||||
|
||||
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
|
||||
not replayed, so the listener has to exist before Chrome decides the page
|
||||
is installable. Deferred/module scripts are already too late. -->
|
||||
<script src="/src/scripts/pwa-install-capture.js?v=1790225533889"></script>
|
||||
<script src="/src/scripts/pwa-install-capture.js?v=1790540575787"></script>
|
||||
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
@@ -116,7 +116,7 @@
|
||||
<link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png">
|
||||
|
||||
<!-- Apple Touch Icons -->
|
||||
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790225533889">
|
||||
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790540575787">
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png">
|
||||
@@ -125,7 +125,7 @@
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790225533889">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790540575787">
|
||||
|
||||
<!-- Microsoft Tiles -->
|
||||
<meta name="msapplication-TileColor" content="#ff6b35">
|
||||
@@ -264,7 +264,7 @@
|
||||
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
|
||||
both execute in document order after parsing, so React still runs before the
|
||||
app modules below, but the parser / first paint is no longer blocked. -->
|
||||
<script defer src="/config/ice-servers.js?v=1790225533889"></script>
|
||||
<script defer src="/config/ice-servers.js?v=1790540575787"></script>
|
||||
<script defer src="/libs/react/react.production.min.js"></script>
|
||||
<script defer src="/libs/react-dom/react-dom.production.min.js"></script>
|
||||
<!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only —
|
||||
@@ -278,8 +278,8 @@
|
||||
the end of <body>: 21 KB of CSS in total, but eight round trips before the
|
||||
browser could paint, which GTmetrix measured at 441 ms on a throttled mobile
|
||||
connection. Bytes were never the problem; requests were. -->
|
||||
<link rel="stylesheet" href="/assets/app.css?v=1790225533889">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
|
||||
<link rel="stylesheet" href="/assets/app.css?v=1790540575787">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
|
||||
<!-- Preload only the fonts needed for first paint. Inter is one variable file that
|
||||
answers for every weight, so there is one to preload rather than the two of five
|
||||
copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and
|
||||
@@ -287,19 +287,19 @@
|
||||
<link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
|
||||
<script defer src="/src/scripts/load-async-css.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/load-async-css.js?v=1790540575787"></script>
|
||||
<noscript>
|
||||
<link rel="stylesheet" href="/libs/prism/prism.css">
|
||||
</noscript>
|
||||
<script defer src="/src/scripts/fa-check.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/fa-check.js?v=1790540575787"></script>
|
||||
<!-- This locale's dictionary, registered before anything can ask for a string.
|
||||
Only the default locale's is bundled (src/i18n/index.js imports it, because t()
|
||||
falls back to it); every other page loads its own here instead of all thirteen
|
||||
riding along inside dist/app.js. Absent on the default locale's own page. -->
|
||||
<script type="module" src="/src/i18n/dict/ur.js?v=1790225533889"></script>
|
||||
<script type="module" src="/src/i18n/dict/ur.js?v=1790540575787"></script>
|
||||
<!-- Update Manager - система принудительного обновления -->
|
||||
<script defer src="/src/utils/updateManager.js?v=1790225533889"></script>
|
||||
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script>
|
||||
<script defer src="/src/utils/updateManager.js?v=1790540575787"></script>
|
||||
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790540575787"></script>
|
||||
<!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the
|
||||
app has mounted and the browser is idle, so the 142 KB it weighs is off the
|
||||
first load. src/components/QRScanner.js used to be here too and registered
|
||||
@@ -487,12 +487,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="/dist/app-boot.js?v=1790225533889"></script>
|
||||
<script type="module" src="/dist/app.js?v=1790225533889"></script>
|
||||
<script type="module" src="/dist/app-boot.js?v=1790540575787"></script>
|
||||
<script type="module" src="/dist/app.js?v=1790540575787"></script>
|
||||
|
||||
<script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script>
|
||||
<script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script>
|
||||
<script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script>
|
||||
<script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/pwa-register.js?v=1790540575787"></script>
|
||||
<script src="/src/pwa/install-prompt.js?v=1790540575787" type="module"></script>
|
||||
<script src="/src/pwa/pwa-manager.js?v=1790540575787" type="module"></script>
|
||||
<script defer src="/src/scripts/pwa-offline-test.js?v=1790540575787"></script>
|
||||
</body>
|
||||
</html>
|
||||
+19
-19
@@ -30,18 +30,18 @@
|
||||
|
||||
<!-- PWA Manifest -->
|
||||
<link rel="manifest" href="/zh/manifest.json">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
|
||||
|
||||
<!-- Blocking, and above the stylesheet on purpose: this stamps data-theme on <html>
|
||||
from localStorage while the parser is still in <head>, so the page paints in the
|
||||
chosen theme on the first frame instead of flashing dark and correcting itself.
|
||||
Cannot be inline (CSP is script-src 'self') and cannot be a module (deferred). -->
|
||||
<script src="/src/scripts/theme-boot.js?v=1790225533889"></script>
|
||||
<script src="/src/scripts/theme-boot.js?v=1790540575787"></script>
|
||||
|
||||
<!-- Blocking, and in <head> on purpose: beforeinstallprompt fires once and is
|
||||
not replayed, so the listener has to exist before Chrome decides the page
|
||||
is installable. Deferred/module scripts are already too late. -->
|
||||
<script src="/src/scripts/pwa-install-capture.js?v=1790225533889"></script>
|
||||
<script src="/src/scripts/pwa-install-capture.js?v=1790540575787"></script>
|
||||
|
||||
<!-- PWA Meta Tags -->
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
@@ -116,7 +116,7 @@
|
||||
<link rel="apple-touch-startup-image" media="screen and (device-width: 744px) and (device-height: 1133px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/logo/splash/splash_screens/8.3__iPad_Mini_portrait.png">
|
||||
|
||||
<!-- Apple Touch Icons -->
|
||||
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790225533889">
|
||||
<link rel="apple-touch-icon" href="/logo/icon-180x180.png?v=1790540575787">
|
||||
<link rel="apple-touch-icon" sizes="57x57" href="/logo/icon-57x57.png">
|
||||
<link rel="apple-touch-icon" sizes="60x60" href="/logo/icon-60x60.png">
|
||||
<link rel="apple-touch-icon" sizes="72x72" href="/logo/icon-72x72.png">
|
||||
@@ -125,7 +125,7 @@
|
||||
<link rel="apple-touch-icon" sizes="120x120" href="/logo/icon-120x120.png">
|
||||
<link rel="apple-touch-icon" sizes="144x144" href="/logo/icon-144x144.png">
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/logo/icon-152x152.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790225533889">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/logo/icon-180x180.png?v=1790540575787">
|
||||
|
||||
<!-- Microsoft Tiles -->
|
||||
<meta name="msapplication-TileColor" content="#ff6b35">
|
||||
@@ -264,7 +264,7 @@
|
||||
<!-- Render-blocking JS is deferred: classic deferred scripts and module scripts
|
||||
both execute in document order after parsing, so React still runs before the
|
||||
app modules below, but the parser / first paint is no longer blocked. -->
|
||||
<script defer src="/config/ice-servers.js?v=1790225533889"></script>
|
||||
<script defer src="/config/ice-servers.js?v=1790540575787"></script>
|
||||
<script defer src="/libs/react/react.production.min.js"></script>
|
||||
<script defer src="/libs/react-dom/react-dom.production.min.js"></script>
|
||||
<!-- Prism syntax highlighting (vendored, offline). Tokenizes code as TEXT only —
|
||||
@@ -278,8 +278,8 @@
|
||||
the end of <body>: 21 KB of CSS in total, but eight round trips before the
|
||||
browser could paint, which GTmetrix measured at 441 ms on a throttled mobile
|
||||
connection. Bytes were never the problem; requests were. -->
|
||||
<link rel="stylesheet" href="/assets/app.css?v=1790225533889">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790225533889">
|
||||
<link rel="stylesheet" href="/assets/app.css?v=1790540575787">
|
||||
<link rel="icon" type="image/x-icon" href="/logo/favicon.ico?v=1790540575787">
|
||||
<!-- Preload only the fonts needed for first paint. Inter is one variable file that
|
||||
answers for every weight, so there is one to preload rather than the two of five
|
||||
copies this used to name. fa-solid covers the bulk of UI icons; fa-regular and
|
||||
@@ -287,19 +287,19 @@
|
||||
<link rel="preload" href="/assets/fontawesome/webfonts/fa-solid-900.subset.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="/assets/fonts/inter/files/inter-latin.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<!-- Non-critical CSS (FontAwesome ~102KB, Prism) loaded async — no longer blocks paint. -->
|
||||
<script defer src="/src/scripts/load-async-css.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/load-async-css.js?v=1790540575787"></script>
|
||||
<noscript>
|
||||
<link rel="stylesheet" href="/libs/prism/prism.css">
|
||||
</noscript>
|
||||
<script defer src="/src/scripts/fa-check.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/fa-check.js?v=1790540575787"></script>
|
||||
<!-- This locale's dictionary, registered before anything can ask for a string.
|
||||
Only the default locale's is bundled (src/i18n/index.js imports it, because t()
|
||||
falls back to it); every other page loads its own here instead of all thirteen
|
||||
riding along inside dist/app.js. Absent on the default locale's own page. -->
|
||||
<script type="module" src="/src/i18n/dict/zh.js?v=1790225533889"></script>
|
||||
<script type="module" src="/src/i18n/dict/zh.js?v=1790540575787"></script>
|
||||
<!-- Update Manager - система принудительного обновления -->
|
||||
<script defer src="/src/utils/updateManager.js?v=1790225533889"></script>
|
||||
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790225533889"></script>
|
||||
<script defer src="/src/utils/updateManager.js?v=1790540575787"></script>
|
||||
<script type="module" src="/src/components/UpdateChecker.jsx?v=1790540575787"></script>
|
||||
<!-- /dist/qr-local.js is deliberately absent: app-boot.js fetches it once the
|
||||
app has mounted and the browser is idle, so the 142 KB it weighs is off the
|
||||
first load. src/components/QRScanner.js used to be here too and registered
|
||||
@@ -487,12 +487,12 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="module" src="/dist/app-boot.js?v=1790225533889"></script>
|
||||
<script type="module" src="/dist/app.js?v=1790225533889"></script>
|
||||
<script type="module" src="/dist/app-boot.js?v=1790540575787"></script>
|
||||
<script type="module" src="/dist/app.js?v=1790540575787"></script>
|
||||
|
||||
<script defer src="/src/scripts/pwa-register.js?v=1790225533889"></script>
|
||||
<script src="/src/pwa/install-prompt.js?v=1790225533889" type="module"></script>
|
||||
<script src="/src/pwa/pwa-manager.js?v=1790225533889" type="module"></script>
|
||||
<script defer src="/src/scripts/pwa-offline-test.js?v=1790225533889"></script>
|
||||
<script defer src="/src/scripts/pwa-register.js?v=1790540575787"></script>
|
||||
<script src="/src/pwa/install-prompt.js?v=1790540575787" type="module"></script>
|
||||
<script src="/src/pwa/pwa-manager.js?v=1790540575787" type="module"></script>
|
||||
<script defer src="/src/scripts/pwa-offline-test.js?v=1790540575787"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user