Skip to content
flasunPublic

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Next Bus Up

A live countdown for the employee shuttle between the off-site parking lot and the hotel employee entrance on Dream Tree Blvd. It shows one column at a time, always in Orlando time, and keeps working at the lot with no signal.

It's a static site: plain React, built with Vite, and hosted on Cloudflare. There is no server and no database. Cloudflare Web Analytics counts visits without cookies. Location, when a rider turns it on, never leaves the phone.

The code is public so anyone can check what the board does, but no license is granted: it is not open for reuse.

Run it

Needs Node 22.

npm install
npm run dev          # http://localhost:5173
npm test             # schedule and countdown logic
npm run typecheck
npm run lint
npm run build        # writes dist/
npm run preview      # serves dist/ at http://localhost:4173
npm run e2e          # browser smoke test against the build (after npm run build)

npm run e2e needs Chromium: run npx playwright install chromium once, or point CHROMIUM_PATH at a Chromium you already have.

Update the bus times

Everything lives in src/lib/shuttle/schedule.ts.

  1. Replace the times in TO_HOTEL (bus leaves the parking lot) and FROM_HOTEL (bus leaves the employee entrance). Write them as on the sheet, e.g. 5:15AM 5:20AM. A typo or a duplicate time makes npm test fail.
  2. Set SHEET_ISO to the date on the new sheet. The board shows it as "Times published/updated as of 4.28.26".
  3. If the overnight break moves, change GAP_START_MIN / GAP_END_MIN in src/lib/shuttle/logic.ts.
  4. Run npm test. The tests check details of the printed sheet, so update them along with it.

The dispatch number, the feedback forms, the stop locations and the ride time (RIDE_MIN) are in the same file.

Battery

The board is built to be light on a phone: no background requests, one clock tick a second only while the page is on screen, a location read about once a minute only when location is on and the page is visible, and no endless animation. "Keep screen on" (the biggest cost) turns itself off after 20 minutes and is never remembered for the next visit. Turning the chime off releases the phone's audio. See the "Battery is life" rule in AGENTS.md before adding anything that runs on a timer.

Languages

The board speaks English, Spanish, Haitian Creole (Kreyòl) and Brazilian Portuguese. It follows the phone's language until the rider taps one at the top; that choice is saved. A link with ?lang=es, ?lang=ht or ?lang=pt opens in that language, which is handy in group chats.

All text lives in src/lib/i18n/. en.ts is the source, with notes on where each line appears and how long it may be. es.ts, ht.ts and pt.ts must match it exactly: TypeScript and npm test fail if a line is missing, left in English, too long, or drops a time. To change wording, edit en.ts and the same line in the other three files. Times stay in the sheet's "7:30 AM" format in every language.

The Android home-screen shortcut names ("To hotel" / "From hotel") stay in English: the browser reads them from the manifest, which cannot follow the language a rider picks.

QR codes and shortcuts

  • /?dir=to opens the parking-lot column ("To hotel").
  • /?dir=from opens the employee-entrance column ("From hotel").

The column applies to that visit only and never changes a rider's saved setting.

Printing the posters

