Files
bincio-activity/hub/index.html
T
Davide Scaini 9540cdd6cb
CI / Python tests (push) Waiting to run
CI / Frontend build (push) Waiting to run
Replace Astro hub build with standalone hub/index.html
2026-05-02 22:49:32 +02:00

280 lines
8.3 KiB
HTML

<!DOCTYPE html>
<html lang="it" style="background:#09090b">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bincio</title>
<script>
(function () {
var palettes = {
default: { accent: '#60a5fa', dim: 'rgba(96,165,250,0.15)' },
giro: { accent: '#f472b6', dim: 'rgba(244,114,182,0.15)' },
tour: { accent: '#facc15', dim: 'rgba(250,204,21,0.15)' },
vuelta: { accent: '#ef4444', dim: 'rgba(239,68,68,0.15)' },
};
var races = [
{ key: 'giro', start: [4, 8], end: [5, 1] },
{ key: 'tour', start: [5, 27], end: [6, 19] },
{ key: 'vuelta', start: [7, 15], end: [8, 6] },
];
function autoKey() {
var now = new Date(), y = now.getFullYear();
for (var i = 0; i < races.length; i++) {
var r = races[i];
if (now >= new Date(y, r.start[0], r.start[1]) &&
now < new Date(y, r.end[0], r.end[1] + 1)) return r.key;
}
return 'default';
}
var p = palettes[autoKey()];
document.documentElement.style.setProperty('--accent', p.accent);
document.documentElement.style.setProperty('--accent-dim', p.dim);
})();
</script>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--accent: #60a5fa;
--accent-dim: rgba(96,165,250,0.15);
--bg: #09090b;
--bg-card: #18181b;
--border: #27272a;
--border-hover: #52525b;
--text: #ffffff;
--text-muted: #71717a;
--text-dim: #3f3f46;
--red: #f87171;
}
html, body {
min-height: 100vh;
background: var(--bg);
color: var(--text);
font-family: ui-sans-serif, system-ui, -apple-system, sans-serif;
-webkit-font-smoothing: antialiased;
}
body { visibility: hidden; }
.wrap {
max-width: 384px;
margin: 0 auto;
padding: 4rem 1rem 2rem;
}
.tagline {
text-align: center;
color: var(--text-dim);
font-size: 0.875rem;
font-style: italic;
line-height: 1.8;
margin-bottom: 2rem;
}
h1 {
font-size: 1.5rem;
font-weight: 700;
text-align: center;
margin-bottom: 1.5rem;
}
label {
display: block;
font-size: 0.875rem;
color: var(--text-muted);
margin-bottom: 0.25rem;
}
input[type=text], input[type=password] {
width: 100%;
padding: 0.5rem 0.75rem;
border-radius: 0.5rem;
background: var(--bg-card);
border: 1px solid var(--border);
color: var(--text);
font-size: 1rem;
outline: none;
transition: border-color 0.15s;
}
input[type=text]:focus, input[type=password]:focus {
border-color: var(--accent);
}
.field { margin-bottom: 1rem; }
.btn-primary {
width: 100%;
padding: 0.5rem;
border-radius: 0.5rem;
background: var(--accent);
color: #000;
font-weight: 600;
font-size: 1rem;
border: none;
cursor: pointer;
transition: opacity 0.15s;
margin-top: 0.25rem;
}
.btn-primary:hover { opacity: 0.85; }
.error {
font-size: 0.875rem;
color: var(--red);
margin-bottom: 0.75rem;
display: none;
}
.forgot {
text-align: center;
margin-top: 1.5rem;
}
.forgot a {
font-size: 0.875rem;
color: var(--text-muted);
text-decoration: none;
transition: color 0.15s;
}
.forgot a:hover { color: var(--text-dim); }
/* App cards */
.greeting {
text-align: center;
font-size: 0.875rem;
color: var(--text-muted);
margin-bottom: 1rem;
}
.cards { display: flex; flex-direction: column; gap: 0.75rem; }
.card {
display: block;
padding: 1rem 1.25rem;
border-radius: 0.75rem;
background: var(--bg-card);
border: 1px solid var(--border);
text-decoration: none;
transition: border-color 0.15s;
}
.card:hover { border-color: var(--border-hover); }
.card-title { font-weight: 600; color: var(--text); }
.card-sub { font-size: 0.75rem; color: var(--text-muted); margin-top: 0.125rem; }
.logout-wrap { text-align: center; margin-top: 2rem; }
.btn-logout {
font-size: 0.75rem;
color: var(--text-dim);
background: none;
border: none;
cursor: pointer;
transition: color 0.15s;
}
.btn-logout:hover { color: var(--text-muted); }
#hub-apps { display: none; }
</style>
</head>
<body>
<div class="wrap">
<div id="hub-login">
<p class="tagline">mangia<br>bevi<br>stai calmo<br>non strappare</p>
<h1>Bincio</h1>
<form id="login-form">
<div class="field">
<label for="handle">Handle</label>
<input id="handle" name="handle" type="text" autocomplete="username" placeholder="your handle" required>
</div>
<div class="field">
<label for="password">Password</label>
<input id="password" name="password" type="password" autocomplete="current-password" required>
</div>
<p id="login-error" class="error"></p>
<button type="submit" class="btn-primary">Sign in</button>
</form>
<p class="forgot">
<a href="https://activity.bincio.org/reset-password/">Forgot password?</a>
</p>
</div>
<div id="hub-apps">
<p id="hub-greeting" class="greeting"></p>
<p class="tagline">mangia<br>bevi<br>stai calmo<br>non strappare</p>
<div id="hub-cards" class="cards"></div>
<div class="logout-wrap">
<button id="hub-logout" class="btn-logout">Log out</button>
</div>
</div>
</div>
<script>
const ACTIVITY_URL = 'https://activity.bincio.org';
const WIKI_URL = 'https://wiki.bincio.org';
const loginDiv = document.getElementById('hub-login');
const appsDiv = document.getElementById('hub-apps');
const greeting = document.getElementById('hub-greeting');
const cardsDiv = document.getElementById('hub-cards');
const form = document.getElementById('login-form');
const errEl = document.getElementById('login-error');
function appCard(title, sub, href) {
const a = document.createElement('a');
a.className = 'card';
a.href = href;
a.innerHTML = `<p class="card-title">${title}</p><p class="card-sub">${sub}</p>`;
return a;
}
function showApps(user) {
loginDiv.style.display = 'none';
appsDiv.style.display = '';
greeting.textContent = 'Ciao ' + (user.display_name || user.handle);
cardsDiv.innerHTML = '';
if (user.activity_access)
cardsDiv.appendChild(appCard('BincioActivity', 'Tracks, strade e numeri', ACTIVITY_URL));
if (user.wiki_access)
cardsDiv.appendChild(appCard('BincioWiki', 'La memoria collettiva del gruppo', WIKI_URL));
}
function showLogin() {
appsDiv.style.display = 'none';
loginDiv.style.display = '';
}
fetch('/api/me', { credentials: 'include' })
.then(async r => {
document.body.style.visibility = '';
if (r.ok) showApps(await r.json());
})
.catch(() => { document.body.style.visibility = ''; });
form.addEventListener('submit', async e => {
e.preventDefault();
errEl.style.display = 'none';
const handle = form.querySelector('#handle').value.trim();
const password = form.querySelector('#password').value;
try {
const r = await fetch('/api/auth/login', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ handle, password }),
});
if (!r.ok) {
const d = await r.json().catch(() => ({}));
errEl.textContent = d.detail ?? 'Invalid credentials';
errEl.style.display = '';
return;
}
showApps(await r.json());
} catch {
errEl.textContent = 'Could not reach server';
errEl.style.display = '';
}
});
document.getElementById('hub-logout').addEventListener('click', async () => {
try { await fetch('/api/auth/logout', { method: 'POST', credentials: 'include' }); } catch (_) {}
form.querySelector('#handle').value = '';
form.querySelector('#password').value = '';
showLogin();
});
</script>
</body>
</html>