From 62344b79699dd629b00cabf274ea84e619554d39 Mon Sep 17 00:00:00 2001 From: aamoghS Date: Mon, 5 Oct 2026 12:05:10 -0400 Subject: [PATCH 1/4] fix(hacklytics): give /hacklytics the Hacklytics site's look /hacklytics is where people land from hacklytics.io, but it rendered in the portal's newsletter style with the "Query." wordmark. It now wears the event site's look, scoped to the route: - .hl-theme overrides the portal tokens on the page wrapper (dark grey ground, bloom pink accent, Silkscreen headings, Instrument Sans text), so the shared primitives and the interest form follow unchanged - fonts loaded with next/font in the route layout - masthead is the Hacklytics wordmark linking to hacklytics.io - a still flower bed from the event site's sprites, a few in bloom - metadata no longer calls it "premier" --- .../app/(portal)/hacklytics/layout.tsx | 28 ++++- .../mainweb/app/(portal)/hacklytics/page.tsx | 115 ++++++++++++++++-- sites/mainweb/app/(portal)/liquid-glass.css | 45 +++++++ 3 files changed, 175 insertions(+), 13 deletions(-) diff --git a/sites/mainweb/app/(portal)/hacklytics/layout.tsx b/sites/mainweb/app/(portal)/hacklytics/layout.tsx index 749312c5..21032edb 100644 --- a/sites/mainweb/app/(portal)/hacklytics/layout.tsx +++ b/sites/mainweb/app/(portal)/hacklytics/layout.tsx @@ -1,6 +1,22 @@ import type { Metadata } from "next"; +import { Instrument_Sans, Silkscreen } from "next/font/google"; import { db, hackathons, inArray, eq, and, asc } from "@query/db"; +// The Hacklytics site's own faces (sites/hacklytics2027): Silkscreen for +// headings, Instrument Sans for text. Scoped to this route by .hl-theme. +const silkscreen = Silkscreen({ + subsets: ["latin"], + weight: ["400", "700"], + variable: "--font-hl-pixel", + display: "swap", +}); + +const instrumentSans = Instrument_Sans({ + subsets: ["latin"], + variable: "--font-hl-sans", + display: "swap", +}); + const SITE = "https://datasciencegt.org"; const URL = `${SITE}/hacklytics`; @@ -65,7 +81,7 @@ export async function generateMetadata(): Promise { // The root layout's template appends the organisation. title: "Hacklytics", description: - "Georgia Tech's premier data science hackathon, run by Data Science @ Georgia Tech.", + "Georgia Tech's data science hackathon, run by Data Science @ Georgia Tech.", alternates: { canonical: URL }, }; } @@ -73,7 +89,7 @@ export async function generateMetadata(): Promise { const dates = formatRange(edition.startDate, edition.endDate); const description = edition.description ?? - "Georgia Tech's premier data science hackathon, open to students nationwide."; + "Georgia Tech's data science hackathon, open to students nationwide."; const title = `${edition.name} | ${dates}`; return { @@ -141,7 +157,7 @@ async function EventJsonLd() { eventAttendanceMode: "https://schema.org/OfflineEventAttendanceMode", description: edition.description ?? - "Georgia Tech's premier data science hackathon, open to students nationwide.", + "Georgia Tech's data science hackathon, open to students nationwide.", url: URL, image: [`${SITE}/logo512.png`], organizer: { @@ -201,9 +217,11 @@ export default function HacklyticsLayout({ children: React.ReactNode; }) { return ( - <> +
{children} - +
); } diff --git a/sites/mainweb/app/(portal)/hacklytics/page.tsx b/sites/mainweb/app/(portal)/hacklytics/page.tsx index 535bb534..0949fcfb 100644 --- a/sites/mainweb/app/(portal)/hacklytics/page.tsx +++ b/sites/mainweb/app/(portal)/hacklytics/page.tsx @@ -68,19 +68,115 @@ const formatDeadline = (deadline: Date) => }); /** - * The sidebar is suppressed here, so the page carries its own masthead line: - * the club's wordmark, linking home. + * The sidebar is suppressed here, so the page carries its own masthead: the + * Hacklytics wordmark (linking to the event site) and who runs it. */ function Masthead() { return (
- - Query. - - Data Science @ GT + Hacklytics + + by Data Science @ GT +
+ ); +} + +/** + * The flower bed from hacklytics.io, still: mostly dormant grey plants with + * a few in bloom. Pixel maps and colours match sites/hacklytics2027/ + * components/pixel/sprites.ts. Decorative, so hidden from assistive tech. + */ +const DAISY = [ + "....ppp....", + "...pPPPp...", + "..pPcccPp..", + "..pPcYcPp..", + "..pPcccPp..", + "...pPPPp...", + "....ppp....", + ".....g.....", + ".....g.....", + "..GG.g.....", + ".G..Gg.....", + ".....g.GG..", + ".....gG..G.", + ".....g.....", + ".....g.....", + "....ggg....", +]; +const TULIP = [ + "..p.p.p..", + ".pPpPpPp.", + ".pPPcPPp.", + ".pPPcPPp.", + "..pPcPp..", + "...ppp...", + "....g....", + "....g....", + "..GGg....", + ".G..g....", + "....gGG..", + "....g..G.", + "....g....", + "....g....", + "...ggg...", +]; +const STEM = { g: "#2f6b3a", G: "#4c9a55" }; +const BLOOM = { ...STEM, p: "#b8285a", P: "#ff4f8b", c: "#ffc2d6", Y: "#ffe9f0" }; +const DORMANT = { ...STEM, p: "#3a403c", P: "#5a625c", c: "#8a938c", Y: "#c9cfc9" }; + +function spriteUri(map: string[], colors: Record) { + let rects = ""; + map.forEach((row, y) => + [...row].forEach((ch, x) => { + if (colors[ch]) { + rects += ``; + } + }), + ); + const w = Math.max(...map.map((r) => r.length)); + return `data:image/svg+xml,${encodeURIComponent( + `${rects}`, + )}`; +} + +const BED = Array.from({ length: 14 }, (_, i) => ({ + map: i % 3 === 1 ? TULIP : DAISY, + bloom: i % 4 === 2, + scale: i % 2 ? 4 : 5, +})); + +function FlowerBed() { + return ( + ); } @@ -122,6 +218,7 @@ export default function HacklyticsPage() { The next Hacklytics has not been announced. Follow Data Science @ GT and it will show up here first.

+ ); } @@ -226,6 +323,8 @@ export default function HacklyticsPage() { More about {event.name} ) : null} + + ); } diff --git a/sites/mainweb/app/(portal)/liquid-glass.css b/sites/mainweb/app/(portal)/liquid-glass.css index 113e249f..b467607e 100644 --- a/sites/mainweb/app/(portal)/liquid-glass.css +++ b/sites/mainweb/app/(portal)/liquid-glass.css @@ -403,3 +403,48 @@ a { .card-printed { position: relative; } + +/* ===== HACKLYTICS (the public /hacklytics page) ===== + The page people reach from hacklytics.io, so it wears that site's look, + not the portal's: dark grey ground, one bloom pink, pixel headings. The + tokens are overridden on the wrapper, so the portal primitives and the + interest form inside follow without their own variants. Same values as + sites/hacklytics2027/app/globals.css. */ +.hl-theme { + --bg-primary: #131715; + --bg-secondary: #1a1f1d; + --bg-tertiary: #131715; + --bg-card: #1a1f1d; + --bg-elevated: #1a1f1d; + --bg-input: #1a1f1d; + --text-primary: #edefea; + --text-secondary: #c3c9c4; + --text-muted: #a9b0aa; + --text-subtle: #868e88; + --text-on-accent: #16060c; + --border-subtle: #2a312d; + --border-medium: #39413c; + --border-hover: #5a625c; + --border-accent: #ff4f8b; + --accent: #ff4f8b; + --accent-primary: #ff4f8b; + --accent-secondary: #ff6a9d; + --accent-hover: #ff6a9d; + --accent-dim: rgba(255, 79, 139, 0.12); + --font-display: var(--font-hl-pixel), ui-monospace, monospace; + --font-ui: var(--font-hl-sans), ui-sans-serif, system-ui, sans-serif; + color-scheme: dark; + min-height: 100vh; + background: var(--bg-primary); + color: var(--text-primary); + font-family: var(--font-ui); +} + +/* Pixel glyphs carry their own spacing; the serif's negative tracking would + run them together. */ +.hl-theme h1, +.hl-theme h2, +.hl-theme h3 { + letter-spacing: 0; + font-weight: 700; +} From 70b8fac7842daa99d07d9120e6a6f2248e5f8ae7 Mon Sep 17 00:00:00 2001 From: aamoghS Date: Mon, 5 Oct 2026 12:59:29 -0400 Subject: [PATCH 2/4] fix(hacklytics): kill the service worker and stale caches Visitors from before the 2027 redesign kept getting the old site: the service worker served pages stale-while-revalidate from a cache that outlived deploys, and sw.js itself matched the **/*.js rule and was served "immutable" for a year. - public/sw.js is now a kill switch: browsers with the old worker pick it up on their next update check, delete every cache, unregister and reload open tabs from the network. Keep it deployed for a season - the site no longer registers a service worker (ServiceWorkerRegistrar removed) - firebase.json (hacklytics): only fingerprinted /_next/static/** is immutable; unhashed public fonts join images at 1 day + SWR; HTML is max-age=0, must-revalidate; /sw.js now only matches its no-store rule --- firebase.json | 6 +- sites/hacklytics2027/app/layout.tsx | 2 - .../components/ServiceWorkerRegistrar.tsx | 30 --- sites/hacklytics2027/public/sw.js | 211 +++--------------- 4 files changed, 28 insertions(+), 221 deletions(-) delete mode 100644 sites/hacklytics2027/components/ServiceWorkerRegistrar.tsx diff --git a/firebase.json b/firebase.json index 2f16842b..ab4d69df 100644 --- a/firebase.json +++ b/firebase.json @@ -13,7 +13,7 @@ "trailingSlash": true, "headers": [ { - "source": "**/*.@(js|css|woff|woff2|ttf|eot|otf)", + "source": "/_next/static/**", "headers": [ { "key": "Cache-Control", @@ -26,7 +26,7 @@ ] }, { - "source": "**/*.@(jpg|jpeg|png|gif|webp|avif|ico|svg)", + "source": "**/*.@(jpg|jpeg|png|gif|webp|avif|ico|svg|woff|woff2|ttf|eot|otf)", "headers": [ { "key": "Cache-Control", @@ -43,7 +43,7 @@ "headers": [ { "key": "Cache-Control", - "value": "public, max-age=3600, s-maxage=86400" + "value": "public, max-age=0, must-revalidate" }, { "key": "X-Content-Type-Options", diff --git a/sites/hacklytics2027/app/layout.tsx b/sites/hacklytics2027/app/layout.tsx index a1bbce16..8651d301 100644 --- a/sites/hacklytics2027/app/layout.tsx +++ b/sites/hacklytics2027/app/layout.tsx @@ -2,7 +2,6 @@ import "./globals.css"; import type { Metadata, Viewport } from "next"; import { Instrument_Sans, Silkscreen } from "next/font/google"; import Navbar from "../components/Navbar"; -import ServiceWorkerRegistrar from "../components/ServiceWorkerRegistrar"; import Footer from "../components/Footer"; import { INTEREST_URL } from "../lib/links"; @@ -123,7 +122,6 @@ export default function RootLayout({ children }: { children: React.ReactNode }) {children}