Add auth modal: smart FIO/CDN input redirecting to code-server folder
This commit is contained in:
parent
941abab31e
commit
ccf82b5040
265
index.html
265
index.html
@ -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"> </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 = ' ';
|
||||
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 = ' ';
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user