DownVid
This supports 1000+ websites, with a clean interface, across macOS / Windows / Linux.
AI translation
Find film showtimes and theatre locations in Maine
Translation pending
AI summary · website content
MediaHTML · JavaScriptGitHub @iammisplacedOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗The Maine Playweek is a showtimes website for movie theatres in Maine. Visitors can see what's playing near them, browse by theatre or by film, and find festival screenings. Each film also has its own static page for search engines and sharing.
It's a plain static site (HTML, CSS and JavaScript, with no build step) that reads its data from Supabase. Showtimes go in through an admin panel built into the site, usually by importing a CSV produced by the scrapers in scripts/.
theatre websites ──(npm run scrape)──> scraped-all-showtimes.csv
│
Admin panel: Import CSV, Save All Changes
▼
TMDb ──(daily GitHub Action)──────────> Supabase <── admin edits (films, theatres, promos, festivals)
│
┌──────────────────────────┴───────────────────────────┐
▼ ▼
index.html reads it live in the browser daily GitHub Action regenerates films/
(static film pages), commits to main
main deploys the site. Vercel serves it at https://showtimes.themaineplayweek.com, and GitHub Pages also publishes the root of main (https://iammisplaced.github.io/themaineplayweek/).films/ and commits them to main. Because of that bot commit, pull before you push.| Path | What it is |
|---|---|
index.html, css/styles.css, js/app.js | The site: the public views plus the admin panel |
js/shared.js | Helpers shared by the site and the scripts: Supabase URL and public key, dates and times, film slugs and ranking, showtime notes |
js/map-view.js | The theatre map in the Near You view |
js/admin-csv.js, js/admin-changes.js, test/ | The admin's showtimes CSV import, what a save sends and how the Save bar describes it, and their tests (npm test) |
admin-films.html, css/admin-films.css, js/admin-films.js | Admin film catalog page (staff favourites, featured films, ranking overrides) |
films/ | Static film pages, generated; don't edit by hand |
sitemap.xml | Generated with the film pages; robots.txt points to it |
data/film-pages-source.live.json | Snapshot of the Supabase data used for the last film-page build (generated); also the site's offline fallback |
data/film-pages-source.json | Sample input for building film pages without Supabase |
supabase/schema.sql | Database tables, security policies and save functions |
scripts/scrape-*.mjs, scripts/lib/scraper-utils.mjs | Showtime scrapers (see SCRAPER_GUIDE.md) |
scripts/generate-film-pages.mjs | Builds films/ |
scripts/enrich-tmdb-supabase.mjs | Fills film metadata in Supabase from TMDb (run by the daily Action) |
.github/workflows/ | The two daily Actions |
assets/, site.webmanifest | Icons, logos and images |
Any static file server works:
python3 -m http.server 8080
Then open http://localhost:8080. The local site reads and writes the live Supabase database, so changes you save locally are real.
The scripts need Node (version 24, see .nvmrc). Run npm install once.
Visitors get four views, and the site remembers the one they used last:
Showtimes that have already passed are hidden automatically. Premium showings show as a "Premium 7:00 PM" pill, and any showtime with a note shows it on hover or tap.
Click Admin at the bottom of the site and sign in with a magic link. Enter your email, click the link in the email, and you're back on the site, signed in. Admin accounts are Supabase Auth users.
Nothing is written to Supabase until you click Save. Until then, edits only live in your browser. The bar at the bottom of the panel says what a save would send ("Unsaved: 2 showtime days changed, 1 new film"), and an edit undone by hand doesn't count. Discard changes (click it twice) downloads the saved data again and drops everything unsaved.
What you can do in the panel:
api/tmdb-movie.js, a Vercel function that holds the TMDb key (the TMDB_API_KEY environment variable in Vercel) and answers only signed-in admins, so the button works on the Vercel site and its previews, not on GitHub Pages or a plain local server. Edit Film Metadata sets those details by hand for films that aren't on TMDb, or overrides TMDb.
(The iOS app's watchlist search uses a second function, api/tmdb-search.js. It's public, takes only a title (?q=), returns a few fields per film, and is cached by Vercel's CDN for a day per query.)admin-films.html, which lists every film so you can mark staff favourites, feature films on the Playweek, and override the ranking.The scrapers do most of the work. npm run scrape scrapes every Flagship, Smitty's, Regal, Apple Cinemas, CinemaPlus, Mobile Moviegoing and Spotlight theatre and writes one file, scraped-all-showtimes.csv. Import that file in the admin panel, check the preview and click Apply, then click Save. SCRAPER_GUIDE.md covers options, how each scraper works, and troubleshooting.
Required columns:
theatre_nametheatre_cityfilm_titleshow_date (YYYY-MM-DD)show_times and/or premium_show_times: times as h:mm AM/PM, separated by |, , or ;Optional columns:
film_year, film_tmdb_id: tell films with the same title apartroom: a room or screen label, e.g. Main Hallnotes: shown when a visitor hovers or taps a showtime. Target specific times with a time prefix and separate entries with ;, for example 7:00 PM: Q&A with director; 9:30 PM: Open captions. Text without a time prefix applies to every time that day. Prefix times don't need to match exactly (7pm matches 7:00 PM).festival_name: must match an existing festivalticket_linkHow rows are matched and merged:
theatre_name + theatre_city, ignoring case.Spider-Man matches spider man), and narrowed by film_year or film_tmdb_id when given. If there's no match, the film is created at that theatre. A row with film_tmdb_id only matches a film with that exact TMDb ID, so a film saved without one gets a duplicate.7:00 pm and 7:00 PM count as the same time).ticket_link, room or notes keeps what's saved. Non-blank notes are merged into the saved note (entries it doesn't already have are added), so re-importing scraped notes keeps hand-written ones. A lone - clears the room or the note.Example:
theatre_name,theatre_city,film_title,show_date,show_times,premium_show_times,room,notes,festival_name,ticket_link
Nickelodeon Cinema,Portland,Anora,2026-03-12,6:30 PM|9:15 PM,,Main Hall,9:15 PM: Q&A with director,Portland Film Festival,https://tickets.example.com/anora
Apple Cinemas,Saco,Digger,2026-03-13,1:00 PM,3:00 PM|6:00 PM,,3:00 PM: IMAX; 6:00 PM: IMAX,,
scripts/generate-film-pages.mjs builds one page per film (films/<slug>/index.html) and, when given --site-url, a sitemap.xml at the site root listing the main page, the quiz and every film page (a film's lastmod only changes when its page does). The daily Action does this from Supabase. To build them yourself:
npm run film-pages:build # from data/film-pages-source.json
SUPABASE_URL=... SUPABASE_ANON_KEY=... npm run film-pages:live # from Supabase
Options: --input=..., --out=films, --site-url=https://showtimes.themaineplayweek.com (makes the canonical and share links absolute), --from-supabase, --supabase-url=.../--supabase-anon-key=... (or the env vars), and --write-source=... (saves a snapshot of the data used). The input can be { "films": [...] } or the app's { "theatreGroups": [...] } shape.
Usually you don't need to run this: commit your other changes and let the Action rebuild films/.
| Workflow | When | What it does | Secrets it needs |
|---|---|---|---|
tmdb-supabase-refresh.yml | Daily at 09:25 UTC, or run by hand | Updates every film's TMDb details in Supabase | SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, TMDB_API_KEY |
film-pages-supabase-sync.yml | Daily at 09:40 UTC, or run by hand | Rebuilds films/ and sitemap.xml and commits them to main if anything changed | SUPABASE_URL, SUPABASE_ANON_KEY, SITE_URL |
Set the secrets under the repository's Settings → Secrets and variables → Actions. To run one now, open the Actions tab, pick the workflow, and click Run workflow.
The Supabase URL and anon key are in js/shared.js. The anon key is meant to be public, and the security policies in schema.sql limit what it can do. The service role key bypasses those policies, so it only ever goes in GitHub secrets, never in the repo.
Setting up a new project:
supabase/schema.sql.SUPABASE_URL and SUPABASE_ANON_KEY in js/shared.js, and the GitHub secrets.After schema.sql changes, run it again in the SQL editor. It's written to be re-run safely: it creates what's missing and replaces the functions. If saving fails with DELETE requires a WHERE clause, the save function is out of date, and re-running the file fixes it.
Main tables: theatres, films, showtimes (one row per showtime, with starts_at, is_premium, format, note, room and festival_id; the site and apps read this), theatre_films (ticket links only), festivals and promos. An admin Save sends only what changed since the admin loaded or last saved, to apply_showtimes_changes: changed rows, and showtimes a whole day (theatre, film and date) at a time. That function refuses saves made from out-of-date data, or that would clear a ticket link or delete many rows, and clears out showtimes that have already started. replace_showtimes_data, the older save that replaced everything, is kept as a fallback.
This is what js/app.js works with after loading from Supabase. It's also the { "theatreGroups": [...] } input the film-page generator accepts.
{
"theatreGroups": [
{
"name": "Apple Cinemas",
"city": "Saco",
"address": "779 Portland Rd, Saco, ME 04072",
"website": "https://www.applecinemas.com",
"films": [
{
"title": "Digger",
"year": 2026,
"tmdbId": 12345,
"ticketLink": "https://tickets.example.com/digger",
"tmdb": { "posterUrl": "https://image.tmdb.org/t/p/w342/abc.jpg", "director": "…", "stars": ["…"], "genres": ["Drama"] },
"showings": [
{
"date": "2026-10-07",
"times": ["1:00 PM", "4:00 PM"],
"premiumTimes": ["3:00 PM"],
"notes": "3:00 PM: IMAX",
"room": "",
"festivalId": null
}
]
}
]
}
],
"festivals": []
}
If Supabase can't be reached, the site falls back to the last copy it saved in the browser (localStorage), then to the film-page data (data/film-pages-source.live.json, then data/film-pages-source.json).