morozovka-portal/app/templates/index.html

195 lines
12 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>Morozovka Platform</title>
<style>
* { box-sizing: border-box; }
body { background: #0d1117; color: #e6edf3; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; margin: 0; font-size: 14px; line-height: 1.5; min-height: 100vh; display: flex; flex-direction: column; }
a { color: #58a6ff; text-decoration: none; }
a:hover { text-decoration: underline; }
.topnav { background: #161b22; border-bottom: 1px solid #30363d; padding: 12px 32px; display: flex; align-items: center; gap: 24px; position: sticky; top: 0; z-index: 100; }
.topnav-logo { display: flex; align-items: center; gap: 8px; color: #e6edf3; font-weight: 600; font-size: 15px; }
.topnav-logo:hover { text-decoration: none; color: #e6edf3; }
.topnav-logo svg { color: #58a6ff; }
.topnav-links { display: flex; gap: 20px; flex: 1; }
.topnav-links a { color: #c9d1d9; font-weight: 500; font-size: 14px; }
.topnav-links a:hover { color: #58a6ff; text-decoration: none; }
.topnav-actions { display: flex; gap: 12px; align-items: center; padding-right: 80px; }
.btn { display: inline-flex; align-items: center; gap: 6px; padding: 5px 16px; border-radius: 6px; font-size: 14px; font-weight: 500; line-height: 20px; border: 1px solid rgba(240,246,252,0.1); cursor: pointer; text-decoration: none; transition: all .15s; white-space: nowrap; }
.btn:hover { text-decoration: none; }
.btn-primary { background: #238636; color: #fff; }
.btn-primary:hover { background: #2ea043; color: #fff; }
.btn-default { background: #21262d; color: #c9d1d9; }
.btn-default:hover { background: #30363d; color: #c9d1d9; }
.btn-outline { background: transparent; color: #c9d1d9; border-color: #30363d; }
.btn-outline:hover { background: #21262d; color: #e6edf3; border-color: #8b949e; }
.btn-lg { padding: 10px 24px; font-size: 15px; }
.container { max-width: 1200px; margin: 0 auto; padding: 32px; flex: 1; width: 100%; }
.hero { display: grid; grid-template-columns: 2fr 1fr; gap: 32px; margin-bottom: 32px; }
.hero-main { padding: 32px; background: linear-gradient(135deg, #161b22 0%, #1c2128 100%); border: 1px solid #30363d; border-radius: 6px; position: relative; overflow: hidden; }
.hero-main::before { content: ''; position: absolute; top: -50%; right: -20%; width: 400px; height: 400px; background: radial-gradient(circle, rgba(88,166,255,0.08) 0%, transparent 70%); pointer-events: none; }
.hero-title { font-size: 32px; font-weight: 700; margin: 0 0 12px; background: linear-gradient(90deg, #58a6ff, #a371f7); -webkit-background-clip: text; background-clip: text; color: transparent; line-height: 1.2; }
.hero-subtitle { color: #8b949e; font-size: 16px; margin: 0 0 24px; line-height: 1.6; max-width: 500px; }
.hero-actions { display: flex; gap: 12px; flex-wrap: wrap; }
.hero-side { display: flex; flex-direction: column; gap: 16px; }
.status-card { padding: 20px; border: 1px solid #30363d; border-radius: 6px; background: #161b22; }
.status-label { color: #8b949e; font-size: 12px; text-transform: uppercase; font-weight: 600; margin-bottom: 8px; }
.status-value { font-size: 18px; font-weight: 600; margin-bottom: 4px; display: flex; align-items: center; gap: 8px; }
.status-dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; }
.status-dot-green { background: #3fb950; box-shadow: 0 0 0 3px rgba(63,185,80,0.2); }
.status-dot-yellow { background: #d29922; box-shadow: 0 0 0 3px rgba(210,153,34,0.2); }
.status-dot-red { background: #f85149; box-shadow: 0 0 0 3px rgba(248,81,73,0.2); }
.status-dot-grey { background: #8b949e; box-shadow: 0 0 0 3px rgba(139,148,158,0.2); }
.status-meta { color: #8b949e; font-size: 13px; }
.status-link { display: inline-flex; align-items: center; gap: 4px; margin-top: 12px; font-size: 13px; font-weight: 500; }
.section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; margin-top: 32px; }
.section-title { font-size: 18px; font-weight: 600; color: #e6edf3; margin: 0; }
.cards-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 16px; }
.feature-card { padding: 20px; border: 1px solid #30363d; border-radius: 6px; background: #161b22; transition: all .15s; display: flex; flex-direction: column; text-decoration: none; color: inherit; }
.feature-card:hover { border-color: #58a6ff; transform: translateY(-2px); text-decoration: none; }
.feature-icon { font-size: 32px; margin-bottom: 12px; }
.feature-title { font-size: 16px; font-weight: 600; color: #e6edf3; margin: 0 0 6px; }
.feature-desc { color: #8b949e; font-size: 13px; line-height: 1.5; margin: 0; flex: 1; }
.feature-badge { display: inline-block; margin-top: 12px; padding: 0 8px; border-radius: 2em; font-size: 11px; font-weight: 500; line-height: 18px; border: 1px solid; }
.feature-badge-new { background: rgba(63,185,80,0.15); color: #3fb950; border-color: rgba(63,185,80,0.4); }
.footer { border-top: 1px solid #30363d; padding: 24px 32px; text-align: center; color: #8b949e; font-size: 13px; }
.footer a { color: #8b949e; }
.footer a:hover { color: #58a6ff; }
@media (max-width: 900px) { .hero { grid-template-columns: 1fr; } .hero-side { flex-direction: row; flex-wrap: wrap; } .hero-side > * { flex: 1; min-width: 200px; } }
@media (max-width: 768px) { .topnav { padding: 12px 16px; gap: 12px; } .topnav-links { display: none; } .topnav-actions { padding-right: 60px; } .container { padding: 16px; } .hero-main { padding: 20px; } .hero-title { font-size: 24px; } .section-header { flex-direction: column; align-items: flex-start; gap: 8px; } }
</style>
</head>
<body>
<nav class="topnav">
<a href="/" class="topnav-logo">
<svg width="24" height="24" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 15h-2v-6h2v6zm0-8h-2V7h2v2zm4 8h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg>
Morozovka Platform
</a>
<div class="topnav-links">
<a href="/schedule">Расписание</a>
<a href="/projects">Проекты</a>
<a href="/workshops">Мастер-классы</a>
<a href="/files">Файлы</a>
</div>
<div class="topnav-actions">
{% if username %}
<a href="/profile" class="btn btn-default">{{ username }}</a>
{% if session.get('role') == 'admin' %}<a href="/admin/users" class="btn btn-outline">Админ</a>{% endif %}
<a href="/logout" class="btn btn-outline">Выйти</a>
{% else %}
<a href="/login" class="btn btn-default">Войти</a>
<a href="/register" class="btn btn-primary">Регистрация</a>
{% endif %}
</div>
</nav>
<div class="container">
<div class="hero">
<div class="hero-main">
{% if username %}
<h1 class="hero-title">Привет, {{ username }}!</h1>
<p class="hero-subtitle">Добро пожаловать на платформу команды Морозовки. Расписание, проекты, мастер-классы и файлы — всё в одном месте.</p>
{% else %}
<h1 class="hero-title">Morozovka Platform</h1>
<p class="hero-subtitle">Единая платформа команды: расписание, проекты, мастер-классы и файлы. Войдите, чтобы получить полный доступ.</p>
{% endif %}
<div class="hero-actions">
<a href="/schedule" class="btn btn-primary btn-lg">
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor"><path d="M3.75 1.5a.75.75 0 0 1 .75.75V3h7V2.25a.75.75 0 0 1 1.5 0V3h.5A1.75 1.75 0 0 1 15 4.75v9A1.75 1.75 0 0 1 13.5 15.5h-11A1.75 1.75 0 0 1 1 13.75v-9A1.75 1.75 0 0 1 2.5 3H3V2.25a.75.75 0 0 1 .75-.75Zm10.75 5.5h-13v6.75c0 .138.112.25.25.25h11a.25.25 0 0 0 .25-.25Z"/></svg>
Расписание
</a>
<a href="/projects" class="btn btn-outline btn-lg">Проекты</a>
<a href="/workshops" class="btn btn-outline btn-lg">Мастер-классы</a>
</div>
</div>
<div class="hero-side">
<div class="status-card">
<div class="status-label">Статус Никиты Сергеевича</div>
<div class="status-value" id="status-value"><span class="status-dot status-dot-grey"></span><span>Загрузка...</span></div>
<div class="status-meta" id="status-meta">Проверяем расписание...</div>
<a href="/schedule" class="status-link">Открыть расписание →</a>
</div>
</div>
</div>
<div class="section-header">
<h2 class="section-title">Разделы платформы</h2>
</div>
<div class="cards-grid">
<a href="/schedule" class="feature-card">
<div class="feature-icon">📅</div>
<h3 class="feature-title">Расписание</h3>
<p class="feature-desc">Календарь событий, статус преподавателя, встроенный ассистент</p>
</a>
<a href="/projects" class="feature-card">
<div class="feature-icon">🗂</div>
<h3 class="feature-title">Проекты</h3>
<p class="feature-desc">Каталог проектов команды, канбан-доски, участники и роли</p>
</a>
<a href="/workshops" class="feature-card">
<div class="feature-icon">🎓</div>
<h3 class="feature-title">Мастер-классы</h3>
<p class="feature-desc">Пайка, embedded, Linux и творческие занятия. Регистрация и материалы</p>
<span class="feature-badge feature-badge-new">NEW</span>
</a>
<a href="/files" class="feature-card">
<div class="feature-icon">📁</div>
<h3 class="feature-title">Файлы</h3>
<p class="feature-desc">Единое хранилище документов, схем и материалов</p>
</a>
{% if session.get('role') in ['admin', 'professor'] %}
<a href="/admin/schedule" class="feature-card">
<div class="feature-icon">⚙️</div>
<h3 class="feature-title">Управление расписанием</h3>
<p class="feature-desc">Быстрые действия, события, импорт/экспорт .ics</p>
</a>
{% endif %}
{% if session.get('role') == 'admin' %}
<a href="/admin/users" class="feature-card">
<div class="feature-icon">👥</div>
<h3 class="feature-title">Пользователи</h3>
<p class="feature-desc">Управление ролями и доступом</p>
</a>
{% endif %}
</div>
</div>
<div class="footer">
Morozovka Platform · <a href="/schedule">Расписание</a> · <a href="/projects">Проекты</a> · <a href="/workshops">Мастер-классы</a> · <a href="/files">Файлы</a>
</div>
<script>
(function() {
fetch('/api/public-status')
.then(r => r.ok ? r.json() : null)
.then(data => {
const valEl = document.getElementById('status-value');
const metaEl = document.getElementById('status-meta');
if (!data || !data.status) {
valEl.innerHTML = '<span class="status-dot status-dot-grey"></span><span>Нет данных</span>';
metaEl.textContent = '';
return;
}
const map = {
'on_place': { text: 'На месте', dot: 'green' },
'delayed': { text: 'Опаздывает на ' + (data.delay_minutes || 0) + ' мин', dot: 'yellow' },
'away': { text: 'Вне вуза', dot: 'red' },
'cancelled': { text: 'Отменено', dot: 'red' },
'free': { text: 'Вне расписания', dot: 'grey' }
};
const info = map[data.status] || map.free;
valEl.innerHTML = '<span class="status-dot status-dot-' + info.dot + '"></span><span>' + info.text + '</span>';
metaEl.textContent = data.message || '';
})
.catch(() => {
document.getElementById('status-value').innerHTML = '<span class="status-dot status-dot-grey"></span><span>Нет данных</span>';
});
})();
</script>
<script src="/static/notif_bell.js"></script>
</body>
</html>