Add live countdown, Silver Q venue section, real address, OneTouch favicon
This commit is contained in:
parent
cd91fbbb95
commit
7a331d241a
6 changed files with 329 additions and 30 deletions
|
|
@ -28,9 +28,9 @@ DTSTART;TZID=America/New_York:20260827T190000
|
|||
DTEND;TZID=America/New_York:20260827T210000
|
||||
RRULE:FREQ=WEEKLY;BYDAY=TH
|
||||
SUMMARY:The Founder's Break
|
||||
LOCATION:Silver Q\, Gainesville\, FL
|
||||
LOCATION:Silver Q Billiards & Sports Bar\, 225 W University Ave\, Gainesville\, FL 32601
|
||||
URL:https://foundersbreak.onetouch.studio
|
||||
DESCRIPTION:UF startup pool social. Every Thursday\, 7 to 9 PM at Silver Q.
|
||||
DESCRIPTION:UF startup pool social. Every Thursday\, 7 to 9 PM at Silver Q\, 225 W University Ave.
|
||||
Zero pool skills required. Free\, 18+. Bring your ideas and a valid ID.\n\n
|
||||
Hosted by OneTouch Studio. Details and RSVP: https://foundersbreak.onetouch.
|
||||
studio
|
||||
|
|
|
|||
|
|
@ -161,6 +161,53 @@ a { color: var(--accent2); }
|
|||
background: linear-gradient(190deg, transparent 55%, rgba(6, 6, 11, 0.5));
|
||||
}
|
||||
|
||||
/* ── Countdown ────────────────────────────────────────────── */
|
||||
|
||||
.countdown-band { max-width: var(--shell); margin: 0 auto; padding: 0 var(--gut) 4.5rem; }
|
||||
|
||||
.countdown {
|
||||
position: relative; isolation: isolate; overflow: hidden;
|
||||
padding: 2.6rem 2rem;
|
||||
border: 1px solid var(--border); border-radius: var(--r-surface);
|
||||
background:
|
||||
radial-gradient(90% 140% at 50% 0%, rgba(16, 185, 129, 0.12), transparent 68%),
|
||||
var(--bg2);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.countdown-label {
|
||||
font-size: 0.72rem; font-weight: 700; letter-spacing: 0.16em;
|
||||
text-transform: uppercase; color: var(--accent2);
|
||||
}
|
||||
|
||||
.countdown-clock {
|
||||
display: flex; justify-content: center; align-items: flex-start;
|
||||
gap: clamp(1.2rem, 5vw, 3.4rem);
|
||||
margin: 1.5rem 0 1.2rem;
|
||||
}
|
||||
.countdown-clock .unit { display: flex; flex-direction: column; align-items: center; gap: 0.35rem; min-width: 3.4ch; }
|
||||
.countdown-clock .num {
|
||||
font-family: "Space Grotesk", sans-serif; font-weight: 700;
|
||||
font-size: clamp(2.4rem, 7vw, 4rem); line-height: 1;
|
||||
font-variant-numeric: tabular-nums; letter-spacing: -0.03em;
|
||||
}
|
||||
.countdown-clock .cap {
|
||||
font-size: 0.7rem; font-weight: 600; letter-spacing: 0.13em;
|
||||
text-transform: uppercase; color: var(--muted);
|
||||
}
|
||||
/* One-frame lift as a digit changes, so the clock reads as live rather than static. */
|
||||
.countdown-clock .num.tick { animation: tick 0.42s var(--ease); }
|
||||
@keyframes tick {
|
||||
0% { transform: translateY(-0.14em); opacity: 0.45; }
|
||||
100% { transform: none; opacity: 1; }
|
||||
}
|
||||
|
||||
.countdown-when { color: var(--muted); font-size: 0.95rem; }
|
||||
|
||||
.countdown.is-live { border-color: var(--border-hover); }
|
||||
.countdown.is-live .countdown-label { color: var(--accent2); }
|
||||
.countdown.is-live .countdown-when { color: var(--text); font-weight: 600; }
|
||||
|
||||
/* ── Details: display-value strip ─────────────────────────── */
|
||||
|
||||
.details { max-width: var(--shell); margin: 0 auto; padding: 0 var(--gut) 6rem; }
|
||||
|
|
@ -248,6 +295,39 @@ a { color: var(--accent2); }
|
|||
.cell-text h3 { font-size: 1.16rem; font-weight: 600; margin-bottom: 0.65rem; }
|
||||
.cell-text p { color: var(--muted); font-size: 0.96rem; max-width: 52ch; }
|
||||
|
||||
/* ── Venue ────────────────────────────────────────────────── */
|
||||
|
||||
.venue {
|
||||
max-width: var(--shell); margin: 0 auto;
|
||||
padding: 0 var(--gut) 7rem;
|
||||
display: grid; grid-template-columns: 0.85fr 1.15fr; gap: 4rem; align-items: center;
|
||||
}
|
||||
.venue-logo { width: auto; height: clamp(96px, 13vw, 132px); margin-bottom: 1.8rem; opacity: 0.95; }
|
||||
.venue h2 { font-size: clamp(1.6rem, 3.2vw, 2.2rem); font-weight: 700; }
|
||||
.venue p { margin-top: 1rem; color: var(--muted); max-width: 46ch; }
|
||||
|
||||
.venue address { margin-top: 1.4rem; font-style: normal; }
|
||||
.venue address a {
|
||||
font-family: "Space Grotesk", sans-serif; font-weight: 600;
|
||||
font-size: 1.12rem; line-height: 1.45; color: var(--text); text-decoration: none;
|
||||
border-bottom: 1px solid var(--border-hover);
|
||||
}
|
||||
.venue address a:hover { color: var(--accent2); }
|
||||
|
||||
.venue-meta {
|
||||
display: flex; flex-wrap: wrap; gap: 0.5rem 1.5rem;
|
||||
margin-top: 1.4rem; font-size: 0.9rem;
|
||||
}
|
||||
.venue-meta a { color: var(--accent2); text-decoration: none; border-bottom: 1px solid var(--border-hover); }
|
||||
.venue-meta a:hover { border-bottom-color: var(--accent2); }
|
||||
.venue-meta span { color: var(--muted); }
|
||||
|
||||
.venue-figure img {
|
||||
width: 100%; aspect-ratio: 3 / 2; object-fit: cover;
|
||||
border-radius: var(--r-surface); border: 1px solid var(--hairline);
|
||||
}
|
||||
.venue-figure figcaption { margin-top: 0.8rem; font-size: 0.82rem; color: var(--fine); }
|
||||
|
||||
/* ── RSVP ─────────────────────────────────────────────────── */
|
||||
|
||||
.rsvp { padding: 0 var(--gut) 7rem; }
|
||||
|
|
@ -321,9 +401,17 @@ form { margin-top: 2rem; }
|
|||
.cell-wide { grid-column: span 2; }
|
||||
}
|
||||
|
||||
@media (max-width: 1000px) {
|
||||
.venue { grid-template-columns: 1fr; gap: 2.4rem; }
|
||||
.venue-figure { order: -1; }
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
:root { --gut: 1.25rem; }
|
||||
.nav-link { display: none; }
|
||||
.countdown { padding: 2rem 1rem; }
|
||||
.countdown-clock { gap: 0.9rem; }
|
||||
.venue { padding-bottom: 5rem; }
|
||||
.detail-grid { grid-template-columns: 1fr 1fr; }
|
||||
.detail { padding: 1.4rem 1.1rem; }
|
||||
.detail:nth-child(odd) { border-left: 0; }
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
|
||||
<!-- OneTouch Studio's mark, cropped to the cube and ring so it still reads at 16px. -->
|
||||
<rect width="64" height="64" rx="14" fill="#06060b"/>
|
||||
<g transform="translate(32 32)" fill="none" stroke="#10b981" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="0" cy="0" r="23" stroke-width="2.5"/>
|
||||
<g transform="scale(0.62)" stroke-width="4.5">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 672 672" width="672" height="672">
|
||||
<!-- The OneTouch Studio mark exactly as it ships on onetouch.studio, sat on a
|
||||
dark rounded square so it stays visible in a light browser tab. -->
|
||||
<rect width="672" height="672" rx="148" fill="#06060b"/>
|
||||
<g transform="translate(336 336)">
|
||||
<circle cx="0" cy="0" r="160" fill="none" stroke="#FFFFFF" stroke-width="5.5"/>
|
||||
<circle cx="0" cy="0" r="223" fill="none" stroke="#FFFFFF" stroke-width="7.5"/>
|
||||
<circle cx="0" cy="0" r="279" fill="none" stroke="#FFFFFF" stroke-width="5.5"/>
|
||||
|
||||
<g transform="scale(0.72)" fill="none" stroke="#FFFFFF" stroke-width="8" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polygon points="0,-34 30,-14 0,6 -30,-14"/>
|
||||
<polygon points="0,6 30,-14 30,20 0,40 -30,20 -30,-14"/>
|
||||
<line x1="0" y1="-34" x2="0" y2="6"/>
|
||||
|
|
@ -11,6 +15,17 @@
|
|||
<line x1="-30" y1="-14" x2="-30" y2="20"/>
|
||||
<line x1="30" y1="-14" x2="30" y2="20"/>
|
||||
</g>
|
||||
<circle cx="0" cy="-23" r="3.4" fill="#10b981" stroke="none"/>
|
||||
|
||||
<circle cx="0" cy="-279" r="9" fill="#FFFFFF"/>
|
||||
<circle cx="223" cy="0" r="9" fill="#FFFFFF"/>
|
||||
<circle cx="0" cy="223" r="9" fill="#FFFFFF"/>
|
||||
<circle cx="-197.28" cy="-197.28" r="6.5" fill="#FFFFFF"/>
|
||||
<circle cx="113.14" cy="113.14" r="5.5" fill="#FFFFFF"/>
|
||||
|
||||
<line x1="-12" y1="-279" x2="12" y2="-279" stroke="#FFFFFF" stroke-width="3" stroke-linecap="round"/>
|
||||
<line x1="223" y1="-12" x2="223" y2="12" stroke="#FFFFFF" stroke-width="3" stroke-linecap="round"/>
|
||||
<line x1="-12" y1="223" x2="12" y2="223" stroke="#FFFFFF" stroke-width="3" stroke-linecap="round"/>
|
||||
<line x1="-205.76" y1="-188.80" x2="-188.80" y2="-205.76" stroke="#FFFFFF" stroke-width="2.5" stroke-linecap="round"/>
|
||||
<line x1="108.19" y1="118.09" x2="118.09" y2="108.19" stroke="#FFFFFF" stroke-width="2.5" stroke-linecap="round"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 834 B After Width: | Height: | Size: 1.8 KiB |
|
|
@ -1,9 +1,10 @@
|
|||
/* The Founder's Break - nav state, scroll reveal, RSVP submit.
|
||||
/* The Founder's Break - nav state, scroll reveal, live countdown, RSVP submit.
|
||||
No scroll listeners: nav state and reveal both ride IntersectionObserver. */
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var TZ = "America/New_York";
|
||||
var reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||
|
||||
/* ── Nav background once the page has moved off the top ── */
|
||||
|
|
@ -23,14 +24,130 @@
|
|||
var io = new IntersectionObserver(function (entries, obs) {
|
||||
entries.forEach(function (entry, i) {
|
||||
if (!entry.isIntersecting) return;
|
||||
var delay = Math.min(i, 4) * 70;
|
||||
setTimeout(function () { entry.target.classList.add("in"); }, delay);
|
||||
setTimeout(function () { entry.target.classList.add("in"); }, Math.min(i, 4) * 70);
|
||||
obs.unobserve(entry.target);
|
||||
});
|
||||
}, { threshold: 0.15, rootMargin: "0px 0px -8% 0px" });
|
||||
reveals.forEach(function (el) { io.observe(el); });
|
||||
}
|
||||
|
||||
/* ── Live countdown ──────────────────────────────────────
|
||||
Mirrors the rule in views/index.ejs so the clock keeps itself honest
|
||||
without a reload: once an event ends, it rolls to the following week
|
||||
on its own, skipping any cancelled date. */
|
||||
|
||||
var el = document.getElementById("countdown");
|
||||
if (el) startCountdown(el);
|
||||
|
||||
function etParts(date) {
|
||||
// "2026-08-27" plus the weekday index, both in Eastern time.
|
||||
var ymd = new Intl.DateTimeFormat("en-CA", {
|
||||
timeZone: TZ, year: "numeric", month: "2-digit", day: "2-digit",
|
||||
}).format(date);
|
||||
var wd = new Intl.DateTimeFormat("en-US", { timeZone: TZ, weekday: "short" }).format(date);
|
||||
var order = { Sun: 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6 };
|
||||
return { iso: ymd, day: order[wd] };
|
||||
}
|
||||
|
||||
function etOffsetHours(iso) {
|
||||
var label = new Intl.DateTimeFormat("en-US", { timeZone: TZ, timeZoneName: "shortOffset" })
|
||||
.format(new Date(iso + "T12:00:00Z"));
|
||||
var m = label.match(/GMT([+-]?\d{1,2})/);
|
||||
return m ? parseInt(m[1], 10) : -5;
|
||||
}
|
||||
|
||||
function instantFor(iso, hour) {
|
||||
var p = iso.split("-").map(Number);
|
||||
return new Date(Date.UTC(p[0], p[1] - 1, p[2], hour - etOffsetHours(iso), 0, 0));
|
||||
}
|
||||
|
||||
function startCountdown(root) {
|
||||
var weekday = Number(root.dataset.weekday);
|
||||
var startHour = Number(root.dataset.startHour);
|
||||
var endHour = Number(root.dataset.endHour);
|
||||
var first = root.dataset.first;
|
||||
var cancelled = (root.dataset.cancelled || "").split(",").filter(Boolean);
|
||||
|
||||
var nums = {};
|
||||
root.querySelectorAll("[data-unit]").forEach(function (n) { nums[n.dataset.unit] = n; });
|
||||
var label = document.getElementById("countdown-label");
|
||||
var when = document.getElementById("countdown-when");
|
||||
var clock = document.getElementById("countdown-clock");
|
||||
|
||||
function nextOccurrence(now) {
|
||||
var today = etParts(now);
|
||||
var parts = today.iso.split("-").map(Number);
|
||||
var cur = new Date(Date.UTC(parts[0], parts[1] - 1, parts[2]));
|
||||
var firstParts = first.split("-").map(Number);
|
||||
var firstDate = new Date(Date.UTC(firstParts[0], firstParts[1] - 1, firstParts[2]));
|
||||
if (cur < firstDate) cur = firstDate;
|
||||
|
||||
var iso = function (d) { return d.toISOString().slice(0, 10); };
|
||||
while (cur.getUTCDay() !== weekday) cur.setUTCDate(cur.getUTCDate() + 1);
|
||||
// Only roll past today once the night is actually over.
|
||||
if (iso(cur) === today.iso && now >= instantFor(today.iso, endHour)) {
|
||||
cur.setUTCDate(cur.getUTCDate() + 7);
|
||||
}
|
||||
while (cancelled.indexOf(iso(cur)) !== -1) cur.setUTCDate(cur.getUTCDate() + 7);
|
||||
return iso(cur);
|
||||
}
|
||||
|
||||
function render() {
|
||||
var now = new Date();
|
||||
var iso = nextOccurrence(now);
|
||||
var start = instantFor(iso, startHour);
|
||||
var end = instantFor(iso, endHour);
|
||||
|
||||
var pretty = new Intl.DateTimeFormat("en-US", {
|
||||
timeZone: TZ, weekday: "long", month: "long", day: "numeric",
|
||||
}).format(start);
|
||||
|
||||
if (now >= start && now < end) {
|
||||
root.classList.add("is-live");
|
||||
label.textContent = "Happening right now";
|
||||
clock.hidden = true;
|
||||
when.textContent = "Until " + fmtTime(end) + " tonight. Come find us at the tables.";
|
||||
return;
|
||||
}
|
||||
|
||||
root.classList.remove("is-live");
|
||||
clock.hidden = false;
|
||||
var diff = Math.max(0, start - now);
|
||||
var total = Math.floor(diff / 1000);
|
||||
set("days", Math.floor(total / 86400));
|
||||
set("hours", Math.floor(total / 3600) % 24);
|
||||
set("minutes", Math.floor(total / 60) % 60);
|
||||
set("seconds", total % 60);
|
||||
|
||||
label.textContent = total < 86400 ? "The Break is tonight" : "Until the next Break";
|
||||
when.textContent = pretty + ", " + fmtTime(start) + " at Silver Q";
|
||||
}
|
||||
|
||||
function fmtTime(d) {
|
||||
return new Intl.DateTimeFormat("en-US", { timeZone: TZ, hour: "numeric", minute: "2-digit" }).format(d);
|
||||
}
|
||||
|
||||
function set(unit, value) {
|
||||
var node = nums[unit];
|
||||
if (!node) return;
|
||||
var text = unit === "days" ? String(value) : String(value).padStart(2, "0");
|
||||
if (node.textContent === text) return;
|
||||
node.textContent = text;
|
||||
if (reduceMotion) return;
|
||||
node.classList.remove("tick");
|
||||
void node.offsetWidth; // restart the animation
|
||||
node.classList.add("tick");
|
||||
}
|
||||
|
||||
render();
|
||||
var timer = setInterval(render, 1000);
|
||||
// A backgrounded tab throttles the interval; catch up the moment it returns.
|
||||
document.addEventListener("visibilitychange", function () {
|
||||
if (!document.hidden) render();
|
||||
});
|
||||
window.addEventListener("pagehide", function () { clearInterval(timer); });
|
||||
}
|
||||
|
||||
/* ── RSVP ── */
|
||||
var form = document.getElementById("rsvp-form");
|
||||
if (!form) return;
|
||||
|
|
|
|||
|
|
@ -2,14 +2,15 @@
|
|||
//
|
||||
// The event recurs weekly, so nothing here is a one-off date. index.ejs computes
|
||||
// the next occurrence at render time from `weekday` + `firstEvent`, minus anything
|
||||
// listed in `cancelled`. To skip a week (holiday, venue closed, whatever), add the
|
||||
// ISO date to `cancelled` and redeploy. Do not hand-edit dates anywhere else.
|
||||
// listed in `cancelled`, and public/js/main.js runs the same rule client-side so a
|
||||
// page left open overnight still rolls itself forward. To skip a week, add the ISO
|
||||
// date to `cancelled` and redeploy. Do not hand-edit dates anywhere else.
|
||||
|
||||
module.exports = {
|
||||
name: "The Founder's Break",
|
||||
tagline: "UF Startup Pool Social",
|
||||
description:
|
||||
"A weekly pool social for the people building things in Gainesville. Every Thursday at Silver Q.",
|
||||
"A weekly pool social for the people building things in Gainesville. Every Thursday at Silver Q, 225 W University Ave.",
|
||||
|
||||
// Schedule
|
||||
timezone: "America/New_York",
|
||||
|
|
@ -17,15 +18,28 @@ module.exports = {
|
|||
firstEvent: "2026-08-27",
|
||||
startTime: "7:00 PM",
|
||||
endTime: "9:00 PM",
|
||||
startHour24: 19,
|
||||
endHour24: 21, // after this hour on event day, the page rolls to next week
|
||||
cancelled: [
|
||||
// "2026-11-26", // example: Thanksgiving
|
||||
],
|
||||
|
||||
// Venue
|
||||
venue: "Silver Q",
|
||||
city: "Gainesville",
|
||||
mapUrl: "https://www.google.com/maps/search/?api=1&query=Silver+Q+Gainesville+FL",
|
||||
// Venue. Every field here is sourced, not guessed. Silver Q Billiards & Sports
|
||||
// Bar, established 1992, is the host venue; it is not run by OneTouch Studio.
|
||||
venue: {
|
||||
name: "Silver Q",
|
||||
fullName: "Silver Q Billiards & Sports Bar",
|
||||
street: "225 W University Ave",
|
||||
city: "Gainesville",
|
||||
state: "FL",
|
||||
zip: "32601",
|
||||
note: "Downtown, about a mile from campus. Doors from 4:00 PM, we take over the tables at 7:00.",
|
||||
established: "1992",
|
||||
instagram: "silverqgnv",
|
||||
instagramUrl: "https://www.instagram.com/silverqgnv/",
|
||||
mapUrl:
|
||||
"https://www.google.com/maps/search/?api=1&query=Silver+Q+Billiards+225+W+University+Ave+Gainesville+FL+32601",
|
||||
},
|
||||
entry: "Free, 18+",
|
||||
|
||||
// Host
|
||||
|
|
@ -45,6 +59,10 @@ module.exports = {
|
|||
"https://nyc3.digitaloceanspaces.com/corben-sites/images/c23ea202-5442-41b4-9a1b-1586e81c50eb/0b5cd98c-b31a-4d09-980f-d31da5873a53/break-shot.jpg",
|
||||
napkin:
|
||||
"https://nyc3.digitaloceanspaces.com/corben-sites/images/c23ea202-5442-41b4-9a1b-1586e81c50eb/eb54a5f7-a30e-4a7a-b3cb-0beb5eff43ff/napkin.jpg",
|
||||
venue:
|
||||
"https://nyc3.digitaloceanspaces.com/corben-sites/images/c23ea202-5442-41b4-9a1b-1586e81c50eb/458dab49-e942-4016-a744-127da6f9a754/silverq-venue.jpg",
|
||||
venueLogo:
|
||||
"https://nyc3.digitaloceanspaces.com/corben-sites/images/c23ea202-5442-41b4-9a1b-1586e81c50eb/5adbc5b1-ed83-48ae-b067-8ca01d0fde44/silverq-logo.png",
|
||||
},
|
||||
|
||||
expect: [
|
||||
|
|
|
|||
|
|
@ -2,9 +2,12 @@
|
|||
/* ---- 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. */
|
||||
correct across DST without needing a timezone library. public/js/main.js
|
||||
runs the same rule so the live countdown agrees with this render and can
|
||||
roll itself over without a reload. */
|
||||
const TZ = site.timezone || "America/New_York";
|
||||
const now = new Date();
|
||||
const V = site.venue;
|
||||
|
||||
const todayISO = new Intl.DateTimeFormat("en-CA", {
|
||||
timeZone: TZ, year: "numeric", month: "2-digit", day: "2-digit",
|
||||
|
|
@ -24,10 +27,10 @@
|
|||
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.
|
||||
const cancelled = site.cancelled || [];
|
||||
let skippedWeek = null;
|
||||
while (cancelled.includes(isoOf(next))) {
|
||||
if (!skippedWeek) skippedWeek = isoOf(next);
|
||||
next.setUTCDate(next.getUTCDate() + 7);
|
||||
|
|
@ -40,14 +43,23 @@
|
|||
const nextShort = fmtDate({ weekday: "short", month: "short", day: "numeric" });
|
||||
const weekdayName = new Intl.DateTimeFormat("en-US", { timeZone: "UTC", weekday: "long" }).format(next);
|
||||
|
||||
// Eastern UTC offset on the event date, so the countdown target is a real instant.
|
||||
const offLabel = new Intl.DateTimeFormat("en-US", { timeZone: TZ, timeZoneName: "shortOffset" })
|
||||
.format(new Date(`${nextISO}T12:00:00Z`));
|
||||
const offHours = Number((offLabel.match(/GMT([+-]?\d{1,2})/) || [, "-5"])[1]);
|
||||
const pad = (n) => String(n).padStart(2, "0");
|
||||
const offStr = `${offHours < 0 ? "-" : "+"}${pad(Math.abs(offHours))}:00`;
|
||||
const startISO = `${nextISO}T${pad(site.startHour24)}:00:00${offStr}`;
|
||||
const endISO = `${nextISO}T${pad(site.endHour24)}:00:00${offStr}`;
|
||||
|
||||
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}.`;
|
||||
? `Tonight at ${V.name}, ${site.startTime} to ${site.endTime}. Grab a cue and meet the people building things in ${V.city}.`
|
||||
: `Every ${weekdayName} at ${V.name}, ${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: "Where", value: `${V.name}, ${V.street}`, href: V.mapUrl },
|
||||
{ label: "Entry", value: site.entry },
|
||||
];
|
||||
%><!DOCTYPE html>
|
||||
|
|
@ -67,6 +79,7 @@
|
|||
<meta name="twitter:card" content="summary_large_image">
|
||||
|
||||
<link rel="icon" href="/images/favicon.svg" type="image/svg+xml">
|
||||
<link rel="apple-touch-icon" href="/images/favicon.svg">
|
||||
<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">
|
||||
|
|
@ -83,12 +96,12 @@
|
|||
"@type": "Event",
|
||||
name: site.name,
|
||||
description: site.description,
|
||||
startDate: `${nextISO}T19:00:00-04:00`,
|
||||
endDate: `${nextISO}T21:00:00-04:00`,
|
||||
startDate: startISO,
|
||||
endDate: endISO,
|
||||
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" } },
|
||||
location: { "@type": "Place", name: V.fullName, address: { "@type": "PostalAddress", streetAddress: V.street, addressLocality: V.city, addressRegion: V.state, postalCode: V.zip, addressCountry: "US" } },
|
||||
organizer: { "@type": "Organization", name: site.host.name, url: site.host.url },
|
||||
isAccessibleForFree: true,
|
||||
image: [site.images.hero],
|
||||
|
|
@ -108,6 +121,7 @@
|
|||
<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-link" href="#venue">The venue</a>
|
||||
<a class="nav-cta" href="#rsvp">RSVP</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
|
@ -131,6 +145,31 @@
|
|||
</figure>
|
||||
</section>
|
||||
|
||||
<!-- Live countdown. Server-rendered values are the no-JS fallback; main.js
|
||||
takes over, ticks, and recomputes the following Thursday on its own. -->
|
||||
<section class="countdown-band">
|
||||
<div class="countdown"
|
||||
id="countdown"
|
||||
data-start="<%= startISO %>"
|
||||
data-end="<%= endISO %>"
|
||||
data-weekday="<%= site.weekday %>"
|
||||
data-start-hour="<%= site.startHour24 %>"
|
||||
data-end-hour="<%= site.endHour24 %>"
|
||||
data-first="<%= site.firstEvent %>"
|
||||
data-cancelled="<%= (site.cancelled || []).join(',') %>">
|
||||
<p class="countdown-label" id="countdown-label">
|
||||
<%= isTonight ? "The Break is tonight" : "Until the next Break" %>
|
||||
</p>
|
||||
<div class="countdown-clock" id="countdown-clock" role="timer" aria-live="off">
|
||||
<div class="unit"><span class="num" data-unit="days">-</span><span class="cap">days</span></div>
|
||||
<div class="unit"><span class="num" data-unit="hours">-</span><span class="cap">hours</span></div>
|
||||
<div class="unit"><span class="num" data-unit="minutes">-</span><span class="cap">min</span></div>
|
||||
<div class="unit"><span class="num" data-unit="seconds">-</span><span class="cap">sec</span></div>
|
||||
</div>
|
||||
<p class="countdown-when" id="countdown-when"><%= nextLong %>, <%= site.startTime %> at <%= V.name %></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Details: display-value strip, vertical hairlines only -->
|
||||
<section class="details" id="details">
|
||||
<% if (skippedWeek) { %>
|
||||
|
|
@ -176,6 +215,28 @@
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Venue -->
|
||||
<section class="venue" id="venue">
|
||||
<div class="venue-copy reveal">
|
||||
<img class="venue-logo" src="<%= site.images.venueLogo %>" width="395" height="736"
|
||||
alt="<%= V.fullName %>" loading="lazy">
|
||||
<h2>We play at <%= V.name %>.</h2>
|
||||
<p><%= V.note %></p>
|
||||
<address>
|
||||
<a href="<%= V.mapUrl %>" target="_blank" rel="noopener"><%= V.street %><br><%= V.city %>, <%= V.state %> <%= V.zip %></a>
|
||||
</address>
|
||||
<p class="venue-meta">
|
||||
<a href="<%= V.instagramUrl %>" target="_blank" rel="noopener">@<%= V.instagram %></a>
|
||||
<span>Pouring since <%= V.established %></span>
|
||||
</p>
|
||||
</div>
|
||||
<figure class="venue-figure reveal">
|
||||
<img src="<%= site.images.venue %>" width="1400" height="991" loading="lazy"
|
||||
alt="The Silver Q storefront on West University Avenue at dusk.">
|
||||
<figcaption>Look for the sign on West University Avenue.</figcaption>
|
||||
</figure>
|
||||
</section>
|
||||
|
||||
<!-- RSVP -->
|
||||
<section class="rsvp" id="rsvp">
|
||||
<div class="rsvp-panel reveal">
|
||||
|
|
@ -213,7 +274,7 @@
|
|||
|
||||
<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>
|
||||
<p>Check your inbox for a confirmation. See you <%= isTonight ? "tonight" : nextLong %> at <%= V.name %>, <%= V.street %>, <%= 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>
|
||||
|
|
@ -229,7 +290,7 @@
|
|||
<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>
|
||||
<a href="<%= V.mapUrl %>" target="_blank" rel="noopener"><%= V.name %>, <%= V.street %></a>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue