Underground 2 Revived
Play Need for Speed: Underground 2 (2004) in Chrome and Firefox with WebGPU graphics, Web Audio, up to 120 fps widescreen, controller support, free roam and Online races with friends. Fan project, not
Competitive Pokémon TCG, VGC and GO events — cups, challenges, prereleases and community meetups.
Translation pending
GamesJavaScript · CSSGitHub @pizzacatzAvailability needs another checkOwner 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
Loading the full README. The saved excerpt is shown below.
A single-page, dependency-light web calendar showing competitive Pokémon events (TCG, VGC, GO, prereleases, community) from six public Google Calendars. Plain HTML/CSS/JS — no framework, no build step. Deployed on GitHub Pages and embedded into carrd.co pages as an auto-sizing iframe widget.Read the full README ↗
A single-page, dependency-light web calendar showing competitive Pokémon events (TCG, VGC, GO, prereleases, community) from six public Google Calendars. Plain HTML/CSS/JS — no framework, no build step. Deployed on GitHub Pages and embedded into carrd.co pages as an auto-sizing iframe widget.
Live: https://pizzacatz.github.io/pokemon-calendar-webapp/
pokedata.ovh ICS feeds
│ (Google Apps Script sync, daily — lives in the private
│ pizzacatz/pokemon-calendar repo; copies in *.gs here)
▼
6 public Google Calendars ──Google Calendar API──▶ this app (FullCalendar 6.1.21)
│
▼
GitHub Pages ─▶ carrd.co iframes
| File | Role |
|---|---|
index.html | page shell, CDN script tags, filter panel + modal markup |
app.js | config, calendar setup, filtering, custom week view, modals, embed plumbing |
styles.css | dark theme (#1A1A1A / #1F1F1F / gold #FFD180), mobile rules |
pokemon_calendar_sync*.gs | reference copies of the Apps Script pipeline (not used by the app) |
pokemon-events-calendar-PRD.md | original spec + addendum of post-launch changes |
app.js)GOOGLE_CALENDAR_API_KEY — public by design; must stay restricted in
Google Cloud Console to the Calendar API + HTTP referrers
(pizzacatz.github.io/*, localhost:8000/*).DISPLAY_TIME_ZONE — fixed America/New_York; times are labeled "ET".CALENDARS — one entry per category (id, chip label, color, Google Calendar
ID). Add/remove categories by editing only this list.Every calendar must be public with "See all event details" — the free/busy-only sharing level strips titles/locations and events render as "(untitled)".
Important: carrd "pages" are sections of ONE HTML document, so every embed
on the site coexists in the same DOM. Never target the iframe by id (ids
collide and all height messages land on the first iframe — some embeds stay
clipped, others inflate with empty space). Match by e.source instead.
Each embed is just an iframe (vary view=/cats= per page):
<iframe
src="https://pizzacatz.github.io/pokemon-calendar-webapp/?embed=1"
style="width:100%; height:900px; border:0;"
scrolling="no"
allow="clipboard-write"
title="Pokémon Events Calendar"></iframe>
Plus this listener once anywhere on the site (per-embed duplicates are harmless — it routes each message to the iframe that sent it):
<script>
window.addEventListener('message', function (e) {
if (e.origin !== 'https://pizzacatz.github.io') return;
if (!e.data || e.data.type !== 'pokecal:height') return;
document.querySelectorAll('iframe').forEach(function (f) {
if (f.contentWindow === e.source) f.style.height = e.data.height + 'px';
});
});
</script>
The app posts its content height (pokecal:height) whenever it changes —
view switches, data loads, modals opening/closing — so each iframe grows AND
shrinks to fit and the page never double-scrolls. The 900px is only a
pre-load fallback.
| Param | Values | Effect |
|---|---|---|
embed=1 | — | bare widget: hides the app's own header/footer |
view= | month, week, list (alias agenda) | initial view; default month |
cats= | comma list of tcg-cups, tcg-challenges, vgc, go, prerelease(s), community | pre-activates those filter chips at load (visitors can still change them) |
Examples for dedicated pages:
TCG page (month): ?embed=1&view=month&cats=tcg-cups,tcg-challenges
VG/GO page (agenda): ?embed=1&view=agenda&cats=vgc,go
Prerelease page (agenda): ?embed=1&view=agenda&cats=prereleases
Everything (default): ?embed=1
Any number of widgets can coexist — the e.source-matching listener above
routes each height message to its own iframe automatically.
docs/VOCABULARY.md → "UI names".<…> so Discord shows no preview). The official link is the first non-image
URL in the description.
Uses navigator.clipboard with an execCommand('copy') fallback, so it
works inside cross-origin iframes even without allow="clipboard-write"., US, , City/US tail.Visitors submit events through a Google Form; the owner approves them in the
linked response sheet; an Apps Script (community_event_form.gs) publishes
approved rows to the Community Events calendar, which the widget already
displays. Nothing goes live without approval.
Form questions — titles must match community_event_form.gs's Q map exactly:
| Question title | Type | Required |
|---|---|---|
Game | Multiple choice (TCG / VGC / GO / Other) | no (becomes the title prefix) |
Event name | Short answer | yes |
Event Date | Date | yes |
Start Time | Time | no (blank = all-day event) |
End Time | Time | no (blank = start + 2h) |
Venue name | Short answer | yes (goes in the title: "… @ VENUE") |
Venue Address | Short answer | yes |
Description | Paragraph | no |
Event link | Short answer | no |
Image url | Short answer | no (renders in the event popup) |
Published titles follow the site convention GAME Event name @ VENUE, and
the location field is Venue name, Venue Address.
Setup: create the form → link responses to a new spreadsheet → in that
spreadsheet Extensions → Apps Script → paste community_event_form.gs →
set the project time zone to America/New_York (Project Settings) → run
setup() once and authorize. Recommended form settings: collect email
addresses (verified) to deter spam.
Moderation: each submission emails NOTIFY_EMAIL and sits at
PENDING. In the sheet, set Status to APPROVED to publish (script fills
in the Event ID and flips it to PUBLISHED), REJECTED to decline, or
REMOVE on a published row to delete the event. The sheet's
"Community events → Process all rows now" menu re-sweeps everything if a
trigger is ever missed.
python3 -m http.server 8000 # serve from the repo root
# http://localhost:8000 (localhost:8000 must be in the API key's referrer list)
Deploy = git push — the "Deploy to GitHub Pages" Actions workflow
(.github/workflows/pages.yml) publishes the repo as-is, typically in under
a minute. Check on a deploy with gh run list. (The legacy Jekyll Pages
build was abandoned in July 2026 after repeated 20-minute stalls and opaque
"Page build failed" errors.)
timeZone and no timezone plugin,
FullCalendar's event.start/end are Dates whose UTC fields hold the
Eastern wall time. Format with timeZone:'UTC' and label "ET" manually;
formatting with America/New_York double-shifts.body.scrollHeight misses the last element's collapsed
bottom margin and floors fractional heights — report +16px or the bottom
border clips. Modals are position:fixed (invisible to body height), so
they're measured separately.fixed + centered = middle of the whole
grown iframe, not the visitor's screen. The app anchors modal boxes to the
last click's Y position when embedded.isConnected.MouseEvents can lose coordinates in
iframes; only trusted input proves click-anchoring.