Skip to content
Merged

Dev #477

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions firebase.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"trailingSlash": true,
"headers": [
{
"source": "**/*.@(js|css|woff|woff2|ttf|eot|otf)",
"source": "/_next/static/**",
"headers": [
{
"key": "Cache-Control",
Expand All @@ -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",
Expand All @@ -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",
Expand Down
65 changes: 60 additions & 5 deletions sites/hacklytics2027/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,65 @@
--rule: #2a312d;

--bloom: #ff4f8b;
--bloom-hover: #ff6a9d;
--bloom-deep: #b8285a;
--on-bloom: #16060c;
--line-border: #39413c;

/* Neon halo on blooming flowers. */
--glow-1: #ff4f8b;
--glow-2: #ff4f8b;
--glow-3: rgba(255, 79, 139, 0.45);

--navbar-height: 72px;
color-scheme: dark;
}

/* Day: the same greenhouse with the lights on. A cool sage ground (not
cream), near-black ink, and a deeper pink, since the night pink is too
light to read as text on a pale ground. Used when the visitor picks light,
or has no choice saved and their system is light. */
:root[data-theme="light"] {
--ground: #eef1ec;
--raised: #f7f8f5;
--ink: #121613;
--ink-2: #444c46;
--ink-3: #5f6761; /* meta: ≥4.5:1 on ground */
--rule: #d3d9d3;

--bloom: #c2185b;
--bloom-hover: #a8134d;
--bloom-deep: #8e0f41;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Pressed button text fades

In light mode, pressing a btn-bloom button puts near-black text on a dark pink background. The “Notify me” label becomes hard to read while pressed. Keep the pressed text light in this theme.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

--on-bloom: #ffffff;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Light theme active button contrast

Low Severity

Light theme sets --ink to near-black while .btn-bloom:active still paints that ink on --bloom-deep. Pressing Notify me (and other bloom buttons) briefly shows dark text on dark pink. --on-bloom is already white in this theme and is unused here.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6b96cc9. Configure here.

--line-border: #bfc7c0;

--glow-1: rgba(194, 24, 91, 0.55);
--glow-2: rgba(194, 24, 91, 0.35);
--glow-3: rgba(194, 24, 91, 0.18);

color-scheme: light;
}
@media (prefers-color-scheme: light) {
:root:not([data-theme="dark"]) {
--ground: #eef1ec;
--raised: #f7f8f5;
--ink: #121613;
--ink-2: #444c46;
--ink-3: #5f6761;
--rule: #d3d9d3;

--bloom: #c2185b;
--bloom-hover: #a8134d;
--bloom-deep: #8e0f41;
--on-bloom: #ffffff;
--line-border: #bfc7c0;

--glow-1: rgba(194, 24, 91, 0.55);
--glow-2: rgba(194, 24, 91, 0.35);
--glow-3: rgba(194, 24, 91, 0.18);

color-scheme: light;
}
}

/* ─── Base ─── */
Expand Down Expand Up @@ -115,10 +170,10 @@ body {
background: var(--bloom);
color: var(--on-bloom);
}
.btn-bloom:hover { background: #ff6a9d; }
.btn-bloom:hover { background: var(--bloom-hover); }
.btn-bloom:active { background: var(--bloom-deep); color: var(--ink); }
.btn-line {
border: 1px solid #39413c;
border: 1px solid var(--line-border);
color: var(--ink);
}
.btn-line:hover { border-color: var(--ink-3); }
Expand Down Expand Up @@ -174,9 +229,9 @@ body {
will-change: opacity;
transform-origin: 50% calc(var(--top) / 2);
filter:
drop-shadow(0 0 2px #ff4f8b)
drop-shadow(0 0 8px #ff4f8b)
drop-shadow(0 0 22px rgba(255, 79, 139, 0.45));
drop-shadow(0 0 2px var(--glow-1))
drop-shadow(0 0 8px var(--glow-2))
drop-shadow(0 0 22px var(--glow-3));
}

[data-bloom="on"] .flower-stem {
Expand Down
11 changes: 10 additions & 1 deletion sites/hacklytics2027/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,16 @@ const silkscreen = Silkscreen({
});

export const viewport: Viewport = {
themeColor: "#131715",
themeColor: [
{ media: "(prefers-color-scheme: light)", color: "#eef1ec" },
{ media: "(prefers-color-scheme: dark)", color: "#131715" },
Comment on lines +25 to +27

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Browser color ignores theme choice

The new browser colors follow the device setting, but ThemeToggle lets visitors choose the opposite theme. On a mobile browser, the page changes color while the browser’s top bar keeps the device’s color. Update that color when the visitor switches themes.

],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Theme color ignores saved choice

Low Severity

themeColor still follows prefers-color-scheme only. A saved hl-theme can paint the page #eef1ec or #131715 while the browser chrome stays on the other ground, so the status bar no longer matches the page.

Additional Locations (1)
Fix in Cursor Fix in Web

Triggered by learned rule: Hacklytics greenhouse-at-night look; PixelBed not PixelGarden

Reviewed by Cursor Bugbot for commit 6b96cc9. Configure here.

};

// Runs before first paint so a saved light/dark choice never flashes the
// other theme. Kept tiny and dependency-free; the key matches ThemeToggle.
const themeScript = `try{var t=localStorage.getItem("hl-theme");if(t==="light"||t==="dark")document.documentElement.dataset.theme=t}catch(e){}`;

export const metadata: Metadata = {
title: "Hacklytics 2027: Digital Bloom · Data Science Hackathon at Georgia Tech",
description:
Expand Down Expand Up @@ -109,8 +116,10 @@ export default function RootLayout({ children }: { children: React.ReactNode })
<html
lang="en"
className={`${instrumentSans.variable} ${silkscreen.variable}`}
suppressHydrationWarning
>
<head>
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
Expand Down
64 changes: 36 additions & 28 deletions sites/hacklytics2027/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import React, { useState, useEffect, useRef } from "react";
import Link from "next/link";
import { INTEREST_URL } from "@/lib/links";
import ThemeToggle from "./ThemeToggle";

const navItems = [
{ name: "About", href: "/#about" },
Expand Down Expand Up @@ -66,9 +67,13 @@ export default function Navbar() {
return () => document.body.classList.remove("overflow-hidden");
}, [open]);

const handleNavClick = (e: React.MouseEvent<HTMLAnchorElement>, href: string) => {
const handleNavClick = (
e: React.MouseEvent<HTMLAnchorElement>,
href: string,
) => {
// Cmd/ctrl-click opens a new tab; that is the browser's to handle.
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0) return;
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0)
return;
setOpen(false);
const id = href.replace("/#", "");
const el = document.getElementById(id);
Expand Down Expand Up @@ -128,33 +133,36 @@ export default function Navbar() {
))}
</nav>

<a
href={INTEREST_URL}
target="_blank"
rel="noopener noreferrer"
aria-label="Notify me (opens a sign-up form)"
className="btn btn-bloom hidden lg:inline-flex px-4 py-2.5 text-sm shrink-0"
>
Notify me
</a>
<div className="flex items-center gap-3 shrink-0">
<ThemeToggle />
<a
href={INTEREST_URL}
target="_blank"
rel="noopener noreferrer"
aria-label="Notify me (opens a sign-up form)"
className="btn btn-bloom hidden lg:inline-flex px-4 py-2.5 text-sm shrink-0"
>
Notify me
</a>

<button
aria-label="Toggle menu"
aria-expanded={open}
aria-controls="mobile-menu"
onClick={() => setOpen((s) => !s)}
className="lg:hidden w-11 h-11 rounded-full flex flex-col justify-center items-center gap-[6px] border border-rule hover:border-ink-3 transition-colors bg-transparent"
>
<span
className={`block w-4 h-[1px] bg-ink transition-all duration-300 ${open ? "rotate-45 translate-y-[7px]" : ""}`}
/>
<span
className={`block w-4 h-[1px] bg-ink transition-all duration-300 ${open ? "opacity-0 scale-x-0" : ""}`}
/>
<span
className={`block w-4 h-[1px] bg-ink transition-all duration-300 ${open ? "-rotate-45 -translate-y-[7px]" : ""}`}
/>
</button>
<button
aria-label="Toggle menu"
aria-expanded={open}
aria-controls="mobile-menu"
onClick={() => setOpen((s) => !s)}
className="lg:hidden w-11 h-11 rounded-full flex flex-col justify-center items-center gap-[6px] border border-rule hover:border-ink-3 transition-colors bg-transparent"
>
<span
className={`block w-4 h-[1px] bg-ink transition-all duration-300 ${open ? "rotate-45 translate-y-[7px]" : ""}`}
/>
<span
className={`block w-4 h-[1px] bg-ink transition-all duration-300 ${open ? "opacity-0 scale-x-0" : ""}`}
/>
<span
className={`block w-4 h-[1px] bg-ink transition-all duration-300 ${open ? "-rotate-45 -translate-y-[7px]" : ""}`}
/>
</button>
</div>
</div>
</header>

Expand Down
107 changes: 107 additions & 0 deletions sites/hacklytics2027/components/ThemeToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"use client";

import { useSyncExternalStore } from "react";

/** localStorage key; also read by the pre-paint script in app/layout.tsx. */
export const THEME_KEY = "hl-theme";

type Theme = "light" | "dark";

const subscribe = (onChange: () => void) => {
const media = window.matchMedia("(prefers-color-scheme: light)");
const observer = new MutationObserver(onChange);
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["data-theme"],
});
media.addEventListener("change", onChange);
return () => {
observer.disconnect();
media.removeEventListener("change", onChange);
Comment on lines +10 to +20

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Open tabs keep old theme

ThemeToggle watches the device setting and this tab’s data-theme, but not choices saved by another tab. If someone switches themes in one tab, another open tab keeps its old theme until it reloads. Listen for changes to the saved choice and apply them to open tabs.

};
};

// An explicit choice wins; otherwise the system setting decides, which is
// what globals.css does with no data-theme attribute.
const current = (): Theme => {
const set = document.documentElement.dataset.theme;
if (set === "light" || set === "dark") return set;
return window.matchMedia("(prefers-color-scheme: light)").matches
? "light"
: "dark";
};

/**
* Night / day switch. The greenhouse at night stays the default look for
* anyone whose system is dark; this lets either side pick the other.
*/
export default function ThemeToggle({
className = "",
}: {
className?: string;
}) {
// Server render has no theme; the button is drawn as "dark" until hydrated,
// which only affects the icon, never the page colours (set pre-paint).
const theme = useSyncExternalStore<Theme>(subscribe, current, () => "dark");
const next: Theme = theme === "dark" ? "light" : "dark";

return (
<button
type="button"
onClick={() => {
document.documentElement.dataset.theme = next;
try {
localStorage.setItem(THEME_KEY, next);
} catch {
// Private mode or blocked storage: the choice lasts this page view.
}
}}
aria-label={
next === "light" ? "Switch to light mode" : "Switch to dark mode"
}
title={next === "light" ? "Light mode" : "Dark mode"}
className={`w-11 h-11 rounded-full flex items-center justify-center border border-rule hover:border-ink-3 text-ink transition-colors bg-transparent shrink-0 ${className}`}
>
{theme === "dark" ? (
// Sun, drawn on the pixel grid like the rest of the site.
<svg
viewBox="0 0 16 16"
width="16"
height="16"
shapeRendering="crispEdges"
aria-hidden="true"
fill="currentColor"
>
<rect x="6" y="5" width="4" height="6" />
<rect x="5" y="6" width="6" height="4" />
<rect x="7" y="1" width="2" height="2" />
<rect x="7" y="13" width="2" height="2" />
<rect x="1" y="7" width="2" height="2" />
<rect x="13" y="7" width="2" height="2" />
<rect x="3" y="3" width="2" height="2" />
<rect x="11" y="3" width="2" height="2" />
<rect x="3" y="11" width="2" height="2" />
<rect x="11" y="11" width="2" height="2" />
</svg>
) : (
// Moon.
<svg
viewBox="0 0 16 16"
width="16"
height="16"
shapeRendering="crispEdges"
aria-hidden="true"
fill="currentColor"
>
<rect x="6" y="2" width="5" height="2" />
<rect x="4" y="4" width="4" height="2" />
<rect x="3" y="6" width="4" height="4" />
<rect x="4" y="10" width="4" height="2" />
<rect x="6" y="12" width="5" height="2" />
<rect x="10" y="10" width="3" height="2" />
<rect x="11" y="4" width="2" height="2" />
</svg>
)}
</button>
);
}
Loading
Loading