pwa: add bilingual lang toggle to cheatsheet

Split cheatsheet into separate RU/EN <dd> elements with CSS class
toggle. Toggle buttons in nav bar (RU/EN). Language also controlled
by ?lang=ru|en query parameter (default: ru).

The rest of the PWA stays English; voice responses remain Russian.
This commit is contained in:
kami
2026-07-05 11:50:57 +04:00
parent 00a3bba9cb
commit c225ba37b2
+57 -16
View File
@@ -15,6 +15,8 @@ nav{display:flex;background:#1a1a2e;border-bottom:1px solid #333;flex-shrink:0}
nav button{flex:1;padding:.6rem;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{flex:1;padding:.6rem;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.active{color:#00aaff;border-bottom:2px solid #00aaff}
nav button:hover{color:#ddd} nav button:hover{color:#ddd}
nav .lang{flex:0 0 auto;padding:.6rem .8rem;font-size:.75rem;color:#555;cursor:pointer;letter-spacing:.1em}
nav .lang.active{color:#00aaff}
.tab{display:none;flex-direction:column;align-items:center;flex:1;overflow:auto;padding:1rem} .tab{display:none;flex-direction:column;align-items:center;flex:1;overflow:auto;padding:1rem}
.tab.active{display:flex} .tab.active{display:flex}
@@ -33,19 +35,24 @@ h1{font-size:1.2rem;font-weight:400;color:#888;letter-spacing:.1em;text-transfor
#dash-frame{width:100%;flex:1;border:none;background:#111;min-height:0} #dash-frame{width:100%;flex:1;border:none;background:#111;min-height:0}
/* cheatsheet — temporary reference while testing command lines */
#cheat{width:100%;max-width:480px;font-size:.8rem;border-top:1px solid #222;color:#888} #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} #cheat summary{cursor:pointer;padding:.5rem 0;color:#00aaff;letter-spacing:.05em;text-transform:uppercase;font-size:.75rem;user-select:none}
#cheat dl{display:grid;grid-template-columns:auto 1fr;gap:.35rem .8rem;padding:.25rem 0 .5rem} #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 dt{color:#00ff88;font-weight:600;white-space:nowrap}
#cheat dd{color:#aaa;line-height:1.5} #cheat dd{color:#aaa;line-height:1.5}
#cheat dd span{color:#555} #cheat dd span{color:#555}
.cheat-en{display:revert}
.cheat-ru{display:none}
html[data-lang=ru] .cheat-en { display: none; }
html[data-lang=ru] .cheat-ru { display: revert; }
</style> </style>
</head> </head>
<body> <body>
<nav> <nav>
<button class="active" data-tab="voice">Voice</button> <button class="active" data-tab="voice">Voice</button>
<button data-tab="dash">Dash</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> </nav>
<div id="tab-voice" class="tab active"> <div id="tab-voice" class="tab active">
<h1>Maven Voice</h1> <h1>Maven Voice</h1>
@@ -53,14 +60,26 @@ h1{font-size:1.2rem;font-weight:400;color:#888;letter-spacing:.1em;text-transfor
<button id="btn" type="button">&#x1f399;</button> <button id="btn" type="button">&#x1f399;</button>
<div id="log"></div> <div id="log"></div>
<details id="cheat"> <details id="cheat">
<summary>Cheatsheet · шпаргалка</summary> <summary>Cheatsheet</summary>
<dl> <dl>
<dt>Act</dt><dd>включи свет · сделай громче · <span>restart nginx</span></dd> <dt>Act</dt>
<dt>Reminder</dt><dd>напомни через 4 часа размяться · <span>remind me tomorrow at 8am to call the doctor</span></dd> <dd class="cheat-ru">включи свет · сделай громче</dd>
<dt>Fact</dt><dd>выпил кофе · вес 73 кг · <span>ran 3 kilometers</span></dd> <dd class="cheat-en">restart nginx · turn on the light</dd>
<dt>Note</dt><dd>запиши рецепт блинов · <span>note: add a backup job for the db</span></dd> <dt>Reminder</dt>
<dt>Query</dt><dd>какой сегодня день · какой прогноз погоды · <span>what version are you</span></dd> <dd class="cheat-ru">напомни через 4 часа размяться</dd>
<dt>System</dt><dd>включи тихий режим · как загрузка системы</dd> <dd class="cheat-en">remind me tomorrow at 8am to call the doctor</dd>
<dt>Fact</dt>
<dd class="cheat-ru">выпил кофе · вес 73 кг</dd>
<dd class="cheat-en">ran 3 kilometers</dd>
<dt>Note</dt>
<dd class="cheat-ru">запиши рецепт блинов</dd>
<dd class="cheat-en">note: add a backup job for the db</dd>
<dt>Query</dt>
<dd class="cheat-ru">какой сегодня день · какой прогноз погоды</dd>
<dd class="cheat-en">what version are you · what's the weather</dd>
<dt>System</dt>
<dd class="cheat-ru">включи тихий режим · как загрузка системы</dd>
<dd class="cheat-en">quiet mode on · system load</dd>
</dl> </dl>
</details> </details>
</div> </div>
@@ -68,15 +87,37 @@ h1{font-size:1.2rem;font-weight:400;color:#888;letter-spacing:.1em;text-transfor
<iframe id="dash-frame" src="/dash"></iframe> <iframe id="dash-frame" src="/dash"></iframe>
</div> </div>
<script> <script>
document.querySelectorAll('nav button').forEach(function(btn) { (function() {
btn.addEventListener('click', function() { // Read lang from URL param or default to ru
document.querySelectorAll('nav button').forEach(function(b) { b.classList.remove('active'); }); var lang = new URLSearchParams(window.location.search).get('lang') || 'ru';
document.querySelectorAll('.tab').forEach(function(t) { t.classList.remove('active'); }); if (lang !== 'en') lang = 'ru';
btn.classList.add('active'); document.documentElement.setAttribute('data-lang', lang);
var tab = document.getElementById('tab-' + btn.dataset.tab);
if (tab) tab.classList.add('active'); // 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) {
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'); });
el.classList.add('active');
// Update URL without reload
var url = new URL(window.location);
url.searchParams.set('lang', l);
history.replaceState(null, '', url);
});
});
})();
</script> </script>
</body> </body>
</html> </html>