diff options
Diffstat (limited to 'modules/services/nixos/jta/index.html')
| -rw-r--r-- | modules/services/nixos/jta/index.html | 199 |
1 files changed, 102 insertions, 97 deletions
diff --git a/modules/services/nixos/jta/index.html b/modules/services/nixos/jta/index.html index fd93de6..96b6f5f 100644 --- a/modules/services/nixos/jta/index.html +++ b/modules/services/nixos/jta/index.html @@ -3,127 +3,132 @@ <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>jta@ganymede</title> + <title>JTA Login</title> <link rel="stylesheet" href="style.css"> <style> body { margin: 0; - max-width: none; - padding: 0; + min-height: 100vh; + display: grid; + place-items: center; + padding: 1rem; background: var(--bg); } - - header { - padding: 1.25rem 1.5rem; + + .card { + width: min(520px, 100%); + background: var(--card); + border: 1px solid var(--border); + border-radius: 16px; + padding: 1.5rem; + box-shadow: 0 10px 28px rgba(0, 0, 0, 0.38); + } + + h1 { + margin: 0 0 0.25rem 0; text-align: center; font-family: var(--font-heading); font-size: var(--step-2); - line-height: 1.2; - letter-spacing: -0.01em; + line-height: 1.15; font-weight: 700; - background: #020617; - border-bottom: 1px solid var(--border); - } - - .section { - padding: 1rem; - max-width: 1100px; - margin: 0 auto; } - - .section-title { - font-size: var(--step-1); - line-height: 1.25; - letter-spacing: -0.01em; - margin: 1rem 0 0.5rem 0.5rem; + + p { + margin: 0.5rem 0 1.2rem 0; + text-align: center; color: var(--text-soft); + line-height: 1.5; } - - .container { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); - gap: 1rem; - padding: 0 1rem 1rem 1rem; + + .actions { + display: flex; + justify-content: center; + gap: 0.75rem; } - - .card { - background: var(--card); + + button { + appearance: none; border: 1px solid var(--border); - border-radius: 16px; - padding: 1rem; - box-shadow: 0 4px 12px rgba(0,0,0,0.4); - transition: transform 0.15s ease, box-shadow 0.15s ease; - cursor: pointer; - text-decoration: none; + border-radius: 12px; + background: #0f172a; color: var(--text); - display: block; + padding: 0.65rem 1rem; + font-size: var(--step-0); + font-weight: 600; + cursor: pointer; + transition: transform 0.15s ease, box-shadow 0.15s ease; } - - .card:hover { - transform: translateY(-4px); - box-shadow: 0 10px 20px rgba(0,0,0,0.6); + + button:hover { + transform: translateY(-1px); + box-shadow: 0 6px 16px rgba(0, 0, 0, 0.35); + } + + #status { + margin-top: 1rem; + text-align: center; + min-height: 1.25rem; + font-size: var(--step--1); + color: var(--text-soft); } - - .title { font-size: var(--step-1); line-height: 1.3; letter-spacing: -0.01em; font-weight: 600; margin-bottom: 0.5rem; } - .due { font-size: var(--step--1); color: var(--muted); margin-bottom: 0.5rem; } - .desc { font-size: var(--step--1); color: var(--text-soft); line-height: 1.55; } </style> </head> <body> - <header>Juksere Trives Aldri</header> + <div class="card"> + <h1>JTA Portal</h1> + <p>Public landing page. Login is required to view assignments and all other pages.</p> + + <div class="actions"> + <button id="login-btn" type="button">Login</button> + </div> - <div id="app"></div> + <div id="status" aria-live="polite"></div> + </div> <script> - const app = document.getElementById("app"); - - function renderHome(data) { - // Group by class - const groups = {}; - data.forEach(item => { - const cls = item.class || "Other"; - if (!groups[cls]) groups[cls] = []; - groups[cls].push(item); - }); - - // Render sections - Object.entries(groups).forEach(([cls, items]) => { - const section = document.createElement("div"); - section.className = "section"; - - const title = document.createElement("div"); - title.className = "section-title"; - title.textContent = cls; - section.appendChild(title); - - const container = document.createElement("div"); - container.className = "container"; - - items.forEach(item => { - const card = document.createElement("a"); - card.className = "card"; - card.href = item.link || "#"; - - card.innerHTML = ` - <div class="title">${item.title}</div> - <div class="due">Due: ${item.due}</div> - ${item.description ? `<div class="desc">${item.description}</div>` : ""} - `; - - container.appendChild(card); - }); - - section.appendChild(container); - app.appendChild(section); - }); - } - - fetch("assignments.json") - .then(res => res.json()) - .then(data => renderHome(data)) - .catch(() => { - app.innerHTML = "<p style='padding:1rem;'>Failed to load data.</p>"; - }); + const authCookieName = "jta_auth"; + const status = document.getElementById("status"); + const loginButton = document.getElementById("login-btn"); + + function toHex(buffer) { + const bytes = new Uint8Array(buffer); + return Array.from(bytes) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); + } + + async function sha256Hex(input) { + const data = new TextEncoder().encode(input); + const digest = await crypto.subtle.digest("SHA-256", data); + return toHex(digest); + } + + function setAuthCookie(hashValue) { + const maxAge = 10 * 365 * 24 * 60 * 60; + document.cookie = `${authCookieName}=${hashValue}; Path=/; Max-Age=${maxAge}; SameSite=Lax`; + } + + async function loginWithPrompt() { + const password = prompt("Enter password"); + if (password === null) { + status.textContent = "Login cancelled."; + return; + } + + status.textContent = "Logging in..."; + + const hashValue = await sha256Hex(password); + setAuthCookie(hashValue); + + status.textContent = "Success. Redirecting..."; + window.location.href = "/assignments"; + } + + loginButton.addEventListener("click", () => { + loginWithPrompt().catch(() => { + status.textContent = "Network error while logging in."; + }); + }); </script> </body> </html> |