Open /poster on the site (for example https://your-address/poster), untick any you don't need, and press Print. There are three, one per page, on Letter or A4 paper in portrait:

Poster Opens Put it up
To hotel /?dir=to at the parking-lot pickup
From hotel /?dir=from at the employee entrance
Bus times / on a notice board or by the time clock

Each poster has its column's color from the board, the column name and "Scan for the next bus" in all four languages, the address in print for anyone who can't scan, and the dispatch number. The codes are drawn in the browser, so nothing is sent anywhere.

The codes open the site's address from the SITE_URL build variable (see "Deploy on Cloudflare"), even if you open /poster from a preview link. Without SITE_URL they open the address the page is on (always over https), and the page warns you when that looks temporary (workers.dev, a preview link, localhost, a local network name). Setting SITE_URL turns the warning off. If you add your own domain after printing, leave the old address turned on (Worker → Settings → Domains & Routes) so the posters keep working.

Once a poster is up, scan it right there: the board should open on the column printed on it.

Hanging them:

  • Code at about chest height (1.2–1.5 m), where riders can walk right up. It scans from about 1–1.5 m, not from across the stop.
  • Outdoors, use a matte sheet protector or matte lamination. Glossy plastic throws glare that stops phones reading the code. A laser printer holds up better in the rain than inkjet.
  • Sun fades the color band; the black code keeps working. Reprint when the color is gone.
  • For a shelter wall, print on 11×17 (Tabloid) at 135% or A3 at 140%. Each poster still fits on one page, with a code about 13 cm wide.
  • If a date or web address prints along the page edge, turn off Headers and footers in the print dialog.

Deploy on Cloudflare

One-time setup in the Cloudflare dashboard:

  1. Workers & Pages → Create → Import a repository, and pick this repository.
  2. Name the Worker nbu. It must match name in wrangler.jsonc.
  3. Build command: npm run build. Deploy command: npx wrangler deploy (the default).
  4. Production branch: main. Leave preview builds on, so every pull request gets its own link.
  5. Under Worker → Settings → Builds → Variables and secrets, add SITE_URL with the site's address, https://nbu.racason.workers.dev. It's a build variable: the "Runtime variables" section is locked for a site that is only static files, and that's expected. Change it when you add your own domain. Link previews in group chats need it (without it the share image is left out), and the posters print their codes for it.
  6. To use your own domain: Worker → Settings → Domains & Routes → Add → Custom domain. The domain has to be on Cloudflare DNS.

After that, every push to main deploys. To deploy by hand: npx wrangler login, then SITE_URL=https://… npm run deploy.

public/_headers sets the response headers: hashed files under /assets/ are cached for a year, while sw.js and the page always revalidate. It also sets a strict Content-Security-Policy and Referrer-Policy: no-referrer. Web Analytics is already allowed: script-src includes https://static.cloudflareinsights.com and connect-src includes https://cloudflareinsights.com. If you turn on another Cloudflare feature that injects scripts (Rocket Loader, email obfuscation), add its origin to the CSP first, or the browser will block it.

Usage sheet

worker/index.js counts each load of the board (including the poster links) in Cloudflare: one number per load, no visitor id, no cookies, so it counts loads, not people. Cloudflare keeps those counts for about three months. scripts/usage-sheet.gs copies them into a Google Sheet every morning and keeps them there for good.

  1. In Cloudflare, go to My Profile → API Tokens → Create Token → Create Custom Token. Give it one permission, Account → Account Analytics → Read, and copy the token.
  2. Open the Google Sheet. Set File → Settings → Time zone to Eastern Time, so "today" in the summary is Orlando's.
  3. Go to Extensions → Apps Script, replace what's there with scripts/usage-sheet.gs, and save.
  4. In Apps Script, open Project Settings → Script properties → Add script property: name CF_API_TOKEN, value the token from step 1. It stays out of the sheet's cells.
  5. Reload the sheet. In the new Next Bus Up menu, click Update every morning and allow the permissions Google asks for.

You get two tabs. Usage (hourly) has one row per hour in Orlando time. Usage summary shows the last 7 and 30 days, and totals by day, by hour of the day and by weekday. Select a table and use Insert → Chart to graph it. Next Bus Up → Update usage now refreshes it at any time.

For visits by device or browser, see Web Analytics in the Cloudflare dashboard.

How offline works

public/sw.js saves the page, its scripts, its styles and its fonts on the first visit. After that:

  • The page waits up to 2.5 s for the network, then uses the saved copy.
  • Hashed /assets/ files always come from the cache.
  • A saved page is only replaced once everything it loads has been saved.
  • Only the board itself is ever saved as the page.

Change CACHE in sw.js only when you change these rules.

Retire bus.grok.me

Grok keeps serving its own copy, and phones that installed it keep an offline copy of the old board. Once the Cloudflare address works, make one last edit in Grok so those riders move over:

Replace the whole page with a short "Next Bus Up has moved" screen that links to NEW_ADDRESS and redirects there after 3 seconds. Replace public/sw.js with a service worker that, on activate, deletes every cache, unregisters itself and reloads open pages. Keep everything else as is.

Publish it and leave it up for a few weeks. Riders then delete the old home-screen icon and add the new one; the board's "Add it to your phone" section already tells them how.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages