founders-break/README.md

140 lines
6.4 KiB
Markdown

# The Founder's Break
Event site for the weekly UF startup pool social at Silver Q, Gainesville.
Hosted by OneTouch Studio at **https://foundersbreak.onetouch.studio**.
Corben EJS site. There is no build step: `deploy_site` copies this repo verbatim.
```
site.config.js all schedule, venue and copy data
views/index.ejs the single page, including the next-date logic
public/css/style.css brand tokens lifted from onetouch.studio
public/js/main.js nav state, scroll reveal, live countdown, RSVP submit
public/assets/ the recurring .ics and the print-resolution QR
public/images/ logo, favicon, RSVP QR
```
## The date is never hard-coded
`views/index.ejs` computes the next occurrence at render time from `weekday` +
`firstEvent` in `site.config.js`. `public/js/main.js` runs the same rule in the
browser, so the countdown ticks and rolls itself over to the following Thursday
once the night ends, with no reload and no redeploy.
## Cancelling a week
Add the ISO date to `cancelled` in `site.config.js` and redeploy:
```js
cancelled: ["2026-11-26"], // Thanksgiving
```
The page then skips that Thursday, shows a notice naming the cancelled date, and
points at the next one that is actually happening. Two other places do **not**
read this list and need the same date added by hand:
- `public/assets/founders-break.ics` is a plain weekly recurrence.
- the `CANCELLED` array in the `founders-break-reminder` Corben function, or it
will email everyone about a Break that is not happening.
## Deploying
```
deploy_site id=4ae63819-3f63-4fea-b0de-eb1e5befe607 repo=gator/founders-break mode=ejs
```
Nothing redeploys itself. Every edit needs the deploy step.
## What happens when somebody RSVPs
The form POSTs to `/forms/rsvp`. The Corben sites renderer stores it in
`site_form_submissions` and fires a `form.submitted` event. From there it is all
plain JavaScript, deliberately: there is no LLM anywhere in the send path.
| Piece | Id |
|---|---|
| Trigger `Founder's Break RSVP` (form.submitted, form_name=rsvp) | `e035fb68-2ecd-452b-bf74-a051c2775423` |
| Function `founders-break-rsvp` | `3d3c4bed-c073-40a4-9a32-9ff6944737dd` |
| Function `founders-break-reminder` | `cf2a1e4c-43f9-4cf4-8fc5-652c494940ae` |
| Cron `Founder's Break weekly reminder` (every 6h) | `84efd815-5426-4952-af88-2f4ed81efc54` |
| Email list `Founder's Break` | `09429866-0005-4260-bf56-2d520eea4db7` |
`founders-break-rsvp` resolves a CRM contact, writes the email address back onto
it (`create_contact` dedupes server-side and can return a record with no address,
which would put an unmailable member on the list), adds them to the list, sends
the HTML confirmation, and sends an internal note to gatordevin@gmail.com.
`founders-break-reminder` runs every 6 hours, works out the Eastern weekday
itself, and only sends on Wednesday between 09:00 and 20:00 ET. A `kv` marker
keyed to the event date makes any repeat run a no-op, which is what makes it safe
to run four times a day.
Both functions authenticate with a scoped `email + crm` key held by the paused
**Founders Break** agent record (`666f7ad0-f546-4fc6-a3c4-14f1384e91a5`). That
agent runs nothing. It exists only because an API key cannot mint another API
key. Deleting it revokes the key and breaks both functions.
## Sharing
`/flyer` (`views/flyer.ejs`) is the link to send someone: the poster shown as a
sheet, a download button, and the share controls. It is what the footer and the
post-RSVP confirmation both point at, rather than the raw PDF, so a share always
lands on something with context.
There is no general "share protocol". What exists is the **Web Share API**
(`navigator.share`), which on a phone opens the real OS share sheet and is the
only way to reach apps a web page cannot link to. Where `navigator.canShare`
allows files it shares the PDF itself, which is far better in a group chat than
a link; it falls back to sharing the URL, and the button stays hidden entirely on
browsers without support. Under that sit plain per-channel URLs (SMS, mailto,
WhatsApp, X, LinkedIn, Facebook) that work with no JavaScript at all, and a
copy-to-clipboard with a select-the-text fallback for in-app browsers that block
the clipboard.
`site.config.js -> images.share` is a 1200x630 unfurl card carrying the same four
facts the flyer leads with. Both `/` and `/flyer` use it for `og:image`, so a
pasted link never previews as a wordless photo. Regenerate it from
`design/og-card.html` the same way as the flyer.
## The printable flyer
`public/assets/founders-break-flyer.pdf` is a one-page US Letter flyer, linked
from the footer and from the confirmation someone sees right after they RSVP,
which is the moment they might think about bringing people. The source that
generates it lives in `design/` with regeneration instructions; edit that, do not
re-design the PDF.
## The QR code
`public/images/rsvp-qr.png` and `public/assets/rsvp-qr.svg` both encode
`https://foundersbreak.onetouch.studio/#rsvp`. Deliberately dark-on-white:
inverted QR codes are unreliable on older scanners and these go on flyers.
Regenerate with the `qrcode` npm package if the URL ever changes.
## Tracking
The Corben tracker is injected into every EJS site automatically, so pageviews
(with UTM, referrer, device, browser), clicks on any `a`/`button`/`[data-track]`,
`form_start`, `form_submit`, scroll depth and engagement time all land with no
work from us. Key elements carry a `data-track` label so the click rows are
readable: `rsvp-nav`, `rsvp-hero`, `rsvp-submit`, `add-to-calendar`, `venue-map`,
`details-map`, `venue-instagram`, `hero-what-to-expect`.
`public/js/main.js` adds the three steps the tracker cannot see on its own, via
the same public `/analytics/events` endpoint:
| Event | Means |
|---|---|
| `rsvp_reached` | scrolled the RSVP block into view |
| `rsvp_completed` | submission stored, `event_data.via` is `form` or `reminder-email` |
| `rsvp_failed` | submission rejected, with the reason |
So "reached but did not finish" is `rsvp_reached` minus `rsvp_completed`, and
"started typing then left" is `form_start` minus `form_submit`.
Emails carry a 1x1 open pixel at `GET /analytics/open.gif?s=<site>&k=<kind>&e=<email>&d=<date>`,
landing in `site_events` as `email_open` with kind `confirm-first`,
`confirm-returning` or `reminder`. Apple Mail Privacy Protection and Gmail's
image proxy prefetch images, so treat opens as a ceiling rather than a headcount.
Read any of it with `get_site_analytics`, `get_site_events` or `get_site_click_map`.