238 lines
11 KiB
Text
238 lines
11 KiB
Text
<%
|
|
/* ---- Next occurrence -------------------------------------------------
|
|
All arithmetic happens on a UTC Date that carries the *Eastern calendar
|
|
date*, never a wall-clock instant. That keeps day-of-week and day-adding
|
|
correct across DST without needing a timezone library. */
|
|
const TZ = site.timezone || "America/New_York";
|
|
const now = new Date();
|
|
|
|
const todayISO = new Intl.DateTimeFormat("en-CA", {
|
|
timeZone: TZ, year: "numeric", month: "2-digit", day: "2-digit",
|
|
}).format(now);
|
|
|
|
const hourET = Number(
|
|
new Intl.DateTimeFormat("en-US", { timeZone: TZ, hour: "2-digit", hourCycle: "h23" }).format(now)
|
|
) % 24;
|
|
|
|
const toDate = (iso) => { const [y, m, d] = iso.split("-").map(Number); return new Date(Date.UTC(y, m - 1, d)); };
|
|
const isoOf = (dt) => dt.toISOString().slice(0, 10);
|
|
|
|
let next = toDate(todayISO);
|
|
const first = toDate(site.firstEvent);
|
|
if (next < first) next = first;
|
|
|
|
while (next.getUTCDay() !== site.weekday) next.setUTCDate(next.getUTCDate() + 1);
|
|
if (isoOf(next) === todayISO && hourET >= (site.endHour24 || 21)) next.setUTCDate(next.getUTCDate() + 7);
|
|
|
|
const cancelled = site.cancelled || [];
|
|
let skippedWeek = null;
|
|
// Keep the FIRST skipped date: with two cancellations in a row the notice
|
|
// should name the soonest one people were expecting, not the last one.
|
|
while (cancelled.includes(isoOf(next))) {
|
|
if (!skippedWeek) skippedWeek = isoOf(next);
|
|
next.setUTCDate(next.getUTCDate() + 7);
|
|
}
|
|
|
|
const nextISO = isoOf(next);
|
|
const isTonight = nextISO === todayISO;
|
|
const fmtDate = (opts) => new Intl.DateTimeFormat("en-US", Object.assign({ timeZone: "UTC" }, opts)).format(next);
|
|
const nextLong = fmtDate({ weekday: "long", month: "long", day: "numeric" });
|
|
const nextShort = fmtDate({ weekday: "short", month: "short", day: "numeric" });
|
|
const weekdayName = new Intl.DateTimeFormat("en-US", { timeZone: "UTC", weekday: "long" }).format(next);
|
|
|
|
const heroLine = isTonight
|
|
? `Tonight at ${site.venue}, ${site.startTime} to ${site.endTime}. Grab a cue and meet the people building things in ${site.city}.`
|
|
: `Every ${weekdayName} at ${site.venue}, ${site.startTime} to ${site.endTime}. The next one is ${nextLong}.`;
|
|
|
|
const details = [
|
|
{ label: "When", value: `Every ${weekdayName}` },
|
|
{ label: "Time", value: `${site.startTime} to ${site.endTime}` },
|
|
{ label: "Where", value: `${site.venue}, ${site.city}`, href: site.mapUrl },
|
|
{ label: "Entry", value: site.entry },
|
|
];
|
|
%><!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title><%= site.name %> | <%= site.tagline %></title>
|
|
<meta name="description" content="<%= site.description %>">
|
|
<meta name="theme-color" content="#06060b">
|
|
|
|
<meta property="og:type" content="website">
|
|
<meta property="og:title" content="<%= site.name %>">
|
|
<meta property="og:description" content="<%= site.description %>">
|
|
<meta property="og:image" content="<%= site.images.hero %>">
|
|
<meta property="og:url" content="<%= site.url %>">
|
|
<meta name="twitter:card" content="summary_large_image">
|
|
|
|
<link rel="icon" href="/images/favicon.svg" type="image/svg+xml">
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Space+Grotesk:wght@500;600;700&display=swap" rel="stylesheet">
|
|
<link rel="stylesheet" href="/css/style.css">
|
|
<link rel="preload" as="image" href="<%= site.images.hero %>">
|
|
|
|
<!-- Gates the scroll-reveal styles on JS being alive, so a blocked or failed
|
|
main.js leaves the page fully readable instead of blank. -->
|
|
<script>document.documentElement.className += " js";</script>
|
|
|
|
<script type="application/ld+json">
|
|
<%- JSON.stringify({
|
|
"@context": "https://schema.org",
|
|
"@type": "Event",
|
|
name: site.name,
|
|
description: site.description,
|
|
startDate: `${nextISO}T19:00:00-04:00`,
|
|
endDate: `${nextISO}T21:00:00-04:00`,
|
|
eventSchedule: { "@type": "Schedule", repeatFrequency: "P1W", byDay: "https://schema.org/Thursday", startTime: "19:00", endTime: "21:00" },
|
|
eventStatus: "https://schema.org/EventScheduled",
|
|
eventAttendanceMode: "https://schema.org/OfflineEventAttendanceMode",
|
|
location: { "@type": "Place", name: site.venue, address: { "@type": "PostalAddress", addressLocality: site.city, addressRegion: "FL", addressCountry: "US" } },
|
|
organizer: { "@type": "Organization", name: site.host.name, url: site.host.url },
|
|
isAccessibleForFree: true,
|
|
image: [site.images.hero],
|
|
url: site.url,
|
|
}) %>
|
|
</script>
|
|
</head>
|
|
<body>
|
|
|
|
<a class="skip" href="#main">Skip to content</a>
|
|
|
|
<header class="nav" id="nav">
|
|
<a class="brand" href="/">
|
|
<img class="brand-mark" src="/images/onetouch-studio.svg" alt="" width="26" height="26">
|
|
<span class="brand-name"><%= site.name %></span>
|
|
</a>
|
|
<nav class="nav-right" aria-label="Primary">
|
|
<a class="nav-link" href="#details">Details</a>
|
|
<a class="nav-link" href="#expect">What to expect</a>
|
|
<a class="nav-cta" href="#rsvp">RSVP</a>
|
|
</nav>
|
|
</header>
|
|
|
|
<main id="main">
|
|
|
|
<!-- Hero: asymmetric split, copy left / photograph right -->
|
|
<section class="hero">
|
|
<div class="hero-copy">
|
|
<p class="eyebrow"><%= site.tagline %></p>
|
|
<h1><%= site.name %></h1>
|
|
<p class="hero-sub"><%= heroLine %></p>
|
|
<div class="hero-actions">
|
|
<a class="btn btn-primary" href="#rsvp">RSVP</a>
|
|
<a class="btn btn-ghost" href="#expect">What to expect</a>
|
|
</div>
|
|
</div>
|
|
<figure class="hero-figure">
|
|
<img src="<%= site.images.hero %>" width="1536" height="1024" fetchpriority="high"
|
|
alt="Four people talking beside a lit pool table in a dark bar.">
|
|
</figure>
|
|
</section>
|
|
|
|
<!-- Details: display-value strip, vertical hairlines only -->
|
|
<section class="details" id="details">
|
|
<% if (skippedWeek) { %>
|
|
<p class="notice" role="status">No Break on <%= new Intl.DateTimeFormat("en-US", { timeZone: "UTC", month: "long", day: "numeric" }).format(toDate(skippedWeek)) %>. Back on <%= nextLong %>.</p>
|
|
<% } %>
|
|
<dl class="detail-grid reveal">
|
|
<% details.forEach(function (d) { %>
|
|
<div class="detail">
|
|
<dt><%= d.label %></dt>
|
|
<dd><% if (d.href) { %><a href="<%= d.href %>" target="_blank" rel="noopener"><%= d.value %></a><% } else { %><%= d.value %><% } %></dd>
|
|
</div>
|
|
<% }) %>
|
|
</dl>
|
|
<p class="detail-foot">
|
|
<span<% if (isTonight) { %> class="is-live"<% } %>><%= isTonight ? "Happening tonight" : `Next: ${nextShort}` %></span>
|
|
<a class="ics" href="/assets/founders-break.ics">Add the whole series to your calendar</a>
|
|
</p>
|
|
</section>
|
|
|
|
<!-- Pitch: full-bleed editorial band over the break shot -->
|
|
<section class="pitch" style="--pitch-image: url('<%= site.images.breakShot %>')">
|
|
<div class="pitch-inner reveal">
|
|
<h2>We are building Gainesville's startup culture, one terrible pool shot at a time.</h2>
|
|
<p>Whether you are an engineer, a designer, a business strategist, or just an ambitious person driven to build things from the ground up, this is your crowd.</p>
|
|
<p class="pitch-kicker">Zero pool skills required. Seriously. We are here to build startups, not run the table.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- What to expect: bento, four cells, one photographic -->
|
|
<section class="expect" id="expect">
|
|
<h2 class="section-title reveal">What to expect</h2>
|
|
<div class="bento">
|
|
<figure class="cell cell-photo reveal">
|
|
<img src="<%= site.images.napkin %>" width="1024" height="1024" loading="lazy"
|
|
alt="Three students laughing over a napkin sketch at the rail of a pool table.">
|
|
</figure>
|
|
<% site.expect.forEach(function (item, i) { %>
|
|
<div class="cell cell-text<%= i === 2 ? " cell-wide" : "" %><%= i === 1 ? " cell-tint" : "" %> reveal">
|
|
<h3><%= item.title %></h3>
|
|
<p><%= item.body %></p>
|
|
</div>
|
|
<% }) %>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- RSVP -->
|
|
<section class="rsvp" id="rsvp">
|
|
<div class="rsvp-panel reveal">
|
|
<h2>RSVP for <%= isTonight ? "tonight" : nextLong %></h2>
|
|
<p class="rsvp-sub">Not required, but it helps us know how many tables to hold. We will email you a reminder the day before.</p>
|
|
|
|
<form id="rsvp-form" method="POST" action="/forms/rsvp" novalidate>
|
|
<input type="hidden" name="_redirect" value="/?rsvp=ok#rsvp">
|
|
<input type="hidden" name="event_date" value="<%= nextISO %>">
|
|
<p class="hp" aria-hidden="true"><label>Leave this empty<input type="text" name="company_website" tabindex="-1" autocomplete="off"></label></p>
|
|
|
|
<div class="field">
|
|
<label for="f-name">Name</label>
|
|
<input id="f-name" name="name" type="text" autocomplete="name" required>
|
|
<p class="field-error" id="e-name"></p>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label for="f-email">Email</label>
|
|
<input id="f-email" name="email" type="email" autocomplete="email" inputmode="email" required>
|
|
<p class="field-help">For the weekly reminder. Nothing else.</p>
|
|
<p class="field-error" id="e-email"></p>
|
|
</div>
|
|
|
|
<div class="field">
|
|
<label for="f-building">What are you building? <span class="opt">Optional</span></label>
|
|
<input id="f-building" name="building" type="text" autocomplete="off">
|
|
<p class="field-help">A few words is plenty. It helps us introduce you to the right people.</p>
|
|
</div>
|
|
|
|
<button class="btn btn-primary btn-block" type="submit" id="rsvp-submit">RSVP</button>
|
|
<p class="form-status" id="form-status" role="status" aria-live="polite"></p>
|
|
<p class="fine">One email a week at most. Unsubscribe any time.</p>
|
|
</form>
|
|
|
|
<div class="rsvp-done" id="rsvp-done" hidden>
|
|
<h3>You are on the list.</h3>
|
|
<p>See you <%= isTonight ? "tonight" : nextLong %> at <%= site.venue %>, <%= site.startTime %>. Bring your ideas and a valid ID.</p>
|
|
<a class="ics" href="/assets/founders-break.ics">Add the whole series to your calendar</a>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
</main>
|
|
|
|
<footer class="foot">
|
|
<p class="foot-line">Bring your ideas and a valid ID.</p>
|
|
<div class="foot-meta">
|
|
<a class="foot-host" href="<%= site.host.url %>" target="_blank" rel="noopener">
|
|
<img src="/images/onetouch-studio.svg" alt="" width="20" height="20">
|
|
<span>Hosted by <%= site.host.name %></span>
|
|
</a>
|
|
<a href="mailto:<%= site.host.email %>"><%= site.host.email %></a>
|
|
<a href="<%= site.mapUrl %>" target="_blank" rel="noopener"><%= site.venue %>, <%= site.city %></a>
|
|
</div>
|
|
</footer>
|
|
|
|
<script src="/js/main.js" defer></script>
|
|
</body>
|
|
</html>
|