Files
sechenov/public/index.html

736 lines
22 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Информационная система «Университет — обучающийся»</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: Arial, Helvetica, sans-serif;
background: #f0f0f0;
color: #333;
font-size: 14px;
}
/* ── NAVBAR ── */
.snav {
background: #2d5fa6;
display: flex;
align-items: stretch;
min-height: 48px;
}
.snav-links {
display: flex;
align-items: stretch;
}
.snav-links a {
display: flex;
align-items: center;
padding: 0 20px;
color: #fff;
text-decoration: none;
font-size: 14px;
font-weight: 500;
border-right: 1px solid rgba(255,255,255,0.15);
transition: background 0.15s;
}
.snav-links a:first-child {
background: rgba(0,0,0,0.2);
border-bottom: 3px solid #fff;
}
.snav-links a:hover { background: rgba(255,255,255,0.1); }
.snav-right {
margin-left: auto;
display: flex;
align-items: center;
padding: 0 16px;
gap: 14px;
}
.snav-flag { font-size: 20px; line-height: 1; }
.snav-bell {
color: #fff;
font-size: 18px;
cursor: pointer;
}
.snav-auth {
display: flex;
align-items: center;
gap: 6px;
background: transparent;
border: 2px solid #fff;
color: #fff;
padding: 6px 14px;
border-radius: 4px;
font-size: 13px;
font-weight: 600;
cursor: pointer;
text-decoration: none;
}
.snav-auth:hover { background: rgba(255,255,255,0.15); }
/* ── CONTENT AREA ── */
.scontent {
max-width: 960px;
margin: 0 auto;
background: #fff;
padding: 32px 40px 40px;
min-height: 600px;
}
.spage-header {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 28px;
}
.spage-title {
font-size: 24px;
font-weight: normal;
color: #222;
line-height: 1.2;
}
.spage-title::after {
content: '';
display: block;
width: 48px;
height: 3px;
background: #2d5fa6;
margin-top: 8px;
}
.spage-datetime {
text-align: right;
line-height: 1.3;
}
.spage-date {
font-size: 20px;
font-weight: bold;
color: #2d5fa6;
}
.spage-time {
font-size: 20px;
font-weight: bold;
color: #555;
}
.sinfo-box {
background: #d4e5f7;
border-radius: 2px;
padding: 14px 18px;
margin-bottom: 24px;
line-height: 1.6;
color: #333;
}
.sinfo-box a { color: #2d5fa6; }
.scontent p {
margin-bottom: 14px;
line-height: 1.7;
}
.scontent p a { color: #2d5fa6; }
.scontent .red { color: #c0392b; }
.scontent .blue { color: #2d5fa6; }
/* ── FORMS ── */
.sforms {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 32px;
margin-top: 28px;
}
.sform-col h3 {
font-size: 18px;
font-weight: bold;
color: #222;
margin-bottom: 10px;
}
.sform-col h3 span {
font-size: 14px;
font-weight: normal;
color: #555;
}
.sform-col .slinks {
margin-bottom: 16px;
line-height: 2;
}
.sform-col .slinks a {
color: #2d5fa6;
text-decoration: none;
display: block;
font-size: 13px;
}
.sform-col .slinks a::before { content: '→ '; }
.sform-col .slinks a:hover { text-decoration: underline; }
.scard {
background: #f3f3f3;
border-radius: 4px;
padding: 20px;
}
.sfield { margin-bottom: 14px; }
.sfield label {
display: block;
font-size: 13px;
margin-bottom: 5px;
color: #333;
}
.sfield label .req { color: #c0392b; margin-left: 2px; }
.sfield label .hint { color: #888; margin-left: 4px; font-size: 12px; }
.sfield input {
width: 100%;
padding: 8px 10px;
border: 1px solid #ccc;
border-radius: 3px;
font-size: 13px;
background: #fff;
color: #333;
}
.sfield input:focus {
outline: none;
border-color: #2d5fa6;
}
.sfield input::placeholder { color: #aaa; }
.scheckbox {
display: flex;
align-items: flex-start;
gap: 8px;
margin-bottom: 16px;
font-size: 13px;
color: #333;
line-height: 1.4;
}
.scheckbox input[type=checkbox] {
width: auto;
margin-top: 2px;
flex-shrink: 0;
}
.sbtn {
width: 100%;
padding: 10px;
background: #2d5fa6;
color: #fff;
border: none;
border-radius: 4px;
font-size: 14px;
font-weight: bold;
cursor: pointer;
transition: background 0.15s;
}
.sbtn:hover { background: #244d8a; }
/* ── SECHENOV FOOTER ── */
.sfooter {
background: #fff;
max-width: 960px;
margin: 0 auto;
border-top: 1px solid #ddd;
padding: 16px 40px;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 12px;
color: #777;
}
.sfooter-badges { display: flex; gap: 10px; align-items: center; }
.sbadge {
display: inline-flex;
align-items: center;
gap: 5px;
background: #111;
color: #fff;
border-radius: 6px;
padding: 4px 10px;
font-size: 11px;
text-decoration: none;
line-height: 1.3;
}
.sbadge-sub { font-size: 9px; display: block; opacity: 0.7; }
.sbadge-main { font-size: 12px; font-weight: bold; display: block; }
.sfooter a { color: #2d5fa6; text-decoration: none; }
.sfooter a:hover { text-decoration: underline; }
/* ── CHAT SECTION ── */
.chat-section {
max-width: 960px;
margin: 0 auto;
background: #fff;
border-top: 4px solid #2d5fa6;
padding: 32px 40px 40px;
}
.chat-section-title {
font-size: 20px;
font-weight: normal;
color: #222;
margin-bottom: 4px;
}
.chat-section-title::after {
content: '';
display: block;
width: 48px;
height: 3px;
background: #2d5fa6;
margin-top: 8px;
margin-bottom: 20px;
}
.chat-toolbar {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
}
.chat-toolbar label { font-size: 13px; color: #555; }
.chat-toolbar select {
background: #fff;
border: 1px solid #ccc;
color: #333;
padding: 5px 10px;
border-radius: 4px;
font-size: 13px;
cursor: pointer;
}
.chat-toolbar select:focus { outline: none; border-color: #2d5fa6; }
#status {
margin-left: auto;
font-size: 12px;
color: #888;
}
.chat-panels {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0;
border: 1px solid #ddd;
border-radius: 4px;
overflow: hidden;
height: 380px;
}
.chat-pane {
display: flex;
flex-direction: column;
overflow: hidden;
}
.chat-pane + .chat-pane { border-left: 1px solid #ddd; }
.chat-pane-label {
padding: 8px 14px;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.08em;
color: #999;
border-bottom: 1px solid #eee;
background: #fafafa;
flex-shrink: 0;
}
#input {
flex: 1;
background: #fff;
color: #333;
border: none;
padding: 14px;
font-size: 13px;
line-height: 1.6;
resize: none;
font-family: inherit;
}
#input:focus { outline: none; }
#input::placeholder { color: #bbb; }
#output {
flex: 1;
padding: 14px;
overflow-y: auto;
font-size: 13px;
line-height: 1.7;
white-space: pre-wrap;
word-break: break-word;
color: #333;
background: #fff;
}
#output.empty { color: #bbb; font-style: italic; }
#output code {
background: #f5f5f5;
padding: 1px 5px;
border-radius: 3px;
font-family: 'Consolas', monospace;
font-size: 12px;
color: #c7254e;
}
#output pre {
background: #f5f5f5;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 10px;
overflow-x: auto;
margin: 6px 0;
}
#output pre code { background: none; padding: 0; color: #333; }
.chat-actions {
margin-top: 12px;
display: flex;
gap: 10px;
align-items: center;
}
#sendBtn {
padding: 8px 22px;
border: none;
border-radius: 4px;
font-size: 13px;
font-weight: bold;
cursor: pointer;
background: #2d5fa6;
color: #fff;
transition: background 0.15s;
}
#sendBtn:hover { background: #244d8a; }
#sendBtn:disabled { background: #8aabcf; cursor: not-allowed; }
#clearBtn {
padding: 8px 16px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 13px;
cursor: pointer;
background: #fff;
color: #555;
transition: background 0.15s;
}
#clearBtn:hover { background: #f5f5f5; }
.spinner {
display: none;
width: 14px; height: 14px;
border: 2px solid #ddd;
border-top-color: #2d5fa6;
border-radius: 50%;
animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
#timer { font-size: 12px; color: #aaa; }
</style>
</head>
<body>
<!-- ══ SECHENOV PORTAL CLONE ══ -->
<nav class="snav">
<div class="snav-links">
<a href="#">Главная</a>
<a href="#">Репозиторий</a>
<a href="#">Обратная связь</a>
<a href="#">Заявления</a>
</div>
<div class="snav-right">
<span class="snav-flag">🇷🇺</span>
<span class="snav-bell">🔔</span>
<a href="#" class="snav-auth">&#x2192; Авторизация</a>
</div>
</nav>
<div class="scontent">
<div class="spage-header">
<h1 class="spage-title">Авторизация и регистрация</h1>
<div class="spage-datetime">
<span class="spage-date" id="sDate"></span>&nbsp;
<span class="spage-time" id="sTime"></span>
</div>
</div>
<div class="sinfo-box">
При наличии каких либо технических проблем, в первую очередь рекомендуем очистить кеш браузера
и обновить версию браузера при необходимости. На <a href="#">данном ресурсе</a> вы можете найти
инструкцию по очистке «кэша» во всех современных браузерах. После чего рекомендуем
авторизоваться в ЛКО.
</div>
<p>В случае возникновения проблем при регистрации, воспользуйтесь <a href="#">формой обратной связи</a>,
где в теме кратко укажите проблему, в тексте сообщения укажите ФИО, номер студенческого, факультет,
курс, группу (при наличии). <span class="red">Без указания перечисленных данных обращения в
службу техподдержки не рассматриваются.</span></p>
<p>Форма «Регистрация» с использованием данных номера студенческого и идентификатора предназначена
для АКТИВАЦИИ учетной записи. После активации она работать не будет! Форма «Авторизации»
предназначена для входа ПОСЛЕ активации учетной записи с использованием данных электронного адреса
и пароля, который был отправлен по электронному адресу, указанному при активации или внесенному
в последствии в Профиль.</p>
<p class="red"><strong>В случае если вы забыли пароль, необходимо нажать «Восстановить пароль»
в форме «Авторизации» и использовать электронный адрес, с которым вы активировали личный кабинет</strong>,
или тот, который вы указывали в качестве контактного электронного адреса в «Профиле» личного
кабинета обучающегося. Инструкция доступна по ссылке
<a href="#">https://student.sechenov.ru/howto.php</a></p>
<p>При возникновении вопросов/проблем с ЦТ необходимо выбирать соответствующий пункт в причинах
на форме. Письма, отправленные не через форму обратной связи с выбором соответствующей причины,
а по электронной почте рассматриваться не будут.</p>
<p class="red">Уважаемые первокурсники! Убедительная просьба не блокировать кампусные карты МИР
в личном кабинете Сбербанк Онлайн!</p>
<div class="sforms">
<div class="sform-col">
<h3>Регистрация <span>( Получить пароль от портала )</span></h3>
<div class="slinks">
<a href="#">Как зарегистрироваться в системе/Восстановить пароль?</a>
<a href="#">Пароль и результаты ЦТ</a>
<a href="#">Предоставление паспортных данных</a>
</div>
<div class="scard">
<div class="sfield">
<label>Номер студенческого билета <span class="req">*</span> <span class="hint">(?)</span></label>
<input type="text" placeholder="Например, 123456789123">
</div>
<div class="sfield">
<label>Идентификатор <span class="req">*</span> <span class="hint">(?)</span></label>
<input type="text" placeholder="Например, 1122345678">
</div>
<div class="scheckbox">
<input type="checkbox" id="agree1">
<label for="agree1">Даю согласие на передачу и обработку персональных данных</label>
</div>
<button class="sbtn">Получить пароль</button>
</div>
</div>
<div class="sform-col">
<h3>Авторизация <span>( Я уже активировал профиль )</span></h3>
<div class="slinks">
<a href="#">Восстановить пароль</a>
<a href="#">Как зарегистрироваться в системе/Восстановить пароль?</a>
</div>
<div class="scard">
<div class="sfield">
<label>Адрес электронной почты <span class="req">*</span> <span class="hint">(?)</span></label>
<input type="email" placeholder="ivanov@mail.ru">
</div>
<div class="sfield">
<label>Пароль <span class="req">*</span> <span class="hint">(?)</span></label>
<input type="password" placeholder="••••••">
</div>
<div class="scheckbox">
<input type="checkbox" id="agree2">
<label for="agree2">Даю согласие на передачу и обработку персональных данных</label>
</div>
<button class="sbtn">Авторизация</button>
</div>
</div>
</div>
</div>
<footer class="sfooter">
<span>© Информационная система «Университет — обучающийся» 20152026</span>
<div class="sfooter-badges">
<a href="#" class="sbadge">
<span>
<span class="sbadge-sub">Доступно в</span>
<span class="sbadge-main">App Store</span>
</span>
</a>
<a href="#" class="sbadge">
<span>
<span class="sbadge-sub">ДОСТУПНО В</span>
<span class="sbadge-main">Google Play</span>
</span>
</a>
</div>
<a href="#">Форма обратной связи</a>
</footer>
<!-- ══ CHAT SECTION ══ -->
<div class="chat-section">
<h2 class="chat-section-title">Claude Chat</h2>
<div class="chat-toolbar">
<label for="modelSel">Модель:</label>
<select id="modelSel">
<option value="sonnet">Sonnet 4.6</option>
<option value="haiku">Haiku 4.5</option>
</select>
<span id="status"></span>
</div>
<div class="chat-panels">
<div class="chat-pane">
<div class="chat-pane-label">Запрос</div>
<textarea id="input" placeholder="Вставьте текст сюда... (Ctrl+Enter — отправить)"></textarea>
</div>
<div class="chat-pane">
<div class="chat-pane-label">Ответ</div>
<div id="output" class="empty">Ответ появится здесь...</div>
</div>
</div>
<div class="chat-actions">
<button id="sendBtn">Отправить</button>
<button id="clearBtn">Очистить</button>
<div class="spinner" id="spinner"></div>
<span id="timer"></span>
</div>
</div>
<script>
// Clock
function updateClock() {
const now = new Date();
const d = now.toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric' })
.replace(/\./g, '.').replace(/(\d{2})\.(\d{2})\.(\d{4})/, '$3.$2.$1')
.replace(/(\d{4})\.(\d{2})\.(\d{2})/, (_, y, m, d) => `${d}.${m}.${y}`);
const dateStr = now.toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric' });
const [dd, mm, yyyy] = dateStr.split('.');
document.getElementById('sDate').textContent = `${yyyy}-${mm}-${dd}`.replace(/-/g, '.').split('.').reverse().join('.')
.replace(/(\d{2})\.(\d{2})\.(\d{4})/, '$3.$2.$1');
document.getElementById('sDate').textContent = dateStr.replace(/(\d{2})\.(\d{2})\.(\d{4})/, '$3.$2.$1');
const t = now.toLocaleTimeString('ru-RU');
document.getElementById('sTime').textContent = t;
}
updateClock();
setInterval(updateClock, 1000);
// Chat
const inputEl = document.getElementById('input');
const outputEl = document.getElementById('output');
const sendBtn = document.getElementById('sendBtn');
const clearBtn = document.getElementById('clearBtn');
const modelSel = document.getElementById('modelSel');
const statusEl = document.getElementById('status');
const spinnerEl = document.getElementById('spinner');
const timerEl = document.getElementById('timer');
let timerInterval = null;
function setLoading(on) {
sendBtn.disabled = on;
spinnerEl.style.display = on ? 'inline-block' : 'none';
if (on) {
const start = Date.now();
timerInterval = setInterval(() => {
timerEl.textContent = ((Date.now() - start) / 1000).toFixed(1) + 's';
}, 100);
} else {
clearInterval(timerInterval);
}
}
function renderMarkdown(text) {
let html = text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
html = html.replace(/```[\w]*\n?([\s\S]*?)```/g, (_, c) => `<pre><code>${c.trim()}</code></pre>`);
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
html = html.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
return html;
}
async function send() {
const text = inputEl.value.trim();
if (!text) return;
outputEl.className = '';
outputEl.innerHTML = '';
setLoading(true);
statusEl.textContent = 'Генерирую...';
let fullText = '';
try {
const resp = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text, model: modelSel.value }),
});
const reader = resp.body.getReader();
const decoder = new TextDecoder();
let buf = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buf += decoder.decode(value, { stream: true });
const parts = buf.split('\n\n');
buf = parts.pop();
for (const part of parts) {
if (!part.startsWith('data: ')) continue;
const raw = part.slice(6).trim();
if (raw === '[DONE]') continue;
try {
const obj = JSON.parse(raw);
if (obj.error) {
outputEl.innerHTML += `<span style="color:#c0392b">${obj.error}</span>`;
} else if (obj.text) {
fullText += obj.text;
outputEl.innerHTML = renderMarkdown(fullText);
outputEl.scrollTop = outputEl.scrollHeight;
}
} catch (e) {}
}
}
} catch (e) {
outputEl.innerHTML = `<span style="color:#c0392b">Ошибка: ${e.message}</span>`;
} finally {
setLoading(false);
statusEl.textContent = fullText ? `${fullText.length} символов` : '';
}
}
sendBtn.addEventListener('click', send);
inputEl.addEventListener('keydown', e => { if (e.ctrlKey && e.key === 'Enter') send(); });
clearBtn.addEventListener('click', () => {
inputEl.value = '';
outputEl.innerHTML = 'Ответ появится здесь...';
outputEl.className = 'empty';
statusEl.textContent = '';
timerEl.textContent = '';
});
</script>
</body>
</html>