From 1ef9ddab8ed08b9f0e178daab741729604c07562 Mon Sep 17 00:00:00 2001 From: LockeShor <75901583+LockeShor@users.noreply.github.com> Date: Sun, 20 Sep 2026 00:52:43 -0400 Subject: [PATCH] add authors note and links to my email and source code --- README.md | 8 ++++++++ package-lock.json | 2 ++ src/main.ts | 50 +++++++++++++++++++++++++++++++++++++---------- src/style.css | 6 +++++- 4 files changed, 55 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index dc6c84c..3cf2c66 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,11 @@ +## Author's Note + +"author" is a bit of a misnomer because yes, this was completely vibecoded - I don't have a lot of experience in that area, and wanted to see what I could do. It's also a small tool I dont care much about. +It was also a very interesting challenge considering the structure of the CMU Userweb system - obviously I can only host static files, so everything had to be in browser. except one thing. +I wanted the link to have a fancy embed from the new discord documentation that was released recently (see my cyoa-discord repository for another exploration of this), which would have to be updated daily. +To achieve this, I have a somewhat complex Gitea action that has to log into the CMU AFS using sftp, build the new version of the site with the updated date, put it in the www directory, then trigger a Userweb update through their website. Interestingly enough - linux.andrew.cmu.edu is only accessible on-premise. So, instead of going for a complicated VPN route, I have an actions runner on my always-on server on campus. This required some additional logistics - but nothing insane (I have yet to hook up ethernet...). +Anyway, all I really care about is if this can be a useful and easy to use tool for people who want to understand the best way to spend their meal dollars. For me, it makes it a lot easier to track throughout the year so I don't end up with extra (or lacking) meal blocks at the end of the semester. + # CMU Meal Estimator A small single-page web app that estimates how many **meal blocks** and **FLEX dollars** a diff --git a/package-lock.json b/package-lock.json index 0e6a27d..8e130f0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -792,6 +792,7 @@ "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~6.21.0" } @@ -1309,6 +1310,7 @@ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", diff --git a/src/main.ts b/src/main.ts index b6adf35..8ef38f9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -109,14 +109,21 @@ function buildLayout(store: Store, config: Config): HTMLElement { const footer = el("footer", "footer"); footer.append( - footerLine( - "Estimates spread each plan evenly across on-campus days. Informational only, not official balances." + footerLine( + "Found a problem? Like the project? Hate it? Email me at ", + createLink("mailto:jslivka@lockeshor.com", "jslivka@lockeshor.com"), + ". Also, check out the source code on ", + createLink( + "https://gitea.lockeshor.com/lockeshor/cmu-meal-estimator", + "My Gitea" ), - footerLine( - "Not affiliated with, endorsed by, or officially connected to Carnegie Mellon University or CMU Dining. An independent student tool.", - "footer-disclaimer" - ) - ); + "." + ), + footerLine( + "Not affiliated with, endorsed by, or officially connected to Carnegie Mellon University or CMU Dining. An independent student tool.", + "footer-disclaimer" + ) +); wrap.append(header, grid, footer); return wrap; @@ -528,9 +535,32 @@ function field(label: string): HTMLElement { return f; } -function footerLine(text: string, extraClass = ""): HTMLElement { - const p = el("p", `footer-line${extraClass ? ` ${extraClass}` : ""}`); - p.textContent = text; +function createLink(href: string, text: string): HTMLAnchorElement { + const a = document.createElement("a"); + a.className = "tartan"; + a.href = href; + a.textContent = text; + return a; +} + +// Accepts text strings or DOM Nodes, with optional class name logic +function footerLine(...args: (Node | string)[]): HTMLParagraphElement { + let className = ""; + const children: (Node | string)[] = [...args]; + + // If the last argument is a class identifier (e.g., "footer-disclaimer") + const lastArg = children[children.length - 1]; + if ( + children.length > 1 && + typeof lastArg === "string" && + lastArg.startsWith("footer-") + ) { + className = children.pop() as string; + } + + const p = document.createElement("p"); + if (className) p.className = className; + p.append(...children); return p; } diff --git a/src/style.css b/src/style.css index 3120dd0..a8dd556 100644 --- a/src/style.css +++ b/src/style.css @@ -64,10 +64,14 @@ body { letter-spacing: -0.02em; } -.title .tartan { +.tartan { color: var(--tartan); } +.tartan:hover { + color: var(--tartan-soft); +} + .subtitle { margin: 0; color: var(--text-dim);