diff --git a/README.md b/README.md index da255d7..f3cb863 100644 --- a/README.md +++ b/README.md @@ -343,7 +343,7 @@ services: - После создания: **QR** на share-link, иконки **скачать QR (PNG)** и **Web Share** (если есть `navigator.share`) под QR; отдельные кнопки Copy для ссылки / токена / пароля; пароль не советуется слать в той же переписке. - Create: счётчик размера `≈ used / max`; **число открытий** 1–3 (потолок в админке); опционально **«доступно с»** (дата + время); человеческие TTL (в т.ч. «до вечера»). - Unwrap: Enter в поле пароля; focus+select при ошибках пароля; zip all; trust-строка; экран **ещё недоступно** до `available_from`; при N>1 ciphertext остаётся до последнего открытия. -- Картинки: lightbox. Тема: `prefers-color-scheme` при первом визите; haptic на Copy. PWA manifest + кнопка **На экран Домой** (Chrome install / iOS подсказка). +- Картинки: lightbox. Тема: `prefers-color-scheme` при первом визите; haptic на Copy. PWA manifest. - Страница [`/verify`](/verify) — как проверить ZK-модель. - UI EN/RU. Статика с `?v=версия.mtime`. diff --git a/app/static/css/app.css b/app/static/css/app.css index f4c2ca9..538b51a 100644 --- a/app/static/css/app.css +++ b/app/static/css/app.css @@ -267,34 +267,6 @@ html[data-theme="dark"] .icon-btn:hover { color: var(--muted); line-height: 1.5; } -#install-app { - position: relative; - overflow: visible; -} -#install-app .ui-tooltip { - top: calc(100% + 0.5rem); - bottom: auto; - left: 50%; - right: auto; - transform: translateX(-50%) translateY(-4px); - white-space: nowrap; -} -#install-app .ui-tooltip::after { - top: auto; - bottom: 100%; - left: 50%; - right: auto; - transform: translateX(-50%); - border: 5px solid transparent; - border-bottom-color: var(--line); - border-top-color: transparent; -} -#install-app:hover .ui-tooltip, -#install-app:focus-visible .ui-tooltip { - opacity: 1; - visibility: visible; - transform: translateX(-50%); -} .datetime-split { display: grid; grid-template-columns: 1fr 1fr; @@ -1141,6 +1113,63 @@ html[data-theme="light"] .about-version { font-size: 0.92rem; line-height: 1.5; } +.about-verify-link { + display: flex; + align-items: center; + gap: 0.75rem; + margin-top: 0.25rem; + padding: 0.75rem 0.85rem; + border-radius: 12px; + border: 1px solid rgba(61, 214, 198, 0.28); + background: linear-gradient(135deg, rgba(61, 214, 198, 0.1), rgba(110, 168, 255, 0.08)); + color: var(--text); + text-decoration: none; + transition: border-color 0.15s, background 0.15s, transform 0.15s; +} +.about-verify-link:hover, +.about-verify-link:focus-visible { + border-color: rgba(110, 168, 255, 0.45); + background: linear-gradient(135deg, rgba(61, 214, 198, 0.14), rgba(110, 168, 255, 0.14)); + outline: none; + transform: translateY(-1px); +} +.about-verify-icon { + flex-shrink: 0; + width: 2.25rem; + height: 2.25rem; + display: grid; + place-items: center; + border-radius: 10px; + background: rgba(61, 214, 198, 0.14); + color: var(--accent); + font-size: 0.95rem; +} +.about-verify-text { + display: grid; + gap: 0.15rem; + min-width: 0; + flex: 1; +} +.about-verify-text strong { + font-size: 0.92rem; + font-weight: 650; + color: var(--text); +} +.about-verify-text span { + font-size: 0.78rem; + color: var(--muted); + line-height: 1.35; +} +.about-verify-arrow { + flex-shrink: 0; + color: var(--accent-2); + font-size: 0.85rem; + opacity: 0.85; +} +html[data-theme="light"] .about-verify-link { + border-color: rgba(47, 111, 237, 0.22); + background: linear-gradient(135deg, rgba(14, 160, 140, 0.08), rgba(47, 111, 237, 0.08)); +} .about-modal-panel .modal-actions { justify-content: flex-end; } diff --git a/app/static/js/i18n.js b/app/static/js/i18n.js index bd85ec3..2d541f4 100644 --- a/app/static/js/i18n.js +++ b/app/static/js/i18n.js @@ -106,11 +106,8 @@ "verify.s5.title": "Available from", "verify.s5.body": "If \"available from\" is set, unwrap is rejected until that time. After that, normal open/expiry rules apply.", "verify.createCta": "Create a wrap", - "about.verifyLink": "How to verify →", - "footer.verify": "How to verify", - "install.aria": "Add to Home Screen", - "install.tip": "Install Wrapped", - "install.iosTip": "Share → Add to Home Screen", + "about.verifyLink": "How to verify", + "about.verifyHint": "What the server sees and how the #key works", "admin.limits.maxOpens": "Max opens per wrap", "admin.limits.maxOpensHint": "Ceiling for create UI (1–10). Default: 3.", "create.ttl.default": "Default ({seconds} s)", @@ -413,11 +410,8 @@ "verify.s5.title": "Доступно с", "verify.s5.body": "Если задано «доступно с», unwrap отклоняется до этого момента. Дальше действуют обычные правила открытий и срока.", "verify.createCta": "Создать wrap", - "about.verifyLink": "Как проверить →", - "footer.verify": "Как проверить", - "install.aria": "На экран «Домой»", - "install.tip": "Установить Wrapped", - "install.iosTip": "Поделиться → На экран «Домой»", + "about.verifyLink": "Как проверить", + "about.verifyHint": "Что видит сервер и как работает ключ в #…", "admin.limits.maxOpens": "Макс. открытий на wrap", "admin.limits.maxOpensHint": "Потолок для UI создания (1–10). По умолчанию: 3.", "create.ttl.default": "По умолчанию ({seconds} с)", diff --git a/app/static/js/install.js b/app/static/js/install.js deleted file mode 100644 index 1bf8c39..0000000 --- a/app/static/js/install.js +++ /dev/null @@ -1,60 +0,0 @@ -(() => { - const t = (k, vars) => window.WrappedI18n?.t(k, vars) || k; - const btn = document.getElementById("install-app"); - if (!btn) return; - - let deferredPrompt = null; - const isIos = - /iphone|ipad|ipod/i.test(navigator.userAgent) || - (navigator.platform === "MacIntel" && navigator.maxTouchPoints > 1); - const isStandalone = - window.matchMedia("(display-mode: standalone)").matches || - window.navigator.standalone === true; - - function syncLabels() { - btn.setAttribute("aria-label", t("install.aria")); - const tip = btn.querySelector(".ui-tooltip"); - if (tip) tip.textContent = t(isIos ? "install.iosTip" : "install.tip"); - } - - function showBtn() { - if (isStandalone) { - btn.classList.add("hidden"); - return; - } - btn.classList.remove("hidden"); - } - - window.addEventListener("beforeinstallprompt", (e) => { - e.preventDefault(); - deferredPrompt = e; - showBtn(); - syncLabels(); - }); - - if (isIos && !isStandalone) { - showBtn(); - } - - btn.addEventListener("click", async () => { - if (deferredPrompt) { - deferredPrompt.prompt(); - try { - await deferredPrompt.userChoice; - } catch { - /* ignore */ - } - deferredPrompt = null; - return; - } - if (isIos) { - window.WrappedUI?.showBusy?.(t("install.iosTip")); - setTimeout(() => window.WrappedUI?.hideBusy?.(), 2200); - } - }); - - if (window.WrappedI18n?.onChange) { - window.WrappedI18n.onChange(syncLabels); - } - syncLabels(); -})(); diff --git a/app/templates/base.html b/app/templates/base.html index bca9658..5e1fe53 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -31,11 +31,6 @@
Можно отправить заметку или код, а также вложения: документы, архивы, скриншоты (drag-and-drop, выбор с диска или вставка из буфера). И текст, и файлы шифруются в браузере до загрузки — на сервер уходит только ciphertext.
Сервер никогда не видит plaintext: зашифрованные данные лежат на сервере до тех пор, пока получатель не откроет ссылку с ключом и не расшифрует пакет.
После успешной расшифровки копия на сервере уничтожается. Ключ шифрования живёт во фрагменте URL (#…) и не уходит на сервер вместе с запросом страницы. При желании wrap можно дополнительно защитить паролем.
- + + + + Как проверить + Что видит сервер и как работает ключ в #… + + +