Add auth modal: smart FIO/CDN input redirecting to code-server folder

This commit is contained in:
ask 2026-05-17 08:05:00 +05:00
parent 941abab31e
commit ccf82b5040

View File

@ -134,6 +134,123 @@ nav {
.nav-cta:hover { background: var(--blue-hover); }
/* ── AUTH (nav button + modal) ── */
.nav-cta-group { margin-left: auto; display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.nav-cdn-chip {
font-size: 0.75rem;
color: var(--text-55);
background: var(--hover);
padding: 4px 10px;
border-radius: 6px;
cursor: pointer;
user-select: none;
transition: background 150ms ease;
}
.nav-cdn-chip:hover { background: var(--hover-light); color: var(--negative); }
.nav-cdn-chip::after { content: ' ×'; opacity: 0.5; }
.modal-overlay {
position: fixed; inset: 0;
background: rgba(0,0,0,0.4);
display: none; align-items: center; justify-content: center;
z-index: 100; padding: 16px;
animation: fadeIn 150ms ease;
}
.modal-overlay.open { display: flex; }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
.modal-card {
background: #fff;
border-radius: 16px;
padding: 32px 28px 28px;
max-width: 420px; width: 100%;
box-shadow: 0 20px 60px rgba(0,0,0,0.15);
position: relative;
animation: slideUp 200ms ease;
}
@keyframes slideUp { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }
.modal-close {
position: absolute; top: 12px; right: 12px;
width: 32px; height: 32px;
background: transparent; border: none; cursor: pointer;
color: var(--text-45); font-size: 1.5rem; line-height: 1;
border-radius: 8px;
display: flex; align-items: center; justify-content: center;
transition: background 150ms ease, color 150ms ease;
}
.modal-close:hover { background: var(--hover); color: var(--text); }
.modal-title {
font-size: 1.25rem;
font-weight: 500;
letter-spacing: -0.01em;
color: var(--text);
margin-bottom: 6px;
}
.modal-sub {
font-size: 0.875rem;
color: var(--text-55);
margin-bottom: 20px;
line-height: 1.5;
}
.modal-input {
width: 100%;
padding: 12px 14px;
border: 1px solid var(--border);
border-radius: 10px;
font-size: 0.9375rem;
font-family: inherit;
color: var(--text);
background: #fff;
transition: border-color 150ms ease;
}
.modal-input:focus {
outline: none;
border-color: var(--blue);
}
.modal-hint {
margin-top: 10px;
font-size: 0.8125rem;
color: var(--text-55);
min-height: 1.2em;
font-variant-numeric: tabular-nums;
}
.modal-hint code {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
background: var(--hover);
padding: 1px 6px;
border-radius: 4px;
font-size: 0.8125rem;
color: var(--text);
}
.modal-hint.error { color: var(--negative); }
.modal-actions {
margin-top: 24px;
display: flex;
gap: 10px;
}
.modal-submit {
flex: 1;
background: var(--blue);
color: #fff;
font-size: 0.9375rem;
font-weight: 500;
padding: 12px 20px;
border: none;
border-radius: 10px;
cursor: pointer;
font-family: inherit;
transition: background 150ms ease;
}
.modal-submit:hover { background: var(--blue-hover); }
.modal-submit:disabled { opacity: 0.5; cursor: not-allowed; }
/* ── MAIN ── */
main {
background: var(--bg);
@ -779,10 +896,27 @@ footer {
<a href="#glossary" class="nav-link">Словарь</a>
<a href="#projects" class="nav-link">Проекты</a>
</div>
<a href="https://telecom.quest" target="_blank" rel="noopener" class="nav-cta">telecom.quest →</a>
<div class="nav-cta-group">
<span id="nav-cdn" class="nav-cdn-chip" hidden title="Выйти"></span>
<button id="nav-auth" type="button" class="nav-cta">Войти</button>
</div>
</div>
</nav>
<!-- AUTH MODAL -->
<div id="auth-modal" class="modal-overlay" role="dialog" aria-modal="true" aria-labelledby="auth-title">
<div class="modal-card">
<button class="modal-close" id="modal-close" aria-label="Закрыть">×</button>
<div class="modal-title" id="auth-title">Открыть мой VS Code</div>
<div class="modal-sub">Введи своё ФИО (если ты новый) или CDN (если уже работал).</div>
<input id="auth-input" class="modal-input" type="text" placeholder="Askar Tuleev или askar" autocomplete="off" autocapitalize="none" spellcheck="false" />
<div id="auth-hint" class="modal-hint">&nbsp;</div>
<div class="modal-actions">
<button id="auth-submit" type="button" class="modal-submit" disabled>Открыть VS Code →</button>
</div>
</div>
</div>
<!-- MAIN -->
<main id="top">
@ -1278,6 +1412,135 @@ const SCHEDULE = [
`;
projectsEl.appendChild(div);
});
/* ── AUTH ──────────────────────────────────────────────
Single-form login/register:
- "Askar Tuleev" → CDN "atuleev" (initial + lastname, lowercase, latin only)
- "askar" → CDN "askar" (passed through)
Submit → save to localStorage → redirect to code-server in that folder.
*/
const CODE_BASE = 'https://code.telecom.quest/?folder=/Users/telecom/dev/';
const STORAGE_KEY = 'vibe.cdn';
// Cyrillic → Latin transliteration (basic, covers Russian + Kazakh-ish names)
const TRANSLIT = {
'а':'a','б':'b','в':'v','г':'g','д':'d','е':'e','ё':'e','ж':'zh','з':'z','и':'i','й':'i',
'к':'k','л':'l','м':'m','н':'n','о':'o','п':'p','р':'r','с':'s','т':'t','у':'u','ф':'f',
'х':'kh','ц':'ts','ч':'ch','ш':'sh','щ':'sch','ъ':'','ы':'y','ь':'','э':'e','ю':'yu','я':'ya',
'ә':'a','ғ':'g','қ':'k','ң':'n','ө':'o','ұ':'u','ү':'u','һ':'h','і':'i'
};
function translit(s) {
return s.toLowerCase().split('').map(ch => TRANSLIT[ch] ?? ch).join('');
}
function makeCdn(input) {
const trimmed = input.trim().replace(/\s+/g, ' ');
if (!trimmed) return { cdn: '', mode: 'empty' };
// Single token → treat as CDN as-is (lowercased, dots removed, only allowed chars)
if (!/\s/.test(trimmed)) {
const cdn = translit(trimmed).replace(/\./g, '').replace(/[^a-z0-9_-]/g, '');
return { cdn, mode: 'login' };
}
// Multi-word → ФИО, generate CDN as <initial><lastname>
const parts = trimmed.split(' ');
const first = parts[0];
const last = parts[parts.length - 1];
const cdn = (translit(first).charAt(0) + translit(last))
.replace(/\./g, '').replace(/[^a-z0-9_-]/g, '');
return { cdn, mode: 'fio' };
}
const authModal = document.getElementById('auth-modal');
const authBtn = document.getElementById('nav-auth');
const cdnChip = document.getElementById('nav-cdn');
const closeBtn = document.getElementById('modal-close');
const input = document.getElementById('auth-input');
const hint = document.getElementById('auth-hint');
const submit = document.getElementById('auth-submit');
function refreshHeader() {
const saved = localStorage.getItem(STORAGE_KEY);
if (saved) {
authBtn.textContent = 'Открыть VS Code →';
cdnChip.hidden = false;
cdnChip.textContent = saved;
} else {
authBtn.textContent = 'Войти';
cdnChip.hidden = true;
}
}
function redirectTo(cdn) {
window.location.href = CODE_BASE + encodeURIComponent(cdn) + '/';
}
function openModal() {
authModal.classList.add('open');
setTimeout(() => input.focus(), 50);
input.value = '';
hint.innerHTML = '&nbsp;';
hint.classList.remove('error');
submit.disabled = true;
}
function closeModal() {
authModal.classList.remove('open');
}
input.addEventListener('input', () => {
const { cdn, mode } = makeCdn(input.value);
if (mode === 'empty') {
hint.innerHTML = '&nbsp;';
submit.disabled = true;
return;
}
if (!cdn) {
hint.textContent = 'Не получилось разобрать имя — попробуй латиницей';
hint.classList.add('error');
submit.disabled = true;
return;
}
hint.classList.remove('error');
const path = `/Users/telecom/dev/${cdn}/`;
if (mode === 'login') {
hint.innerHTML = `Откроется <code>${path}</code>`;
} else {
hint.innerHTML = `Твой CDN: <code>${cdn}</code> → <code>${path}</code>`;
}
submit.disabled = false;
});
submit.addEventListener('click', () => {
const { cdn } = makeCdn(input.value);
if (!cdn) return;
localStorage.setItem(STORAGE_KEY, cdn);
refreshHeader();
redirectTo(cdn);
});
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !submit.disabled) submit.click();
if (e.key === 'Escape') closeModal();
});
authBtn.addEventListener('click', () => {
const saved = localStorage.getItem(STORAGE_KEY);
if (saved) redirectTo(saved); else openModal();
});
cdnChip.addEventListener('click', () => {
if (!confirm('Выйти и забыть CDN?')) return;
localStorage.removeItem(STORAGE_KEY);
refreshHeader();
});
closeBtn.addEventListener('click', closeModal);
authModal.addEventListener('click', (e) => {
if (e.target === authModal) closeModal();
});
refreshHeader();
</script>
</body>
</html>