aboutsummaryrefslogtreecommitdiffstats
path: root/static
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-11-09 18:12:59 +0100
committerDanilo M. <danix@danix.xyz>2026-11-09 18:12:59 +0100
commit2c098221f4ed09d772df9bece5c71912f1c5e849 (patch)
tree656c77daa1e71bee1ebfc2fbc5a5d5b3b7841757 /static
parent58b6f3feda3662c2004bdfaa1e7300f14dabd931 (diff)
downloaddeck-it-2c098221f4ed09d772df9bece5c71912f1c5e849.tar.gz
deck-it-2c098221f4ed09d772df9bece5c71912f1c5e849.zip
feat: frontend shell and UDT-themed layout
Diffstat (limited to 'static')
-rw-r--r--static/app.js39
-rw-r--r--static/deck.css69
-rw-r--r--static/index.html20
3 files changed, 126 insertions, 2 deletions
diff --git a/static/app.js b/static/app.js
new file mode 100644
index 0000000..7a9ff40
--- /dev/null
+++ b/static/app.js
@@ -0,0 +1,39 @@
+// The shell: discover modules, mount each panel, poll its state.
+// A module is static/modules/<name>.js exporting default {title, interval,
+// mount(el), update(state, el)}. A missing or broken panel is skipped.
+
+const grid = document.getElementById("grid");
+
+async function mountModule(name) {
+ let panel;
+ try {
+ panel = (await import(`./modules/${name}.js`)).default;
+ } catch {
+ return;
+ }
+ const el = document.createElement("section");
+ el.className = "panel";
+ el.dataset.module = name;
+ el.innerHTML = `<h2>${panel.title ?? name}</h2>`;
+ grid.append(el);
+ panel.mount?.(el);
+
+ const poll = async () => {
+ try {
+ const r = await fetch(`/api/${name}`);
+ if (r.ok) panel.update?.(await r.json(), el);
+ } catch {
+ // keep the last state; the next tick retries
+ }
+ };
+ await poll();
+ setInterval(poll, (panel.interval ?? 1) * 1000);
+}
+
+async function boot() {
+ const r = await fetch("/api/modules");
+ if (!r.ok) return;
+ for (const { name } of await r.json()) await mountModule(name);
+}
+
+boot();
diff --git a/static/deck.css b/static/deck.css
new file mode 100644
index 0000000..9bbd806
--- /dev/null
+++ b/static/deck.css
@@ -0,0 +1,69 @@
+* { box-sizing: border-box; margin: 0; }
+
+html, body { height: 100%; }
+
+body {
+ background: var(--udt-base);
+ color: var(--udt-text);
+ font: 400 14px/20px "Noto Sans", system-ui, sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -webkit-tap-highlight-color: transparent;
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ user-select: none;
+ overflow: hidden;
+}
+
+#grid {
+ height: 100dvh;
+ padding: max(12px, env(safe-area-inset-top)) max(12px, env(safe-area-inset-right))
+ max(12px, env(safe-area-inset-bottom)) max(12px, env(safe-area-inset-left));
+ display: grid;
+ gap: 12px;
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+ grid-auto-rows: minmax(0, auto);
+ align-content: start;
+ overflow-y: auto;
+}
+
+.panel {
+ background: var(--udt-surface0);
+ border: 1px solid var(--udt-surface1);
+ border-radius: 16px;
+ padding: 16px;
+ min-height: 0;
+ min-width: 0;
+}
+
+.panel h2 {
+ font-size: 13px;
+ font-weight: 500;
+ color: var(--udt-subtext0);
+ margin-bottom: 12px;
+}
+
+.num {
+ font-family: "Inconsolata Nerd Font Mono", ui-monospace, monospace;
+ font-variant-numeric: tabular-nums;
+}
+
+.muted { color: var(--udt-overlay0); }
+
+button {
+ font: inherit;
+ color: inherit;
+ background: var(--udt-surface1);
+ border: 1px solid var(--udt-surface2);
+ border-radius: 10px;
+ padding: 10px 12px;
+ cursor: pointer;
+ touch-action: manipulation;
+}
+
+button:active { background: var(--udt-surface2); }
+
+button.on { background: var(--udt-accent); color: var(--udt-crust); border-color: var(--udt-accent); }
+
+.good { color: var(--udt-green); }
+.bad { color: var(--udt-red); }
+.warn { color: var(--udt-yellow); }
diff --git a/static/index.html b/static/index.html
index 4afa18f..2c5bdfa 100644
--- a/static/index.html
+++ b/static/index.html
@@ -1,3 +1,19 @@
<!doctype html>
-<html lang="en"><head><meta charset="utf-8"><title>deck-it</title></head>
-<body><p>deck-it</p></body></html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no,viewport-fit=cover">
+<meta name="apple-mobile-web-app-capable" content="yes">
+<meta name="mobile-web-app-capable" content="yes">
+<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
+<meta name="apple-mobile-web-app-title" content="deck-it">
+<meta name="theme-color" content="#24273a">
+<link rel="stylesheet" href="theme.css">
+<link rel="stylesheet" href="deck.css">
+<title>deck-it</title>
+</head>
+<body>
+<main id="grid"></main>
+<script type="module" src="app.js"></script>
+</body>
+</html>