aboutsummaryrefslogtreecommitdiff
path: root/modules/services/nixos/jta/index.html
diff options
context:
space:
mode:
Diffstat (limited to 'modules/services/nixos/jta/index.html')
-rw-r--r--modules/services/nixos/jta/index.html199
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>