A fast, accessible, single-page build with a dark/light theme, five operator calculators and a message builder.
No framework, no runtime dependencies.
Preview · Quick start · Customize · Quality gates · Roadmap · Issues
follows your system |
cookies or analytics |
in both themes |
on every push |
Note
Content status. The About, Services, Approach, Selected work and FAQ copy is placeholder text drafted for layout review. Each spot that needs real information is marked TODO(verify) in index.html, and no testimonials, statistics or client names have been invented. Tracked in #14.
Table of contents
| 1 · The site | Preview · What's on the page · Design system |
| 2 · Build and run | Quick start · Customize · Project structure · Deployment |
| 3 · Quality | Quality gates · Tech stack · Privacy and security · Roadmap |
PART 1 · THE SITE
The site in both themes. It follows the visitor's system setting and has a manual toggle.
Light · hero |
Dark · hero |
Light · prime cost |
Dark · cocktail cost |
Selected work · placeholders |
Contact · message builder |
Services · bento layout |
Approach · scroll-linked progress line |
Mobile · 390px |
Mobile menu · keyboard operable |
Screenshots, the banner and the social card are generated by scripts/screenshots.cjs.
| Section | Purpose |
|---|---|
| Header | Sticky glass bar with section links, a current-section indicator, a reading-progress line and a dark/light toggle with a circular reveal. |
| Hero | Value proposition, two calls to action and an animated brand scene (orbiting light, drifting rings, pointer parallax on fine pointers). |
| Focus marquee | Pausable strip of service areas and venue types. It names no clients. |
| About | The story behind the brand, a pull quote and three principles. |
| Services | Bento grid: concept and positioning, operations, financial performance, growth and openings. |
| Approach | Listen, diagnose, build, sustain, joined by a progress line that fills as you scroll. |
| Operator toolkit | Five tabbed calculators that run entirely in the browser. Prime cost: sliders, or typed percentages and dollar amounts straight from the P&L, a live gauge and rule-of-thumb band, a month-by-month tracker (trend chart, table, CSV export and import, kept only in the browser), a breakdown of where each sales dollar goes and what each point of prime cost is worth. Plate cost and cocktail cost: ingredient or pour rows (bottle price, size and pour), a target cost % and an optional menu price, giving a suggested price, actual cost % and gross profit. Break-even: fixed costs, variable costs (dragged, typed to a decimal or as the P&L dollar total), average check and days open give the sales needed a month, a week and a day open, the guests a day it takes, and how far the visitor's own sales sit above or below it. Actual food cost: opening inventory, purchases, closing inventory and food sales give food used and actual food cost %, and against an optional theoretical food cost, the gap in points and dollars a month and a year (with a check for counts that don't add up). Your menu: dishes and drinks saved from the plate and cocktail tools, side by side with cost, price, cost % and margin; with last month's sales counts, menu engineering sorts each group into stars, plowhorses, puzzles and dogs (Kasavana and Smith: margin against the sales-weighted average, popularity against 70% of an equal share), with a one-line action for each. Saved dishes reopen in their calculator and round-trip as CSV. Any result can be shared as a link or printed (or saved as a PDF) as a one-page sheet with the date and a link back, and "Talk through these numbers" carries it into the contact message. The prime cost tracker also offers a spreadsheet template with the same formulas and bands. Each calculator page has an "Embed this calculator" snippet: other sites can show the calculator alone (?embed=1, optional &theme=dark), credited and linked back, sized to fit through postMessage, and kept out of search results. |
| Selected work | Case-study cards with a blueprint cover and a challenge, approach and outcome outline, labeled as placeholders until real write-ups exist. |
| FAQ | Two-column layout: a short intro with an "Ask something else" link beside a native <details> accordion. |
| Contact | LinkedIn and Instagram, plus a message builder: pick topics and a business type, add a note and, optionally, the calculator results, then copy the message or (once an email is configured) send it with mailto:. On mobile a quick-contact bar appears between the hero and this section. |
| 404 | Branded error page that resolves its assets correctly from any nested URL. |
Tokens live in css/tokens.css and follow the TM Hospitality Strategies Canva brand kit and logo.
Navy #0E1A33 |
Brand #203164 |
Brand blue #336193 |
Action #1F4E8C |
Logo blue #00A5F6 |
Canvas #F6F7FA |
| Principle | How it shows up |
|---|---|
| Tokens | Fluid type and spacing scales, elevation and glow tokens, with complete light and dark sets. |
| Type | Fraunces (with true italics for accent words) for headings and Inter for text: self-hosted variable fonts, subset to Latin and trimmed to the weights used (149 KB in total). |
| Theme | Follows the system setting until the visitor chooses, then remembers the choice. No flash on load. |
| Contrast | Every text pairing is chosen for WCAG AA and verified by axe in both themes. The bright logo blue is decorative, or used for text only on navy. |
| Motion | Transforms and opacity only, paused or disabled under prefers-reduced-motion. Content is fully visible without JavaScript. |
PART 2 · BUILD AND RUN
Tip
There is nothing to install. Any static file server works.
git clone https://github.com/TMHSDigital/Github-Pages-Demo-1.git
cd Github-Pages-Demo-1
python -m http.server 8000 # or: npx serveThen open http://localhost:8000.
Everything is plain files. Change it, reload the page.
| To change | Do this |
|---|---|
| Copy and sections | Edit index.html. Search for TODO(verify) to find placeholder text. |
| Colors, fonts, spacing | Edit the tokens in css/tokens.css. Light values are in :root, dark in [data-theme="dark"]. |
| Calculator bands | Edit BANDS in js/calc-math.js, the note under the calculator in index.html, the prime cost explainer and the bands in scripts/make-tracker-template.py. npm run test:unit fails until all of them agree (then run python scripts/make-tracker-template.py; see Generated files). Band colors are the --band-* tokens. Confirm the ranges before relying on them. |
| Example recipes and targets | Edit defaults() in js/calc-recipe.js and the matching static values in index.html. The break-even example lives in the value attributes of its fields in index.html. |
| Contact email | Set CONTACT_EMAIL in js/inquiry.js. While empty, the buttons point to LinkedIn and the message is copied instead of emailed. |
| Logo | Replace assets/images/tmhs-logo.png and tmhs-logo-96.png (an SVG master is tracked in #16). |
| Social cards, app icons, banner, screenshots | Run node scripts/screenshots.cjs after changing the page. It also renders one social card per calculator page from docs/tool-card.html (text set in the CARDS list in the script). |
| Header, footer or a calculator | Edit it in index.html (the blocks between sync: markers), then run npm run sync to update the pages in tools/. CI fails if they drift apart. |
| Add a calculator | Write its script and its sync:tool-<key> panel and tab in index.html, create tools/<slug>/index.html (copy one and edit the prose), add one entry to tools/tools.json, then run npm run sync and node scripts/screenshots.cjs. The sitemap, manifest shortcuts, links between tool pages, embed snippet, social card and test list all come from that entry, and CI fails if any part is missing. |
| Repo name or domain | Change site in tools/tools.json and run npm run sync: every page, the sitemap, robots.txt, the 404 page's base path and the embed snippets follow. The full checklist, including the GitHub settings, is in CONTRIBUTING. |
Show the file tree
.
├── index.html Single page: hero, about, services, approach, toolkit, work, FAQ, contact
├── 404.html Branded error page (uses <base> for the repo path)
├── manifest.webmanifest Web app manifest: name, icons, shortcuts to each calculator
├── sw.js Service worker source (network first, keeps the pages a visitor opens; the build fills in its version)
├── privacy/ Privacy page (plain language; listed under "pages" in tools/tools.json)
├── tools/ tools.json (the site address, the tool registry, other pages) and one indexable page per calculator, with a worked example:
│ prime-cost-calculator/, plate-cost-calculator/, pour-cost-calculator/,
│ break-even-calculator/, food-cost-calculator/
├── css/
│ ├── tokens.css Fonts, brand tokens, fluid scales, light and dark themes
│ ├── base.css Reset, typography, layout primitives, buttons, reveal rules
│ └── components.css Header, hero scene, marquee, sections, toolkit, contact, footer
├── js/
│ ├── theme.js Dark/light toggle (pre-paint snippet lives in index.html)
│ ├── calc-math.js Pure calculator maths (TMHSCalc) and the tool registry
│ ├── calc-prime.js Prime cost tool: gauge, breakdown, what-if
│ ├── calc-tracker.js Prime cost month-by-month tracker (localStorage, chart, CSV)
│ ├── calc-recipe.js Plate and cocktail cost tools (one shared row-based tool)
│ ├── calc-breakeven.js Break-even tool: sales per month, week and day, guests a day
│ ├── calc-foodcost.js Actual food cost from inventory, and the gap to theoretical
│ ├── calc-menu.js Your menu: saved dishes and drinks, menu engineering, CSV
│ ├── calc-tabs.js Toolkit tabs, shared links and copy link
│ ├── inquiry.js Contact message builder and CONTACT_EMAIL
│ └── main.js Menu, scroll reveal, nav spy, parallax, marquee pause, quick-contact bar
├── assets/
│ ├── fonts/ Self-hosted Fraunces (upright and italic) and Inter, latin variable woff2
│ ├── images/ Logo, small logo, favicon, apple-touch and app icons, social cards (site and one per calculator)
│ └── downloads/ Prime cost tracker spreadsheet template
├── tests/
│ ├── calc-math.test.cjs Unit tests for the calculator maths (node:test, no browser)
│ ├── bands.test.cjs, links.test.cjs Unit tests: band copies agree, link extraction
│ ├── smoke.cjs Browser test runner (Playwright): runs the areas below in parallel
│ └── browser/ One file per area: site, keyboard, pages, theme, prime, tracker, recipe, menu, breakeven, foodcost,
│ contact, embed-print, build, tool-pages
├── scripts/
│ ├── stage.cjs Builds the publishable _site (minified CSS/JS, site files only)
│ ├── sync-tool-pages.cjs Copies shared markup from index.html into tools/*/; builds tool lists, sitemap and manifest shortcuts from tools/tools.json
│ ├── check-links.cjs External link checker
│ ├── lighthouse.cjs Lighthouse floors for every page in the sitemap
│ ├── screenshots.cjs Regenerates docs/screenshots, the banner, social cards and app icons
│ ├── make-tracker-template.py Builds assets/downloads/prime-cost-tracker.xlsx (Python)
│ └── requirements.txt Pinned Python dependency for the template (openpyxl)
├── docs/ README banner, calculator social card and app icon sources, screenshots
├── package.json Pinned build and test tooling (no runtime dependencies)
├── eslint.config.cjs Lint rules
├── CONTRIBUTING.md SECURITY.md LICENSE
└── .github/
├── workflows/ci.yml Validation, tests, Lighthouse, internal links, then deploy
├── workflows/links.yml Weekly external link check; reports rot as an issue
└── dependabot.yml Weekly GitHub Actions and npm tooling updates
Every push to main runs ci.yml: it builds _site/ with scripts/stage.cjs, runs every quality gate against it, and only if they all pass publishes that exact _site/ to GitHub Pages. The build bundles and minifies the CSS (inlined into the home page to save a render-blocking request), minifies the scripts, adds a hash-based Content-Security-Policy, dates each sitemap entry from the page's last commit, writes the service worker (versioned by a hash of the published files) and copies only site files (index.html, 404.html, the calculator pages in tools/, manifest.webmanifest, robots.txt, sitemap.xml, assets). Source files stay unbundled for development.
Preview the published build locally
npm ci
npm run build && cd _site && python -m http.server 8000PART 3 · QUALITY AND ROADMAP
ci.yml runs on every push and pull request. Tool versions are pinned in package.json and installed with npm ci.
| Gate | Tool | Standard |
|---|---|---|
| Calculator maths | node:test |
Table-driven unit tests for every calculation and input parser, run first |
| HTML validity | html-validate |
No errors |
| JavaScript lint | ESLint | No errors |
| Layout | Playwright, source and staged build | No horizontal scroll at 375, 768 and 1280px; no console errors |
| Features | Playwright | Theme toggle persists and follows the system; prime, plate and cocktail maths, shorthand (120k), decimal-comma and dot-grouped (120.000) input, negative sales flagged, prime cost lines typed as dollars or decimal percentages, the month tracker (save, trend, CSV round trip, remove, two-step clear, blocked storage), per-field caps shown in the field, live readouts, caret-safe formatting and reset; ingredient rows add, remove and cap at 12; shared links open the right tool with their numbers, the address bar then follows edits so a reload keeps them, and copy link round-trips; the message builder includes calculator results and switches to mailto: when an email is set; the mobile quick-contact bar shows and hides; marquee pauses; one header call to action per layout; valid JSON-LD |
| Resilience | Playwright | With JavaScript off, or if the main script fails to load, all content stays visible; the mobile nav stays reachable |
| Keyboard | Playwright | A full Tab walk of every page (home, each calculator, privacy) at 1280 and 375px, with saved months and dishes loaded: every stop has an accessible name, is visible or shows focus on the control it stands in for, and follows reading order. Skip link first, menu moves focus into its links and closes on Escape or an outside click, toolkit tabs follow the ARIA arrow/Home/End pattern, focus stays put when rows are added or removed, FAQ operable, visible focus rings |
| Reflow and spacing | Playwright | No sideways scrolling or cut-off content at 320px and 640px (400% and 200% zoom), including with WCAG text-spacing overrides |
| Accessibility | axe-core | 0 violations (WCAG 2.0, 2.1 and 2.2 A/AA, best practice) at mobile and desktop, in both themes and with reduced motion, including the plate and cocktail tools and a filled-in message |
| Security | Playwright, staged build | Content-Security-Policy present on every page and no violations while using the theme toggle, toolkit tabs, message builder and 404 page |
| Performance and quality | Lighthouse, staged build | Every page in sitemap.xml: Performance 90 or higher; Accessibility, Best Practices and SEO 95 or higher (a page under a floor gets one rerun). Scores are posted to the CI job summary |
| Links | internal checker in CI; external checker weekly (links.yml) |
No broken internal links, or no deploy. Broken external links open a link-rot issue instead of blocking a release |
Run the checks locally
npm ci # pinned tooling; tests use your installed Chrome
npm run test:unit # calculator maths only, no browser needed
npm run check # unit tests, HTML validation, lint, browser tests on the source and on the published build
npm run links # external link check (home, 404, README and every tool page); CI runs it weekly
npm run build && npm run lighthouse # Lighthouse floors for every page in the sitemapImportant
Automated checks do not replace a real screen-reader review. A manual NVDA and VoiceOver pass is still recommended before launch (#17).
No framework, no bundler in development and no runtime dependencies.
Playwright, axe-core and esbuild are used only for testing and publishing and are never shipped.
- A plain-language privacy page (
/privacy/, linked from every footer) says the same for visitors. It is markedTODO(verify)until the owner has reviewed it (#29). - The site makes no third-party requests: fonts are self-hosted, and there are no cookies, analytics or forms. The calculators and the message builder run locally and never send what you enter. The only things ever stored are what the visitor chooses to save (a month in the prime cost tracker, a dish in their menu), and those stay in their browser's
localStorageuntil they remove them. A shared calculator link carries its numbers in the URL, and only to whoever you give it to; after opening one, the address bar keeps up with your changes (still only in the URL, never stored). - The published site can be installed as an app, and every page a visitor has opened works offline afterwards. Its service worker downloads nothing up front: each page hands it the files it already loaded. It keeps only the site's own files, never anything a visitor types, and always tries the network first so a new deploy shows up straight away.
- The published pages carry a strict Content-Security-Policy: only the site's own files run, and the few inline blocks are allowed by hash (generated in
scripts/stage.cjs). - GitHub Actions are pinned to commit SHAs and kept current by Dependabot.
- Found a vulnerability? See SECURITY.md and report it privately through the repository's Security tab.
Open work is tracked in Issues, grouped here by what each item is waiting on.
Needs your input
- Replace placeholder copy with real content (#14)
- Choose a contact method, email or booking link (#15)
- Confirm the prime cost bands and the example plate and cocktail recipes and targets (#27)
- Provide the original vector logo (#16)
- Add photography and richer visuals (#20)
Needs a decision
- Rename the repo or move to a custom domain (#18)
- How TMHS and TMHS Digital relate on the site (#19)
- Analytics and privacy statement (#29)
Open to work
- Manual screen-reader and zoom review (#17)










