# CMU Meal Estimator A small single-page web app that estimates how many **meal blocks** and **FLEX dollars** a Carnegie Mellon student should have consumed by a given date — and how many should remain — based on their dining plan and time spent on campus. The tool spreads a plan's totals evenly across the days you're expected to be on campus, then reports three views of that budget: a **per-week** rate, an amount **consumed so far**, and an estimated **remaining** balance, for both meal blocks and FLEX. > Estimates are informational and based on even distribution across on-campus days. They are not > official dining balances. ## Features - Pick from all eight dining plans (Green, Blue, Red, Yellow, Tartan Flex, Scotty's Choice, Whitfield's Favor, Piper Select). - "As of" date picker that defaults to today and is clamped to the meal period. - Checkboxes for configured breaks ("missing periods") you spent off campus. - A numeric input for any additional days you don't want counted. - Live-updating results — every input recomputes the estimate instantly. - Everything is config-driven: plans, dates, and breaks live in one JSON file that is fetched at runtime. Edit it on the server and changes appear on the next page load — no rebuild needed. - Your plan, checked breaks, and extra-days value are remembered between visits (via `localStorage`). The date always resets to today. - Dark theme with restrained CMU Tartan-red accents. - Ships as plain static files — no server-side code or backend. ## Getting started Requires [Node.js](https://nodejs.org/) 18+. ```bash npm install # install dependencies npm run dev # start the dev server at http://localhost:5173 ``` ### Other scripts ```bash npm run build # type-check, build to dist/, then inject the Discord embed npm run embed # (re)inject the Discord embed into an existing dist/ build npm run preview # serve the production build locally npm test # run the estimation-engine tests ``` ## Configuration All tunable values live in [`public/config.json`](public/config.json). This file is **fetched at runtime**, not compiled into the bundle — so after deployment you can edit the deployed `config.json` directly and the changes take effect on the next page load, with no rebuild. During development, edit `public/config.json` and reload the page. ```jsonc { "mealStartDate": "2026-08-24", // first day of the meal period (YYYY-MM-DD) "mealEndDate": "2026-12-13", // last day of the meal period (YYYY-MM-DD) "plans": [ { "id": "green", // stable unique key "displayName": "Green", // shown in the dropdown "totalMealBlocks": 210, // total blocks for the whole meal period "totalFlexDollars": 500 // total FLEX dollars for the whole meal period } // ...more plans ], "missingPeriods": [ { "id": "fall-break", // stable unique key "displayName": "Fall Break", // shown next to the checkbox "startDate": "2026-10-19", // inclusive "endDate": "2026-10-20" // inclusive } // ...add as many as you like; each becomes a checkbox ] } ``` Notes: - **The shipped plan numbers are placeholders.** Replace `totalMealBlocks` and `totalFlexDollars` with the real figures from your CMU dining agreement, and set the correct `mealStartDate` / `mealEndDate` for your semester. - Add or remove plans and missing periods freely. Every missing period automatically becomes a checkbox in the UI. - If the config is malformed (missing fields, bad dates, duplicate ids, negative totals), the app shows a readable list of problems instead of rendering broken estimates. ## How the estimate works 1. **Total active days** = every calendar day in `[mealStartDate, mealEndDate]`, minus the days covered by any break you check. 2. **Daily rate** = `plan total / total active days`, computed separately for blocks and FLEX. 3. **Active days consumed** = active days from the start up to your "as of" date, minus checked breaks in that window, minus any additional excluded days. 4. **Consumed** = `daily rate × active days consumed`. 5. **Remaining** = `plan total − consumed`. 6. **Per week** = `daily rate × 7`. Checking a break or adding excluded days shifts your expected consumption down, which is what you want when you were away and not eating on campus. ## Deployment (static hosting) The app is a purely client-side static site — no backend, no server-side rendering. To deploy: ```bash npm run build # outputs everything to dist/ ``` Then copy the **contents of `dist/`** to wherever you're serving from. The build produces: ``` dist/ index.html config.json # the live, editable config assets/ # hashed JS + CSS ``` ### Subpath hosting Asset paths in the build are **relative**, and `config.json` is loaded relative to the page's own location. That means the app works unchanged whether it's served from a domain root or a subpath — e.g. `https://www.cmu.edu/~you/meal-estimator/`. No base-path configuration or rebuild is needed to move it between locations. The one requirement is that the app is served as a directory with a trailing slash (e.g. `.../meal-estimator/`). Virtually all static servers (Apache, nginx, and the like) redirect `.../meal-estimator` to `.../meal-estimator/` automatically, so this is handled for you. ### Editing config after deployment Because `config.json` sits alongside `index.html` in the deployed files and is fetched at runtime, you can update plan numbers, dates, or breaks by editing the deployed `config.json` directly. No rebuild or redeploy of the JS is required — just reload the page. ## Discord link embed The page's `` includes a Discord [Components V2](https://discord.com/developers/docs/components/reference) link embed (`