Static-site rebuild of Project Daedalus on GitHub Pages. Live data from production's Firestore, no Rails server.
https://agentkush.github.io/daedalus-static-poc/
| URL | Source |
|---|---|
/home/ |
Production-style landing page (hero + 3 feature cards + Discord CTA) |
/ |
Mods listing — search, author / source / week / sort filters, 25-page pagination, all live from Firestore |
/tools.html |
Community modding tools, live from Firestore |
/info.html |
Discord, Mod Requests, IMM Setup PDF, GPortal Install PDF |
/requests.html |
Mod Requests board powered by GitHub Discussions Ideas category, reactions = upvotes |
/mods/<author>/<slug>/ |
Mod detail page for every mod — full PR #112 restyle, live README, ratings, analytics, GitHub stats, comments |
/authors/<slug>/ |
Per-author profile page — covers all 99 distinct authors (curated + Nexus), Nexus mod cards link out to nexusmods.com |
/tools/<author>/<slug>/ |
Per-tool detail page with live-fetched GitHub Releases history (release card per tag, lazy-loaded) |
/tag/<name>/ |
Tag-filtered listings (no-decay, qol, vehicles, cosmetic, food, etc.) — auto-derived from each mod's name + first 120 chars of description |
/week/<n>/ |
Mods released in a specific Icarus game week |
/search/ |
Full-text search across every curated mod's README via Pagefind, plus client-side filter for Nexus mods |
/stats/ |
Public catalog stats — counts, file-type breakdown, oldest/newest |
/leaderboard/ |
Modders ranked by mod count (includes Nexus contributors) |
/status/ |
Live sync status, broken-download tally, last-build SHA |
/api/v1/*.json |
Static JSON API (mods, authors, tags, weeks) for downstream consumers |
/feed.xml + /sitemap.xml |
Atom feed of newest mods + complete sitemap |
production Firestore (projectdaedalus-fb09f)
↓
├── meta/repos ─── 47 modder GitHub repo URLs
├── mods ─── 494 curated mod docs
├── nexus_mods ─── 135 Nexus-sourced mod docs
├── tools ─── 4 tool docs
└── info_content ─── info-page cards
+
public/data/modders_extras.json ─── locally-curated repos (onboarding without
waiting for upstream meta/repos updates)
↓
Browser fetches via Firestore REST API directly (public-read)
↓
Static site renders, polls every 60s for fresh data
The site reads the same data projectdaedalus.app reads, in real time. Status badge in the lower-right corner shows ● LIVE Firestore (REST) when it's working, ○ Bundled snapshot if fallback kicks in, and ◐ LIVE + bundled (empty collection) when the live collection exists but is empty (used for nexus_mods until upstream PR #119 merges and production starts populating it). Polling continues in this mode, so the moment the live collection fills, the listing automatically swaps over without anyone touching the POC.
In the background, two workflows keep our offline fallback bundles fresh:
sync-mods.ymlruns every hour: pulls production'smeta/repos, probes each repo'smodinfo.jsonandtoolinfo.json(acrossmain/master/EXMODZ/Icarusbranches), aggregates everything topublic/data/mods.json+public/data/tools.json. Commits and triggers a Pages deploy if anything changed.discover-modders.ymlruns every Monday morning: sweeps GitHub for Icarus mod repos that production doesn't know about yet (code search formodinfo.json + EXMODZ, repo-name patterns, URL extraction from production's mod data). Probes each candidate. If new ones turned up, opens a verified-commit PR titled "Auto: new candidate Icarus mod repos discovered" with the full URL list and per-candidate metadata in the body.
So even if production's Firestore is briefly unavailable, the bundled JSON fallback contains a fresh-within-the-hour snapshot. And if a new modder shows up, you'll see a PR within a week — usually less.
Production's compatibility field is hand-authored, so older mods often left it blank. The hourly sync fills any gap by walking the GitHub Commits API for each mod's file URL and converting the latest commit date to an Icarus game week (Apr 24 2026 = w229 anchor).
The URL parser handles the four shapes mods are linked from:
github.com/owner/repo/raw/branch/pathgithub.com/owner/repo/blob/branch/pathraw.githubusercontent.com/owner/repo/branch/pathgithub.com/owner/repo/releases/download/tag/file(falls back to repo HEAD)
Percent-encoded paths (More%20Drop%20Ship%20Slots) are decoded before the API call, and non-default branches (e.g. zailfyer's EXMODZ) are passed via &sha=. Mods derived this way carry a compatibility_derived: true marker on the JSON record so the UI can show them differently if it ever wants to. Result: 491/491 curated mods now show a week pill on the listing and detail pages.
- Real
daedalus-logo.pngin the gradient header, links to/home/ - Real
bg-topography-dark.svg/bg-topography-light.svgpage backgrounds viabg-topo-light dark:bg-topo-darkTailwind utilities - Inter font, icarus-500 gold palette, paper card body
- Theme toggle (moon/sun emoji), persisted via
localStorage, defaults to OS preference - Pagination — 20 per page, 25 pages,
« Prev | 1 | 2 | … | last | Next », "Showing X to Y of N mods" counter, deep-linkable via#page=N- Mobile-aware wrap, no forced scroll on Next/Prev
- Floating back-to-top button appears once you've scrolled past 400 px
- Search debounce at 400 ms (matches the merged search-debounce-timing PR)
- Author filter with all 99 distinct authors (curated + Nexus) in true alphabetical order (case-insensitive
localeCompare) - Source filter (Curated / Nexus / All) — Curated wins on name+author duplicates
- Week filter (Latest w220+ / Recent w200–219 / Older < w200 / Any) — backed by 100% game-week coverage across all 491 curated mods
- Sort order (Name A–Z / Name Z–A / Newest first / Oldest first) — newest/oldest sort by derived game week
- NEXUS badge + "View on Nexus ↗" link on Nexus rows (PR #119)
- All dropdowns use a custom inline-SVG chevron (slate-400) with
appearance-none, so the roundedrounded-lgcorner stays clean instead of being clipped by the browser-native arrow - Full OG meta block including
og:imageper mod viaeleventyComputed
Beyond the listing page, the site exposes a few ways to navigate the catalog:
- Tags (
/tag/<name>/) — every mod is auto-tagged from its name + the first 120 chars of its description (the "primary purpose" zone, to avoid changelog false positives). Categories includeno-decay,qol,vehicles,cosmetic,food,building,creatures,weapons,armor,performance, plus more. Tag pages include both curated and Nexus mods. - Full-text search (
/search/) — Pagefind builds a search index over every curated mod's README at deploy time. Results show snippet-level matches with highlighting. Nexus mods are merged in via a client-side filter. - Author profile pages (
/authors/<slug>/) — one page per modder showing their full output, file-type breakdown, and most-recent-update. Includes Nexus authors (cards link out to nexusmods.com); modders with mods in both sources get one combined profile. - Mod-pack export — multi-select on the listing, then "Build pack" downloads a zip of EXMODZ/PAK files for the selected mods. Failed downloads (CORS-blocked or 404) get a
SKIPPED.txtline in the zip with the reason. - Tool detail pages (
/tools/<author>/<slug>/) — live-fetched GitHub Releases history per tool. Shared-repo tools (e.g. Mod Editor + Mod Manager both ship fromJimk72/Icarus_Software) filter the release feed by tool keyword so they don't echo each other's history. - Random mod, Recently viewed, Share + QR code, and Keyboard shortcuts (
/to focus search,g h/m/sto jump to home/mods/stats,?for help) - Mobile: hamburger menu, full mobile-responsive grids, single-column on small viewports
Every mod has a pre-rendered detail page at /mods/<author-slug>/<name-slug>/ matching PR #112's restyle:
- File-type badges (
exmodz= gold,pak= green, others = slate) in the header - One
DOWNLOAD <TYPE>button per file type - Author + version on a single flex row
- EXMODZ install note for mods that ship as EXMOD/EXMODZ
- Compatibility pill (with the right Icarus week computed from the data)
- Image floats right inside the prose at
md:and up - "Other mods by <author>" grid (up to 6 cards)
- Prev / Back to List / Next navigation, pre-computed at build time
Plus several things the static rebuild adds on top:
When the page loads, JS pulls the mod's readmeURL from Firestore, fetches the markdown, and renders it with marked v13. GFM tables, code blocks, shields.io badges, and links all style correctly against the dark theme via custom .prose CSS.
- File Types — coloured badges from the mod's files map
- Mod Age — first commit on the mod's file in its GitHub repo (not the Firestore document timestamp). Computed via the GitHub Commits API.
- Freshness — most recent commit on the same file. Coloured Fresh/Recent/Aging/Stale label.
- Your Activity — views + downloads tracked in
localStorage, per browser - Compatibility + EXMOD-format note
- Author Stats — total mods + file-type breakdown + most-recently-updated mod by the same author (clickable)
- GitHub Stats card lazy-loads on panel open: stars, forks, open issues, since last push, license. Cached per repo for 1 hour.
- Mod Status — combines validator findings (
validation.jsonfrom the weekly EXMODZ + PAK validator workflow), health-check results (health.json— broken downloads), and 6 in-browser checks (empty description, placeholder text, no image, very short description, compatibility age, no tags). Renders asAll Clear/1 warning/2 errorsetc. with a per-finding breakdown.
Five-star widget below the README. Stable per-browser fingerprint UUID prevents ballot-stuffing. Without Firestore the rating is localStorage-only ("visible only to you"); with Firestore configured, ratings aggregate live across all visitors via onSnapshot.
Discussion thread per mod, backed by GitHub Discussions on this repo. Visitors sign in with GitHub OAuth, comments land as Discussion comments under the General category, one thread per mod via the mod-{modId} term mapping.
The shared <head> warm-preconnects to giscus.app, api.github.com, github.com, and avatars.githubusercontent.com, and the giscus client script is loaded with defer (not async) so the iframe is initialised early and is fully authenticated by the time visitors scroll to it. First-click latency is noticeably better; per-click latency after the iframe is warm is still bounded by the GitHub Discussions GraphQL round-trip (the cost of using GitHub login without a custom backend).
| Where | Auth | Limit | Cost in practice |
|---|---|---|---|
| Browser-side (Mod Age, Freshness, GitHub Stats card) | unauthenticated | 60/hr per visitor IP | 24h localStorage cache means navigating across 30+ Jimk72 mods costs one round trip total |
| Server-side (sync-mods, discover-modders workflows) | GITHUB_TOKEN from Actions |
5,000/hr Core, 30/min Search | Plenty for hourly + weekly cadence |
Embedding a PAT in the static-site JS would lift the browser-side limit to 5,000/hr — but anyone viewing source could exfiltrate the token. The pragmatic answer is: keep it unauthenticated, cache aggressively, fall back to Firestore timestamps if the rate limit ever kicks in. If traffic grows past that, add a Cloudflare Worker / Netlify Function that proxies the calls and hides the token, or pre-fetch commit data nightly via Actions and cache it in Firestore.
- Eleventy 3 — static site generator (per-page templates, build-time pagination of every mod detail page)
- Tailwind CSS via the CDN script (no build step)
- marked v13 for live README rendering
- Firestore REST API for live data (no Firebase SDK, no apiKey)
- Plain ES modules everywhere else
- Giscus for comments + Mod Requests
- GitHub Pages + Actions
npm install
npm run serve # http://localhost:8080
npm run build # outputs to _site/| File | Cadence | Purpose |
|---|---|---|
deploy.yml |
on push to main |
Build with Eleventy + Pagefind index, deploy to Pages |
sync-mods.yml |
hourly + manual | Pull production's meta/repos + modders_extras.json, aggregate every modder's modinfo.json/toolinfo.json, commit mods.json + tools.json if changed |
sync-nexus.yml |
daily + manual | Refresh public/data/nexus_mods.json from the Nexus API (no-op without NEXUS_API_KEY secret; bundled snapshot keeps Nexus mods visible regardless) |
discover-modders.yml |
weekly + manual | Search GitHub for Icarus mod repos not in production's registry, open a verified-commit PR with the candidate list |
validate-mods.yml |
weekly + manual | Run icarus-modinfo-validator across every EXMODZ + lightweight PAK checks (filename, size, Unreal Pak magic). Writes public/data/validation.json |
health-check.yml |
daily + manual | HEAD-probe every download URL in the catalog, write public/data/health.json with broken/redirected results — drives the /status/ page and Mod Status panel |
notify-discord.yml |
on mods.json change |
Post a Discord-webhook embed for new or updated mods (no-op without DISCORD_WEBHOOK_URL secret) |
The discovery workflow uses peter-evans/create-pull-request@v8 with sign-commits: true, so the auto-PR commit is GitHub-API-signed and shows the green Verified badge. The PR body lists each candidate (clickable repo link, mod count, author, example mod, first-seen date) plus a copy-pasteable code block of all candidate URLs. PRs only fire when the candidate set actually changes — the JSON file is deterministic across runs.
The site already runs against live Firestore via REST. To upgrade to real-time push updates via onSnapshot instead of 60-second polling, drop a full firebaseConfig object into public/assets/firebase-config.js:
window.FIREBASE_CONFIG = {
apiKey: "AIzaSy...",
authDomain: "projectdaedalus-fb09f.firebaseapp.com",
projectId: "projectdaedalus-fb09f",
// ... etc
};Configs are public-safe by design — security comes from Firestore Rules, not from hiding the apiKey. The loader prefers the Web SDK if present, falls back to REST otherwise.
For anonymous mod ratings to write back to Firestore, this rule needs to be in place:
match /ratings/{modId}/raters/{fingerprint} {
allow read: if true;
allow write: if request.resource.data.rating is int
&& request.resource.data.rating >= 1
&& request.resource.data.rating <= 5;
allow delete: if true;
}
The Discussion section on every mod page (and the Mod Requests board) is wired against this repo's GitHub Discussions. The repo + category IDs are pre-filled. The one-time setup step a maintainer needs to do:
- Visit https://github.com/apps/giscus
- Configure → choose Only select repositories → tick daedalus-static-poc
- Click Install
After install, the per-mod threads + the Mod Requests board are fully functional.
| Rails (today) | Static POC | |
|---|---|---|
| Hosting cost | Container host | $0, GitHub Pages |
| Server maintenance | Yes | None |
| Mod data refresh | Cron on Donovan's local machine | GitHub Actions cron, hourly |
| Mod data freshness in browser | up to 5 min Rails cache | up to 60s Firestore polling (or sub-second with Web SDK) |
| Search | Server-side via Turbo Frame | Client-side over fetched JSON |
| Pagination | Server-rendered per page | Client-side over the same fetched array |
| Anonymous mod ratings | Server-handled | Browser writes directly, security rules enforce |
| Comments | None on production | GitHub Discussions via Giscus |
| Mod Requests | External Canny.io | GitHub Discussions Ideas category |
| Mod Age / Freshness | Static field | Live from GitHub commit history |
| Auto-discovery of new modders | None | Weekly workflow opens a PR with candidates |
See CONTRIBUTING.md. The most useful contribution is getting added to production's meta/repos — once Donovan adds a repo, the hourly cron picks it up automatically.
MIT — see LICENSE.