feat(web): iPhone PWA fixes (M1) + warm RTO redesign (M2)
M1 — PWA mechanics: - Generate real app icons (apple-touch-icon + manifest 192/512/maskable) via pure-stdlib gen_icons.py; iOS uses apple-touch-icon, not manifest icons. - viewport-fit=cover + env(safe-area-inset-*) on header/input/menu so content clears the notch and home indicator. - Dynamic height pinned to the VisualViewport (height + offsetTop, re-measured across the keyboard animation) so the input stays above the iOS keyboard; 100dvh fallback. Kills the squish/gap bugs in standalone mode. - overscroll containment; flesh out manifest (scope, portrait, maskable). M2 — visual redesign: - Realign style.css to the warm low-glow RTO palette already used by the standalone pages (#0e0e0e panels, #2a1d12 borders); remove the neon saturated-orange borders and ~15 glow shadows. - Reserve filled accent for one element (Send); glow only on status pulse + input focus. Flat warm message bubbles with tail corners. - Reclaim the mobile header into [≡] Lyra · [status dot]; drop the redundant status bar (relay status now the header dot, updated in checkHealth). - prefers-reduced-motion support; fix undefined var(--text); real light-mode tokens. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,10 +5,14 @@
|
||||
<title>Lyra Core Chat</title>
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
<!-- PWA -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="apple-mobile-web-app-title" content="Lyra" />
|
||||
<meta name="theme-color" content="#070707" />
|
||||
<link rel="apple-touch-icon" href="apple-touch-icon.png" />
|
||||
<link rel="icon" type="image/png" href="icon-192.png" />
|
||||
<link rel="manifest" href="manifest.json" />
|
||||
|
||||
</head>
|
||||
@@ -55,6 +59,8 @@
|
||||
<span></span>
|
||||
<span></span>
|
||||
</button>
|
||||
<span class="brand">Lyra</span>
|
||||
<span class="brand-dot" id="brandDot" title="Relay status"></span>
|
||||
<label for="mode">Mode:</label>
|
||||
<select id="mode">
|
||||
<option value="standard">Standard</option>
|
||||
@@ -412,16 +418,56 @@
|
||||
if (resp.ok) {
|
||||
document.getElementById("status-dot").className = "dot ok";
|
||||
document.getElementById("status-text").textContent = "Relay Online";
|
||||
document.getElementById("brandDot").className = "brand-dot ok";
|
||||
} else {
|
||||
throw new Error("Bad status");
|
||||
}
|
||||
} catch (err) {
|
||||
document.getElementById("status-dot").className = "dot fail";
|
||||
document.getElementById("status-text").textContent = "Relay Offline";
|
||||
document.getElementById("brandDot").className = "brand-dot fail";
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
// --- PWA: track the *visible* viewport height so the layout follows the
|
||||
// iOS keyboard and the dynamic Safari toolbars (keeps the input bar visible
|
||||
// instead of hiding behind the keyboard). Falls back to 100dvh via CSS.
|
||||
function setAppHeight() {
|
||||
const vv = window.visualViewport;
|
||||
const h = (vv && vv.height) || window.innerHeight;
|
||||
const off = (vv && vv.offsetTop) || 0;
|
||||
const root = document.documentElement.style;
|
||||
root.setProperty("--app-height", h + "px");
|
||||
// iOS pans the visual viewport when the keyboard opens; follow its top
|
||||
// edge so the pinned #chat sits exactly in the visible area.
|
||||
root.setProperty("--app-offset", off + "px");
|
||||
}
|
||||
// Re-measure across the keyboard animation: iOS reports a stale (too-short)
|
||||
// height mid-animation, so sample a few times until it settles.
|
||||
function nudgeAppHeight() {
|
||||
setAppHeight();
|
||||
[50, 150, 300, 550].forEach((t) => setTimeout(setAppHeight, t));
|
||||
}
|
||||
setAppHeight();
|
||||
if (window.visualViewport) {
|
||||
window.visualViewport.addEventListener("resize", nudgeAppHeight);
|
||||
window.visualViewport.addEventListener("scroll", setAppHeight);
|
||||
}
|
||||
window.addEventListener("resize", nudgeAppHeight);
|
||||
window.addEventListener("orientationchange", nudgeAppHeight);
|
||||
|
||||
// Keep the latest message in view when the keyboard opens/closes.
|
||||
const userInputEl = document.getElementById("userInput");
|
||||
userInputEl.addEventListener("focus", () => {
|
||||
nudgeAppHeight();
|
||||
setTimeout(() => {
|
||||
const m = document.getElementById("messages");
|
||||
m.scrollTo({ top: m.scrollHeight, behavior: "smooth" });
|
||||
}, 350);
|
||||
});
|
||||
userInputEl.addEventListener("blur", nudgeAppHeight);
|
||||
|
||||
// Mobile Menu Toggle
|
||||
const hamburgerMenu = document.getElementById("hamburgerMenu");
|
||||
const mobileMenu = document.getElementById("mobileMenu");
|
||||
|
||||
Reference in New Issue
Block a user