mavweb: ui design system refresh — shared nav, cards, component classes
- Rewrote ui.css with design tokens, card/btn/badge/dot components - All pages wrapped in <main class=page> with max-width container - Replaced inline <style> blocks with ui.css classes - passkey page now uses shared nav.site template - PWA voice page unified under shared nav.site (no more separate tab nav) - Inline lang toggle moved from nav to voice page body
This commit is contained in:
@@ -5,58 +5,57 @@
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
|
||||
<meta name="theme-color" content="#111">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<link rel=stylesheet href=/ui.css>
|
||||
<script defer src="/app.js"></script>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
html,body{height:100%;background:#111;color:#ddd;font-family:system-ui,-apple-system,sans-serif}
|
||||
body{display:flex;flex-direction:column}
|
||||
/* Voice page layout — the shared nav.site handles everything above */
|
||||
html,body{height:100%}
|
||||
body{display:flex;flex-direction:column;padding:1rem}
|
||||
.voice{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1.5rem;padding:1rem 0}
|
||||
|
||||
nav{display:flex;align-items:stretch;background:#1a1a2e;border-bottom:1px solid #333;flex-shrink:0}
|
||||
/* min-width:0 lets the tabs shrink below their text's min-content width —
|
||||
without it they push the lang toggle past the right edge on phones */
|
||||
nav button{flex:1 1 0;min-width:0;overflow:hidden;white-space:nowrap;padding:.6rem .2rem;background:none;border:none;color:#666;font-size:.85rem;cursor:pointer;font-family:inherit;letter-spacing:.05em;text-transform:uppercase;transition:color .15s;-webkit-tap-highlight-color:transparent}
|
||||
nav button.active{color:#00aaff;border-bottom:2px solid #00aaff}
|
||||
nav button:hover{color:#ddd}
|
||||
nav .lang{flex:0 0 auto;display:flex;align-items:center;padding:.6rem .5rem;font-size:.75rem;color:#555;cursor:pointer;letter-spacing:.1em}
|
||||
nav .lang.active{color:#00aaff}
|
||||
/* Voice PTT button */
|
||||
#btn{width:140px;height:140px;border-radius:50%;border:4px solid var(--accent);background:var(--panel);color:var(--accent);font-size:1rem;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all var(--transition);user-select:none;-webkit-tap-highlight-color:transparent;touch-action:manipulation}
|
||||
#btn:active,#btn.active{background:rgba(0,170,255,.13);border-color:var(--ok);color:var(--ok);transform:scale(1.05)}
|
||||
#btn:disabled{opacity:.3;border-color:var(--border)}
|
||||
|
||||
.tab{display:none;flex-direction:column;align-items:center;flex:1;overflow:auto;padding:1rem}
|
||||
.tab.active{display:flex}
|
||||
#tab-voice{gap:2rem}
|
||||
#tab-dash{padding:0}
|
||||
|
||||
h1{font-size:1.2rem;font-weight:400;color:#888;letter-spacing:.1em;text-transform:uppercase}
|
||||
#status{font-size:.85rem;color:#666;min-height:1.2em}
|
||||
#btn{width:140px;height:140px;border-radius:50%;border:4px solid #00aaff;background:#1a1a2e;color:#00aaff;font-size:1rem;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .15s;user-select:none;-webkit-tap-highlight-color:transparent;touch-action:manipulation}
|
||||
#btn:active,#btn.active{background:#00aaff22;border-color:#00ff88;color:#00ff88;transform:scale(1.05)}
|
||||
#btn:disabled{opacity:.3;border-color:#444}
|
||||
#log{width:100%;max-width:480px;max-height:40vh;overflow-y:auto;font-size:.8rem;color:#666;line-height:1.6;padding:.5rem;border-top:1px solid #222;margin-top:1rem}
|
||||
#log .reply{color:#8f8}
|
||||
#log .error{color:#f88}
|
||||
#status{font-size:var(--fs-sm);color:var(--muted);min-height:1.2em}
|
||||
#log{width:100%;max-width:480px;max-height:40vh;overflow-y:auto;font-size:var(--fs-sm);color:var(--muted);line-height:1.6;padding:var(--space-sm);border-top:1px solid var(--line)}
|
||||
#log .reply{color:var(--ok)}
|
||||
#log .error{color:var(--err)}
|
||||
#log .push{color:#88f}
|
||||
|
||||
#dash-frame{width:100%;flex:1;border:none;background:#111;min-height:0}
|
||||
|
||||
#cheat{width:100%;max-width:480px;font-size:.8rem;border-top:1px solid #222;color:#888}
|
||||
#cheat summary{cursor:pointer;padding:.5rem 0;color:#00aaff;letter-spacing:.05em;text-transform:uppercase;font-size:.75rem;user-select:none}
|
||||
/* Cheatsheet */
|
||||
#cheat{width:100%;max-width:480px;font-size:var(--fs-sm);border-top:1px solid var(--line);color:var(--muted)}
|
||||
#cheat summary{cursor:pointer;padding:var(--space-sm) 0;color:var(--accent);letter-spacing:.05em;text-transform:uppercase;font-size:var(--fs-xs);user-select:none}
|
||||
#cheat dl{display:grid;grid-template-columns:auto 1fr;gap:.35rem .8rem;padding:.25rem 0 .5rem}
|
||||
#cheat dt{color:#00ff88;font-weight:600;white-space:nowrap}
|
||||
#cheat dd{color:#aaa;line-height:1.5}
|
||||
#cheat dd span{color:#555}
|
||||
#cheat dt{color:var(--ok);font-weight:600;white-space:nowrap}
|
||||
#cheat dd{color:var(--soft);line-height:1.5}
|
||||
#cheat dd span{color:var(--dim)}
|
||||
.cheat-en{display:revert}
|
||||
.cheat-ru{display:none}
|
||||
html[data-lang=ru] .cheat-en { display: none; }
|
||||
html[data-lang=ru] .cheat-ru { display: revert; }
|
||||
html[data-lang=ru] .cheat-en{display:none}
|
||||
html[data-lang=ru] .cheat-ru{display:revert}
|
||||
|
||||
/* Lang toggle inline (not in nav) */
|
||||
.lang-toggle{display:flex;gap:0;justify-content:center;margin-top:1rem}
|
||||
.lang-toggle span{font-size:var(--fs-xs);color:var(--dim);cursor:pointer;padding:.2rem .5rem;letter-spacing:.1em;text-transform:uppercase;border:1px solid var(--border);transition:color var(--transition);user-select:none}
|
||||
.lang-toggle span:first-child{border-radius:var(--radius-sm) 0 0 var(--radius-sm);border-right:none}
|
||||
.lang-toggle span:last-child{border-radius:0 var(--radius-sm) var(--radius-sm) 0;border-left:none}
|
||||
.lang-toggle span.active{color:var(--accent);border-color:var(--accent);background:rgba(0,170,255,.08)}
|
||||
.lang-toggle span:hover{color:var(--fg)}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<button class="active" data-tab="voice">Voice</button>
|
||||
<button data-tab="dash">Dash</button>
|
||||
<span class="lang active" data-lang="ru">RU</span>
|
||||
<span class="lang" data-lang="en">EN</span>
|
||||
<nav class=site>
|
||||
<a href=/ class=active>voice</a>
|
||||
<a href=/dash>dash</a>
|
||||
<a href=/history>history</a>
|
||||
<a href=/trace>trace</a>
|
||||
<a href=/notifications>notifications</a>
|
||||
<a href=/tools>tools</a>
|
||||
<a href=/auth/passkey>passkey</a>
|
||||
</nav>
|
||||
<div id="tab-voice" class="tab active">
|
||||
<div class=voice>
|
||||
<h1>Maven Voice</h1>
|
||||
<div id="status">tap & hold to speak</div>
|
||||
<button id="btn" type="button">🎙</button>
|
||||
@@ -84,36 +83,23 @@ html[data-lang=ru] .cheat-ru { display: revert; }
|
||||
<dd class="cheat-en">quiet mode on · system load</dd>
|
||||
</dl>
|
||||
</details>
|
||||
</div>
|
||||
<div id="tab-dash" class="tab">
|
||||
<iframe id="dash-frame" src="/dash"></iframe>
|
||||
<div class=lang-toggle>
|
||||
<span class="active" data-lang="ru">RU</span>
|
||||
<span data-lang="en">EN</span>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function() {
|
||||
// Read lang from URL param or default to ru
|
||||
var lang = new URLSearchParams(window.location.search).get('lang') || 'ru';
|
||||
if (lang !== 'en') lang = 'ru';
|
||||
document.documentElement.setAttribute('data-lang', lang);
|
||||
|
||||
// Nav buttons
|
||||
document.querySelectorAll('nav button[data-tab]').forEach(function(btn) {
|
||||
btn.addEventListener('click', function() {
|
||||
document.querySelectorAll('nav button[data-tab]').forEach(function(b) { b.classList.remove('active'); });
|
||||
document.querySelectorAll('.tab').forEach(function(t) { t.classList.remove('active'); });
|
||||
btn.classList.add('active');
|
||||
var tab = document.getElementById('tab-' + btn.dataset.tab);
|
||||
if (tab) tab.classList.add('active');
|
||||
});
|
||||
});
|
||||
|
||||
// Lang toggle
|
||||
document.querySelectorAll('nav .lang').forEach(function(el) {
|
||||
document.querySelectorAll('.lang-toggle span').forEach(function(el) {
|
||||
el.addEventListener('click', function() {
|
||||
var l = el.dataset.lang;
|
||||
document.documentElement.setAttribute('data-lang', l);
|
||||
document.querySelectorAll('nav .lang').forEach(function(b) { b.classList.remove('active'); });
|
||||
document.querySelectorAll('.lang-toggle span').forEach(function(b) { b.classList.remove('active'); });
|
||||
el.classList.add('active');
|
||||
// Update URL without reload
|
||||
var url = new URL(window.location);
|
||||
url.searchParams.set('lang', l);
|
||||
history.replaceState(null, '', url);
|
||||
|
||||
Reference in New Issue
Block a user