Your personal event command center — capture events, keep every link handy, schedule with confidence, and catch conflicts before they bite.
AntechEvents is a fast, framework-free multi-page app for tracking the events you're
planning or attending. It's built with semantic HTML5, compiled Tailwind CSS,
modern vanilla JavaScript (ES modules), Firebase Authentication, and Cloud
Firestore — no React/Vue/Angular, no bundler, no runtime CSS-in-JS. Each page is
served at a clean, extensionless URL (/dashboard, not /dashboard.html).
- Overview
- Latest Release
- Core Features
- Project Structure
- Tech Stack
- Getting Started
- Running the Project
- Firebase Setup
- Data Model
- Conflict Detection
- Deferred Integrations
- Progressive Web App
- Design & Conventions
- Deployment
- Developer
- License
The goal is a calm, focused place to manage personal events without the friction of a full calendar suite. AntechEvents brings together:
- a dashboard that answers "what's next?" at a glance
- an agenda grouped by day, with search, status filters, and time scopes
- one create/edit form with a live conflict preview
- deterministic overlap detection so you never double-book by accident
- a free/busy availability view with manually blocked busy periods
- shareable free/busy links with owner-approved meeting requests
- per-user data, private by default and enforced by Firestore security rules
- automatic sign-out after five minutes without activity, shared across open tabs
Every screen handles its loading, empty, filtered-empty, validation, and error states explicitly, and the whole UI is built mobile-first with accessibility as a baseline.
AntechEvents is now an installable Progressive Web App. Add it to your device, launch it full-screen, keep using pages you've already opened when your connection drops, and get reminded on your device before events start — all with no backend and nothing to pay for.
- Install to your device — standalone, full-screen on mobile and desktop, with a contextual install prompt that only appears once you actually have events.
- Works offline — visited pages stay available without a connection, with a clean offline screen and a "Try again" action for everything else.
- Device reminders — opt in from Settings for real notifications before an event starts (while a tab is open). Titles and times only, never notes or location.
- Safe updates — an unobtrusive banner lets you update on your terms; it never reloads mid-interaction.
- Connectivity awareness — a subtle indicator when you go offline and come back, with no surprise retries of failed actions.
Auth and Firestore responses are never cached, no secrets ship to the browser, and every capability is feature-detected — on unsupported browsers AntechEvents runs as a normal website. Background push while the app is fully closed remains out of scope (it needs a server to send it); the groundwork is in place to add it later without a rewrite. See Progressive Web App for the technical detail.
A public landing page (/) introducing the product, with clear calls to action into
sign-up and log-in. Authenticated visitors are sent straight to their dashboard.
The central experience (/dashboard): a time-of-day greeting, today's counts, your
next event, upcoming events, schedule conflicts, an availability summary, and quick
actions.
An agenda (/events) grouped by day, with full-text search across title, location, and
organizer, status filters, and an upcoming / all / past time scope.
One form (/createevent, ?id= to edit) for both creating and editing, with inline
validation, progressive disclosure for optional details, a live conflict preview,
and a smart default end time drawn from your preferences.
A detail view (/event?id=…) with every action: edit, duplicate, mark confirmed /
attended, cancel, restore, open link, copy link, and delete behind an accessible
confirmation.
A per-day free/busy view (/availability) with manually blocked busy periods, a month
calendar for navigation, and a weekly summary.
Shareable links. Generate a capability link that shows others only your busy/free times — never event titles or details. Per link, choose whether visitors may leave a note or propose a meeting; proposals arrive as pending requests you approve, and accepting one creates the event. Links need no sign-in to view and are revocable at any time.
Account name, scheduling preferences (timezone, week start, default duration and
reminder), and notification choices (/settings).
Semantic landmarks, keyboard-navigable menus and modals with focus trapping, visible
focus rings, aria-live toasts, and status communicated with icon + text — never color
alone.
Each page lives in its own folder as index.html, so Hosting (and the local dev
server) serves it at a clean, extensionless path. Every asset and navigation reference
is root-absolute (/css, /js, /dashboard) so links resolve identically no
matter how deep the current URL is.
.
├── index.html # Landing page → /
├── login/index.html # Auth screens → /login
├── register/index.html # → /register
├── dashboard/index.html # Central product experience → /dashboard
├── events/index.html # Agenda list → /events
├── event/index.html # Event detail → /event?id=…
├── createevent/index.html # Create / edit form → /createevent (?id= to edit)
├── availability/index.html # Free/busy + month calendar → /availability
├── share/index.html # Public shared free/busy → /share?token=…
├── settings/index.html # Account, preferences → /settings
├── css/
│ ├── input.css # Tailwind entry: @theme tokens + component classes
│ └── output.css # Compiled stylesheet (committed; regenerate on change)
├── js/
│ ├── firebase.js # SDK init + single re-export surface
│ ├── app.js # Auth-guarded shell bootstrap (initShell)
│ ├── auth.js # Sign in / up / out, guards, redirect safety
│ ├── navigation.js # Header + mobile tab bar + account menu
│ ├── ui.js # Icons, toasts, modals, badges, state placeholders
│ ├── eventcard.js # Reusable event card
│ ├── conflicts.js # Deterministic overlap detection
│ ├── reminders.js # Reminder timing + inert delivery boundary
│ ├── eventinbox.js # Prepared Event Inbox boundary (disabled)
│ ├── <page>.js # One controller per page (dashboard.js, events.js, …)
│ ├── share.js # Public share page controller (no auth)
│ ├── services/ # Firestore data access (events, users, availability, shares)
│ └── utils/ # dates, intervals, formatters, validation, storage, token
├── assets/icons/favicon.svg
├── firebase/
│ ├── firestore.rules # Per-user ownership rules
│ └── firestore.indexes.json # Composite indexes (events, availability)
├── firebase.json # Hosting + Firestore config
└── package.json
Layering: pages render and wire only; all Firestore access lives in js/services/*;
pure logic (dates, formatting, validation, conflicts) has no Firebase or DOM dependency
and is trivially testable in isolation.
The root-level index.html, manifest.json, sw.js, and offline.html are web/PWA
entry points that must stay at the site root. package.json, its lockfile, and hosting
configuration also stay at the root because npm and the hosting providers resolve them
there. Other application code, styles, assets, and route pages are already grouped in
their respective folders.
There is no build step for JavaScript — modules are served as-is and import the Firebase SDK from Google's pinned CDN. The only compile step is Tailwind → CSS.
You'll need Node 18+. The repo is wired to a Firebase project called antechevent;
point it at your own by editing the config in js/firebase.js.
git clone <repository-url> antechevents
cd antecheventsnpm installDependencies are dev-only: the Tailwind CLI and a static file server. The app ships no runtime npm dependencies.
npm run build:css # compile css/input.css → css/output.css (minified)npm run serve # static server at http://localhost:5173The local server resolves clean URLs the same way Hosting does — /dashboard serves
dashboard/index.html — so what you see locally matches production.
There's no JavaScript build step; modules load the Firebase SDK from Google's pinned CDN, so the only thing that compiles is the stylesheet. While working, run the watcher in a second terminal to rebuild CSS on save:
npm run watch:css # or: npm run dev| Script | What it does |
|---|---|
npm run build:css |
One-off Tailwind compile to css/output.css (minified) |
npm run watch:css |
Rebuild css/output.css on every change |
npm run serve |
Serve the project at http://localhost:5173 |
npm run dev |
Alias for watch:css |
The web config in js/firebase.js is committed. These values aren't
secrets — Firestore rules do the enforcing — so they're safe to ship to the browser. To
back the app with your own project:
-
Authentication → Sign-in method — enable Email/Password and Google.
-
Authentication → Settings → Authorized domains — add
localhostand your Firebase Hosting domain. -
Firestore Database — create one in production mode.
-
Deploy the security rules and composite indexes:
firebase deploy --only firestore:rules,firestore:indexes
Rules and indexes live in firebase/. The composite indexes backing the
ownerId + startAt queries (events and availability) are declared in
firebase/firestore.indexes.json, so they deploy
with the command above — no manual index creation in the console required.
Four collections, each scoped to its owner by ownerId:
| Collection | Contents |
|---|---|
users/{uid} |
Profile and scheduling/notification preferences. |
events/{eventId} |
A single event and its status. |
availability/{docId} |
A manually blocked busy period. |
shares/{token} |
A public free/busy link: label, timezone, permission flags, revoke state, and a snapshot of merged busy times. |
An event document holds:
title, description, startAt, endAt, timezone, location, eventUrl,
registrationUrl, organizer, notes, status, reminderSettings,
ownerId, createdAt, updatedAt
status is one of planned, registered, confirmed, attended, or cancelled.
Dates persist as Firestore Timestamps and are converted to Date at the service
layer — nothing above js/services/ ever touches a Timestamp.
A shares/{token} document's id is an unguessable capability token. It stores only a
denormalized snapshot of merged busy time ranges (start/end timestamps) plus an
owner-chosen label, timezone, and the allowNotes / allowProposals / revoked flags —
no event titles, locations, or counts. Visitors' notes and meeting proposals live in a
shares/{token}/submissions/{subId} subcollection:
type (note|proposal), name, email, message, proposedStart?, proposedEnd?, status, createdAt
The public /share page reads a single non-revoked share by token — it can't list or
enumerate links, and the private events / availability collections are never exposed.
Proposals are created with status: pending and never touch the calendar until the owner
accepts one, which creates a normal event.
firebase/firestore.rules locks every document to its
owner: reads and writes require request.auth.uid == resource.data.ownerId, and new
events must carry the caller's ownerId and a non-empty title. The one public surface is a
non-revoked shares/{token} document (busy/free times only); a visitor may create a
submissions entry only when the link permits it, and the rules validate every field and
size. Listing shares is denied to the public, so links can't be enumerated. The
client-side checks are there for a clean UX; the rules are the real authorization boundary.
Two active events conflict when their intervals overlap:
startA < endB && endA > startB
Cancelled events are ignored. If two events fall on the same day but one is missing an
end time, they're reported as a possible conflict rather than a definite one. The
logic lives in js/conflicts.js and has no Firebase or DOM
dependency.
Two features are scaffolded behind boundaries so they can land later without touching the core app:
- Event Inbox (
js/eventinbox.js) — paste a link, have a backend extract the details, review the draft, save. Nothing runs client-side yet; the module returns an empty draft shaped like the create form. FlipEVENT_INBOX_ENABLEDonce the backend exists. - Email reminders (
js/reminders.js) — reminder timing is computed on the client, but delivery is a no-op. A backend (a Cloud Function, say) will read due reminders and call Resend. That key stays on the backend and never reaches the browser.
AntechEvents is installable and works offline for pages you've already opened.
- Manifest + icons —
manifest.json(standalone, portrait, brand theme) with the existing SVG mark plus a maskable variant. - Service worker (
sw.js) — versioned caches (antechevents-static-*,antechevents-runtime-*); network-first for navigations, stale-while-revalidate for static assets, and anoffline.htmlfallback. Auth and Firestore responses are never cached. BumpVERSIONto invalidate old caches. - Registration + updates (
js/pwa.js) — registers the worker on every page and shows an unobtrusive "Update" banner when a new version is waiting; the reload only happens on click, never mid-interaction. - Install prompt (
js/installprompt.js) — contextual, shown on the dashboard only after the user has events, and dismissable (persisted locally). - Device reminders (
js/notificationpermission.js) — opt-in from Settings. Due reminders surface as real OS notifications via the service worker while a tab is open — free, no backend. Titles + times only, never notes or location. Background push while the app is fully closed is out of scope: it requires a server to send Web Push/FCM with credentials that can't ship to the browser. The service worker keeps an inertpushhandler ready for that future backend, but no FCM SDK or token code ships today. - Network status (
js/networkstatus.js) — a subtle, announced online/offline indicator; never auto-retries failed writes.
Every capability is feature-detected: on unsupported browsers AntechEvents runs as a normal web app.
Authenticated pages sign the user out after five minutes without keyboard, pointer, touch, scroll, click, or form input activity. Activity is shared across tabs in the same browser, and the timeout is rechecked when a background tab becomes active again.
- No frameworks, no inline styles, no inline event handlers. Behavior is wired with
addEventListenerfrom ES modules. - Filenames are lowercase with no dashes (
createevent.js,eventservice.js);camelCasefor values,PascalCasefor constructors. - User input is escaped with
escapeHtmlbefore it reaches the DOM. - Design tokens live in
@themeincss/input.css; components consume them through Tailwind utilities. - Internal links and assets are root-absolute so pages work identically from any URL depth.
The app deploys to Firebase Hosting. firebase.json enables
cleanUrls with trailingSlash: false, and the folder-per-page layout means each route
resolves to its index.html at a canonical, extensionless URL.
npm run build:css # ensure css/output.css is current
firebase deploy --only hosting # ship the static site
firebase deploy --only firestore:rules,firestore:indexes # ship rules + indexesfirebase deploy with no flags does all three at once. The README.md, package.json,
css/input.css, and firebase/** paths are excluded from the Hosting upload via the
ignore list in firebase.json.
Built and maintained by Antech — Antony Mwendwa.
| Platform | Handle |
|---|---|
| Antech-greyhat | |
| AntechDevSecOps | |
| Antech1629 | |
| Antony Mwendwa | |
| +254 714 452 396 |
If AntechEvents is useful to you, consider a ⭐ and a follow at github.com/Antech-greyhat.
Private. All rights reserved.