diff --git a/Dockerfile b/Dockerfile
index 867bfd4..9fd9344 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -111,9 +111,11 @@ RUN chown node:node .next
COPY --from=builder --chown=node:node /app/.next/standalone ./
COPY --from=builder --chown=node:node /app/.next/static ./.next/static
-# If you want to persist the fetch cache generated during the build so that
-# cached responses are available immediately on startup, uncomment this line:
-# COPY --from=builder --chown=node:node /app/.next/cache ./.next/cache
+# Persist the build-time cache so cached fetch responses are available
+# immediately on startup. Note this does NOT pre-warm the image optimizer:
+# /_next/image results are produced on demand, so the first request for each
+# (image, width) still pays the sharp encode on every task.
+COPY --from=builder --chown=node:node /app/.next/cache ./.next/cache
# Switch to non-root user for security best practices
USER node
diff --git a/app/(marketing)/how-to-mcp/HowToMcp.tsx b/app/(marketing)/how-to-mcp/HowToMcp.tsx
new file mode 100644
index 0000000..60b247e
--- /dev/null
+++ b/app/(marketing)/how-to-mcp/HowToMcp.tsx
@@ -0,0 +1,368 @@
+"use client";
+
+import { useState } from "react";
+import { motion, useReducedMotion } from "framer-motion";
+import { AgentTerminal } from "@/components/landing/AgentTerminal";
+import { PillTabGroup } from "@/components/landing/PillTabGroup";
+import { CopyChip, CommandBlock } from "@/components/landing/CopyBlock";
+import Image from "next/image";
+import { DOT_PAPER_BG, GRAIN_240 } from "@/lib/landing/textures";
+import {
+ AUTH_NOTES,
+ CLIENT_GUIDES,
+ CLIENT_IDS,
+ INTRO,
+ MACHINE_MD,
+ PROMPTS,
+ SERVER_URL,
+ type AuthNotePart,
+ type ClientBlock,
+ type ClientId,
+ type RichSegment,
+} from "./content";
+
+const HAIRLINE = "rgba(58,74,38,0.14)";
+
+function Rise({
+ children,
+ delay = 0,
+ className,
+}: {
+ children: React.ReactNode;
+ delay?: number;
+ className?: string;
+}) {
+ const reduced = useReducedMotion();
+ return (
+
+ {children}
+
+ );
+}
+
+function SectionTitle({ title }: { title: string }) {
+ return (
+
+
+ {title}
+
+
+ );
+}
+
+function Step({ n, children }: { n: number; children: React.ReactNode }) {
+ return (
+
+ Server URL: use it exactly as written, in any client below
+
+ {SERVER_URL}
+
+
+
+
+
+
+
+
+ {PROMPTS.map((p) => (
+
+
+
+ “{p.quote}”
+
+
+ {p.detail}
+
+
+
+ ))}
+
+
+
+
+
+
+ {AUTH_NOTES.map((note) => (
+
+
+
+ ✳
+
+
+
+ {note.lead}
+ {" "}
+
+
+
+
+ ))}
+
+
+
+ );
+}
+
+function MachineMode() {
+ return (
+
+
+ Copy this markdown and paste it into your agent (Claude Code, Codex, or
+ any MCP-capable client). It has everything your agent needs to connect
+ and apply; from there, just follow your agent’s instructions.
+
+
+
+ how-to-mcp.md
+
+
+
+
+ {MACHINE_MD}
+
+
+ );
+}
+
+/**
+ * Paxel-style document page: the garden photo runs full-bleed behind a
+ * centered paper sheet. `human` renders the typeset guide with copyable
+ * commands; `machine` renders the same content as one plain markdown file.
+ */
+export function HowToMcp() {
+ const [mode, setMode] = useState("human");
+
+ return (
+
+ {/* Backdrop: aerial terraces behind the same frosted veil as the hero
+ (blur values match HeroReveal's, painted on an oversized plate so
+ the blur never samples past the edges). Fixed so the paper scrolls
+ over it — z-0 (not negative) so it paints above the shell's cream
+ parchment background. */}
+
+
+
+
+
+
+
+
+
+ {/* The paper */}
+
+ {/* Same dotted tooth as the FAQ sheet */}
+
+
+
+ {/* Mode toggle — sits at the top of the paper and scrolls away
+ with it */}
+
+
+
+
+ {mode === "human" ? : }
+
+
+
+
+ );
+}
diff --git a/app/(marketing)/how-to-mcp/content.ts b/app/(marketing)/how-to-mcp/content.ts
new file mode 100644
index 0000000..98394ab
--- /dev/null
+++ b/app/(marketing)/how-to-mcp/content.ts
@@ -0,0 +1,266 @@
+/**
+ * Single source for the How to MCP page copy. Human-mode JSX in HowToMcp.tsx
+ * renders from these exports; MACHINE_MD is generated from the same data.
+ */
+
+export const SERVER_URL = "https://www.mhacks.org/mcp";
+
+export const INTRO =
+ "MHacks has an MCP server that lets you apply through Claude, Codex, or any other MCP-capable agent instead of filling out the web form by hand. Your agent can read the application schema, save a draft, ask you questions, upload your resume, and submit, all tied to your real, logged-in MHacks account.";
+
+export const PROMPTS = [
+ {
+ quote: "Who am I connected as?",
+ detail:
+ "Confirms the MHacks account your agent is authenticated as, straight from your login, before you do anything else.",
+ },
+ {
+ quote: "Check my MHacks application status",
+ detail:
+ "See whether you've already applied, and if so, its current status.",
+ },
+ {
+ quote: "Help me fill out my MHacks application",
+ detail:
+ "Your agent can walk you through each field, save your progress as a draft, and come back to it later.",
+ },
+ {
+ quote: "Submit my MHacks application",
+ detail: "Once everything's filled in, your agent submits it for you.",
+ },
+] as const;
+
+export type AuthNotePart = string | { text: string; href: string };
+
+export interface AuthNote {
+ lead: string;
+ parts: AuthNotePart[];
+}
+
+export const AUTH_NOTES: AuthNote[] = [
+ {
+ lead: "Your identity comes from your login, not from anything you tell the agent.",
+ parts: [
+ "Whatever email you authenticate with is the account the application is tied to. An agent can't submit on someone else's behalf.",
+ ],
+ },
+ {
+ lead: "Submission is final.",
+ parts: [
+ "There's currently no MCP tool to edit or withdraw a submitted application, so review it with your agent before confirming.",
+ ],
+ },
+ {
+ lead: "You'll be asked to explicitly agree",
+ parts: [
+ "to the MLH Code of Conduct, Privacy Policy, and communications terms before submission. Your agent should read these to you and ask for a clear yes/no, not assume.",
+ ],
+ },
+ {
+ lead: "Resume upload usually won't happen through the agent.",
+ parts: [
+ "Uploading requires the agent to make its own HTTP request with the file's raw bytes. Attaching a PDF to the chat only lets the agent read it. Coding-agent clients with their own network access (Claude Code, Codex, Cursor) can do this; standard Claude.ai / Claude Desktop chat can't, so expect your agent to tell you to upload your resume yourself at ",
+ { text: "mhacks.org/apply", href: "https://www.mhacks.org/apply" },
+ ", then it'll confirm it landed before continuing.",
+ ],
+ },
+ {
+ lead: "You can revoke access at any time.",
+ parts: [
+ "See and revoke any agent's access at ",
+ {
+ text: "mhacks.org/account/connections",
+ href: "https://www.mhacks.org/account/connections",
+ },
+ ".",
+ ],
+ },
+];
+
+export type ClientId = "claude" | "claude-code" | "codex" | "other";
+
+export type RichSegment = string | { code: string };
+
+export type ClientBlock =
+ | { type: "p"; text: string }
+ | { type: "p-rich"; segments: RichSegment[] }
+ | { type: "steps"; intro?: string; items: string[] }
+ | { type: "cmd"; template: string };
+
+export interface ClientGuide {
+ label: string;
+ mdHeading: string;
+ blocks: ClientBlock[];
+}
+
+export const CLIENT_GUIDES: Record = {
+ claude: {
+ label: "Claude.ai",
+ mdHeading: "Claude.ai / Claude Desktop",
+ blocks: [
+ {
+ type: "p",
+ text: "Works in Claude.ai on the web and in Claude Desktop.",
+ },
+ {
+ type: "steps",
+ items: [
+ "Go to Settings → Connectors → Add custom connector.",
+ "Paste the server URL above.",
+ "Claude will open a login page — sign in with your email (MHacks uses a one-time code sent to your inbox, no password).",
+ "Approve the connection when prompted. You'll see what Claude is requesting access to before you approve.",
+ ],
+ },
+ ],
+ },
+ "claude-code": {
+ label: "Claude Code",
+ mdHeading: "Claude Code",
+ blocks: [
+ {
+ type: "cmd",
+ template: "claude mcp add --transport http mhacks ${SERVER_URL}",
+ },
+ {
+ type: "p-rich",
+ segments: [
+ "Then inside a session, run ",
+ { code: "/mcp" },
+ ", select ",
+ { code: "mhacks" },
+ ", and authenticate — same email login + approval as Claude.ai.",
+ ],
+ },
+ ],
+ },
+ codex: {
+ label: "Codex CLI",
+ mdHeading: "Codex CLI",
+ blocks: [
+ {
+ type: "p",
+ text: "Add the server to ~/.codex/config.toml:",
+ },
+ {
+ type: "cmd",
+ template: '[mcp_servers.mhacks]\nurl = "${SERVER_URL}"',
+ },
+ { type: "p", text: "Then log in and approve access with:" },
+ { type: "cmd", template: "codex mcp login mhacks" },
+ {
+ type: "p",
+ text: "Codex will open a browser window for the same email one-time-code flow.",
+ },
+ ],
+ },
+ other: {
+ label: "Other",
+ mdHeading: "Other clients",
+ blocks: [
+ {
+ type: "p",
+ text: "Any client that supports the MCP Streamable HTTP transport and OAuth 2.1 can connect using the same server URL. You'll go through the same email-login-and-approve flow regardless of client.",
+ },
+ ],
+ },
+};
+
+export const CLIENT_IDS = Object.keys(CLIENT_GUIDES) as ClientId[];
+
+function expandTemplate(template: string): string {
+ return template.replaceAll("${SERVER_URL}", SERVER_URL);
+}
+
+function richSegmentsPlain(segments: RichSegment[]): string {
+ return segments
+ .map((segment) => (typeof segment === "string" ? segment : segment.code))
+ .join("");
+}
+
+function clientBlockMarkdown(block: ClientBlock): string {
+ switch (block.type) {
+ case "p":
+ return block.text;
+ case "p-rich":
+ return richSegmentsPlain(block.segments);
+ case "cmd":
+ return `\n ${expandTemplate(block.template)}\n`;
+ case "steps":
+ return block.items.map((item, i) => `${i + 1}. ${item}`).join("\n");
+ }
+}
+
+function clientGuideMarkdown(guide: ClientGuide): string {
+ const body = guide.blocks
+ .map((block) => {
+ if (block.type === "steps") {
+ return clientBlockMarkdown(block);
+ }
+ if (block.type === "cmd") {
+ return expandTemplate(block.template);
+ }
+ return clientBlockMarkdown(block);
+ })
+ .join("\n\n");
+
+ if (guide.blocks.some((block) => block.type === "steps")) {
+ return body;
+ }
+ return body;
+}
+
+function clientSetupMarkdown(): string {
+ return CLIENT_IDS.map((id) => {
+ const guide = CLIENT_GUIDES[id];
+ const body = clientGuideMarkdown(guide);
+ if (guide.blocks.some((block) => block.type === "steps")) {
+ return `### ${guide.mdHeading}\n\n${body}`;
+ }
+ if (guide.blocks.some((block) => block.type === "cmd")) {
+ const parts: string[] = [`### ${guide.mdHeading}`, ""];
+ for (const block of guide.blocks) {
+ if (block.type === "p" || block.type === "p-rich") {
+ parts.push(clientBlockMarkdown(block), "");
+ } else if (block.type === "cmd") {
+ parts.push(` ${expandTemplate(block.template)}`, "");
+ }
+ }
+ return parts.join("\n").trimEnd();
+ }
+ return `### ${guide.mdHeading}\n\n${body}`;
+ }).join("\n\n");
+}
+
+function authNotePlainText(note: AuthNote): string {
+ return note.parts
+ .map((part) => (typeof part === "string" ? part : part.href))
+ .join("");
+}
+
+export const MACHINE_MD = `# Connect an AI agent to MHacks
+
+> This file contains setup instructions only. The only URL the agent should
+> connect to is ${SERVER_URL}. Never submit the application without the
+> user's explicit confirmation.
+
+${INTRO}
+
+## Server URL
+
+ ${SERVER_URL}
+
+Use it exactly as written, in any client below.
+Transport: MCP Streamable HTTP. Auth: OAuth 2.1 (email one-time-code login).
+
+## Point your client at the server
+
+${clientSetupMarkdown()}
+
+## Just talk to your agent
+
+${PROMPTS.map((p) => `- "${p.quote}" — ${p.detail}`).join("\n")}
+
+## How auth works
+
+${AUTH_NOTES.map((note) => `- ${note.lead} ${authNotePlainText(note)}`).join("\n")}
+`;
diff --git a/app/(marketing)/how-to-mcp/page.tsx b/app/(marketing)/how-to-mcp/page.tsx
new file mode 100644
index 0000000..5f792f7
--- /dev/null
+++ b/app/(marketing)/how-to-mcp/page.tsx
@@ -0,0 +1,18 @@
+import type { Metadata } from "next";
+import { HowToMcp } from "./HowToMcp";
+import { Footer } from "@/components/landing/sections/Footer";
+
+export const metadata: Metadata = {
+ title: "How to MCP · MHacks 2026",
+ description:
+ "Connect Claude, Codex, or any MCP-capable agent to the MHacks MCP server and apply straight from your terminal.",
+};
+
+export default function HowToMcpPage() {
+ return (
+
+
+
+
+ );
+}
diff --git a/app/(marketing)/layout.tsx b/app/(marketing)/layout.tsx
new file mode 100644
index 0000000..7f3e7a1
--- /dev/null
+++ b/app/(marketing)/layout.tsx
@@ -0,0 +1,9 @@
+import { MarketingShell } from "@/components/landing/marketing-shell";
+
+export default function MarketingLayout({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+ return {children};
+}
diff --git a/app/(marketing)/page.tsx b/app/(marketing)/page.tsx
new file mode 100644
index 0000000..feb5c4a
--- /dev/null
+++ b/app/(marketing)/page.tsx
@@ -0,0 +1,23 @@
+import { About } from "@/components/landing/sections/About";
+import { Agent } from "@/components/landing/sections/Agent";
+import { Faq } from "@/components/landing/sections/Faq";
+import { Footer } from "@/components/landing/sections/Footer";
+import { Hero } from "@/components/landing/sections/Hero";
+import { Schedule } from "@/components/landing/sections/Schedule";
+import { Sponsors } from "@/components/landing/sections/Sponsors";
+import { StackedPages } from "@/components/landing/StackedPages";
+
+export default function Home() {
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/app/globals.css b/app/globals.css
index 64ba02b..1cea0b4 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -11,18 +11,67 @@
--font-mono: var(--font-geist-mono);
--font-heading: var(--font-instrument-serif);
--font-red-hat: var(--font-red-hat-display);
+ --font-serif: var(--font-instrument-serif);
+ --font-display: var(--font-red-hat-display);
+ --font-body: var(--font-red-hat-display);
- /* Brand palette */
+ /* Landing moss scale */
+ --color-moss-900: #1d2412;
+ --color-moss-800: #2c3a1c;
+ --color-moss-700: #3a4a26;
+ --color-moss-500: #5d6b3a;
+ --color-moss-300: #bdc59a;
+ --color-parchment: #f5f1de;
+ --color-cream-soft: #f5f1de;
+ --color-bloom: #e07a9a;
+ --color-sun: #e8d35a;
+ --color-leaf: #c9e07a;
+ --color-brand-blue: #3a5ad4;
+ --color-surface: #ffffff;
+ --color-bg: #f5f1de;
+ --color-brand: #3a4a26;
+ --color-brand-on: #efe9d4;
+ --color-border-strong: rgba(29, 36, 18, 0.28);
+
+ --radius-xs: 4px;
+ --radius-pill: 999px;
+
+ --shadow-e-1: 0 1px 2px rgba(20, 30, 10, 0.06);
+ --shadow-e-2: 0 4px 10px rgba(20, 30, 10, 0.08);
+ --shadow-e-3: 0 8px 24px rgba(20, 30, 10, 0.1);
+ --shadow-e-4: 0 16px 40px rgba(20, 30, 10, 0.14);
+ --shadow-e-glass: 0 8px 28px rgba(20, 30, 10, 0.18);
+
+ --text-display-1: clamp(80px, 15vw, 220px);
+ --text-display-1--line-height: 0.92;
+ --text-display-1--letter-spacing: -0.02em;
+ --text-display-2: clamp(56px, 8vw, 112px);
+ --text-display-2--line-height: 0.98;
+ --text-display-2--letter-spacing: -0.02em;
+ --text-display-3: clamp(40px, 5vw, 72px);
+ --text-display-3--line-height: 1.02;
+ --text-display-3--letter-spacing: -0.015em;
+ --text-display-4: clamp(28px, 3vw, 40px);
+ --text-display-4--line-height: 1.1;
+ --text-display-4--letter-spacing: -0.01em;
+
+ --animate-drift: drift 6s ease-in-out infinite;
+ --animate-pulse-soft: pulse_soft 3.4s ease-in-out infinite;
+
+ --ease-soft: cubic-bezier(0.2, 0.7, 0.2, 1);
+ --ease-snap: cubic-bezier(0.2, 0.8, 0.2, 1);
+ --color-night: #0b0d08;
+ --color-fog: #f0efe6;
+ --color-paper: #f4f2e8;
+ --color-haze: #e8e5d6;
+
+ /* Brand palette (legacy + landing) */
--color-moss: #3a4a26;
--color-olive: #445721;
--color-fern: #5d6b3a;
--color-sage: #bec59b;
--color-cream: #efe9d4;
- --color-ink: #1f2a16;
- --color-night: #0b0d08;
- --color-fog: #f0efe6;
- --color-paper: #f4f2e8;
- --color-haze: #e8e5d6;
+ --color-ink: #1d2412;
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
@@ -224,3 +273,266 @@
@apply border border-white/30 bg-paper/88 shadow-[0_24px_64px] shadow-black/40 inset-shadow-[0_1px_0] inset-shadow-white/90 backdrop-blur-xl;
}
}
+
+/* ---------- Landing / marketing site (Digital Garden) ---------- */
+.marketing-site {
+ --primary-blue: #3a5ad4;
+ --landing-secondary: #7a93c9;
+ --secondary-alt: #a8c08a;
+ --accent-green: #c9e07a;
+ --accent-yellow: #e8d35a;
+ --accent-pink: #e07a9a;
+ --moss-900: #1d2412;
+ --moss-800: #2c3a1c;
+ --moss-700: #3a4a26;
+ --moss-500: #5d6b3a;
+ --moss-300: #bdc59a;
+ --parchment: #f5f1de;
+ --landing-ink: var(--moss-900);
+ --landing-bg: var(--parchment);
+ --landing-surface: #ffffff;
+ --landing-muted: #5d6b3a;
+ --landing-brand: var(--moss-700);
+ --landing-brand-on: var(--cream);
+ --border: rgba(29, 36, 18, 0.14);
+ --landing-border: rgba(29, 36, 18, 0.14);
+ --landing-border-strong: rgba(29, 36, 18, 0.28);
+ --pill-glass: rgba(245, 241, 222, 0.78);
+ --glass-border: rgba(255, 255, 255, 0.4);
+ --ease-soft: cubic-bezier(0.2, 0.7, 0.2, 1);
+ --ease-snap: cubic-bezier(0.2, 0.8, 0.2, 1);
+
+ background: var(--landing-bg);
+ color: var(--landing-ink);
+ font-family: var(--font-red-hat-display), system-ui, sans-serif;
+ /* clip — not hidden — so sticky sheets stick to the viewport, not this box */
+ overflow-x: clip;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+.marketing-site a:not([data-slot="button"]) {
+ color: inherit;
+ text-decoration: none;
+}
+
+.marketing-site button {
+ font-family: inherit;
+}
+
+.marketing-site .font-serif-it {
+ font-family: var(--font-instrument-serif), serif;
+ font-style: italic;
+ letter-spacing: -0.01em;
+}
+
+.marketing-site .font-mono {
+ font-family: var(--font-red-hat-mono), ui-monospace, monospace;
+}
+
+.marketing-site .reveal-line {
+ overflow: hidden;
+ display: block;
+ padding: 0 0.14em 0.08em;
+ margin: 0 -0.14em -0.08em;
+}
+
+.marketing-site .type-caret {
+ animation: caret-blink 0.85s steps(1) infinite;
+}
+
+@keyframes caret-blink {
+ 50% {
+ opacity: 0;
+ }
+}
+
+@keyframes drift {
+ 0%,
+ 100% {
+ transform: translateY(0);
+ }
+ 50% {
+ transform: translateY(-8px);
+ }
+}
+
+@keyframes pulse_soft {
+ 0%,
+ 100% {
+ opacity: 0.55;
+ }
+ 50% {
+ opacity: 0.85;
+ }
+}
+
+html.lenis,
+html.lenis body {
+ height: auto;
+}
+
+.lenis.lenis-smooth {
+ scroll-behavior: auto !important;
+}
+
+.lenis.lenis-smooth [data-lenis-prevent] {
+ overscroll-behavior: contain;
+}
+
+.lenis.lenis-stopped {
+ overflow: hidden;
+}
+
+button,
+[role="button"],
+[data-cursor="hover"] {
+ cursor: pointer;
+}
+
+html.has-custom-cursor [data-cursor-zone],
+html.has-custom-cursor [data-cursor-zone] * {
+ cursor: none !important;
+}
+
+html.has-custom-cursor [data-cursor-zone] a,
+html.has-custom-cursor [data-cursor-zone] a *,
+html.has-custom-cursor [data-cursor-zone] button,
+html.has-custom-cursor [data-cursor-zone] button *,
+html.has-custom-cursor [data-cursor-zone] [data-cursor="hover"],
+html.has-custom-cursor [data-cursor-zone] [data-cursor="hover"] *,
+html.has-custom-cursor [data-cursor-zone] [role="button"] {
+ cursor: pointer !important;
+}
+
+html.has-custom-cursor [data-cursor-zone] input,
+html.has-custom-cursor [data-cursor-zone] textarea {
+ cursor: text !important;
+}
+
+html.has-custom-cursor [data-cursor-zone] select {
+ cursor: default !important;
+}
+
+@media (hover: none), (pointer: coarse) {
+ html.has-custom-cursor [data-cursor-zone],
+ html.has-custom-cursor [data-cursor-zone] * {
+ cursor: auto !important;
+ }
+}
+
+.liquid-glass {
+ position: relative;
+ isolation: isolate;
+ border: none;
+ background: linear-gradient(
+ 120deg,
+ rgba(255, 255, 255, 0.14),
+ rgba(255, 255, 255, 0.04) 40%,
+ rgba(255, 255, 255, 0.1)
+ );
+ -webkit-backdrop-filter: blur(6px) saturate(160%) brightness(1.06);
+ backdrop-filter: blur(6px) saturate(160%) brightness(1.06);
+ box-shadow:
+ inset 0 1px 1px rgba(255, 255, 255, 0.65),
+ inset 0 -1px 1px rgba(255, 255, 255, 0.22),
+ inset 2px 4px 9px -4px rgba(255, 255, 255, 0.7),
+ inset -3px -5px 10px -5px rgba(29, 36, 18, 0.28);
+ transition:
+ box-shadow 300ms var(--ease-soft),
+ background 300ms var(--ease-soft);
+}
+
+.liquid-glass.lg-on-light {
+ background: linear-gradient(
+ 120deg,
+ rgba(29, 36, 18, 0.08),
+ rgba(29, 36, 18, 0.02) 40%,
+ rgba(29, 36, 18, 0.06)
+ );
+ box-shadow:
+ inset 0 1px 1px rgba(255, 255, 255, 0.8),
+ inset 0 -1px 1px rgba(29, 36, 18, 0.16),
+ inset 2px 4px 9px -4px rgba(255, 255, 255, 0.9),
+ inset -3px -5px 10px -5px rgba(29, 36, 18, 0.22),
+ 0 0 0 1px rgba(29, 36, 18, 0.14);
+}
+
+html.lg-refract .liquid-glass {
+ -webkit-backdrop-filter: url(#liquid-glass-distortion) blur(1.5px)
+ saturate(165%) brightness(1.06);
+ backdrop-filter: url(#liquid-glass-distortion) blur(1.5px) saturate(165%)
+ brightness(1.06);
+}
+
+.liquid-glass::before {
+ content: "";
+ position: absolute;
+ inset: 1px;
+ border-radius: inherit;
+ pointer-events: none;
+ background: linear-gradient(
+ 173deg,
+ rgba(255, 255, 255, 0.5) 0%,
+ rgba(255, 255, 255, 0.12) 22%,
+ transparent 42%,
+ transparent 78%,
+ rgba(255, 255, 255, 0.14) 100%
+ );
+ opacity: 0.55;
+ mix-blend-mode: screen;
+}
+
+.liquid-glass::after {
+ content: "";
+ position: absolute;
+ inset: 0;
+ border-radius: inherit;
+ pointer-events: none;
+ background:
+ radial-gradient(
+ 55% 130% at -4% 50%,
+ rgba(255, 255, 255, 0.38) 0%,
+ transparent 55%
+ ),
+ radial-gradient(
+ 55% 130% at 104% 50%,
+ rgba(255, 255, 255, 0.28) 0%,
+ transparent 55%
+ );
+ opacity: 0.8;
+}
+
+.liquid-glass.lg-static {
+ -webkit-backdrop-filter: none !important;
+ backdrop-filter: none !important;
+}
+
+.liquid-glass.lg-static::before {
+ mix-blend-mode: normal;
+ opacity: 0.35;
+}
+
+.liquid-glass:hover {
+ background: linear-gradient(
+ 120deg,
+ rgba(255, 255, 255, 0.2),
+ rgba(255, 255, 255, 0.08) 40%,
+ rgba(255, 255, 255, 0.16)
+ );
+ box-shadow:
+ inset 0 1px 1.5px rgba(255, 255, 255, 0.8),
+ inset 0 -1px 1px rgba(255, 255, 255, 0.28),
+ inset 2px 4px 10px -4px rgba(255, 255, 255, 0.8),
+ inset -3px -5px 10px -5px rgba(29, 36, 18, 0.24);
+}
+
+.grain::after {
+ content: "";
+ position: fixed;
+ inset: 0;
+ pointer-events: none;
+ z-index: 1000;
+ opacity: 0.04;
+ background-image: url("data:image/svg+xml;utf8,");
+}
diff --git a/app/how-to-mcp/page.tsx b/app/how-to-mcp/page.tsx
deleted file mode 100644
index 3ec82b8..0000000
--- a/app/how-to-mcp/page.tsx
+++ /dev/null
@@ -1,638 +0,0 @@
-"use client";
-
-// Public docs page: how to connect an AI agent (Claude, Codex, or any
-// custom MCP client) to the MHacks MCP server. Content is adapted from
-// mcp-docs/INSTRUCTIONS.md (kept as the repo-internal reference) with an
-// added Codex section and a developer-facing auth explanation. Linked
-// from the navbar as "MCP".
-//
-// Visual language mirrors the landing page: moss-on-paper, Red Hat
-// throughout (headings included), Geist Mono for everything the machine
-// says, and dotted leaders from the Timeline section. A photo fades in
-// down the side gutters (see GutterPhoto), leaving the center column
-// clear for content. The one dark element is the hero terminal — a
-// staged agent session, since the page's subject is literally an agent
-// talking to this server.
-
-import { useEffect, useState } from "react";
-import { motion, useReducedMotion } from "framer-motion";
-import NavBar from "@/components/navbar";
-import SiteFooter from "@/components/site-footer";
-
-const MOSS = "#3A4A26";
-const MOSS_SOFT = "rgba(58,74,38,0.8)";
-const MOSS_FAINT = "rgba(58,74,38,0.45)";
-const HAIRLINE = "rgba(58,74,38,0.14)";
-const LEADER = "rgba(58,74,38,0.25)";
-const CREAM = "#efe9d4";
-const PINE = "#1c2513"; // terminal panel — the page's single dark surface
-const PARCHMENT = "#ebe4ce"; // terminal foreground, same as hero title
-const SAGE = "#bec59b";
-
-const SERVER_URL = "https://www.mhacks.org/mcp";
-const EASE = [0.25, 0.1, 0.25, 1] as const;
-
-/* ── shared building blocks ─────────────────────────────────────────── */
-
-function Reveal({
- children,
- delay = 0,
- className,
- onLoad = false,
-}: {
- children: React.ReactNode;
- delay?: number;
- className?: string;
- /** Hero pieces animate on mount — the server URL must never wait on a
- scroll trigger. Everything below the fold reveals as it scrolls in. */
- onLoad?: boolean;
-}) {
- const reduced = useReducedMotion();
- return (
-
- {children}
-
- );
-}
-
-function SectionHeading({ title }: { title: string }) {
- return (
-
-
- {title}
-
-
- );
-}
-
-function CopyButton({
- text,
- emphasized = false,
-}: {
- text: string;
- emphasized?: boolean;
-}) {
- const [copied, setCopied] = useState(false);
- useEffect(() => {
- if (!copied) return;
- const id = setTimeout(() => setCopied(false), 1800);
- return () => clearTimeout(id);
- }, [copied]);
- const style = copied
- ? emphasized
- ? { backgroundColor: CREAM, borderColor: CREAM, color: MOSS }
- : { backgroundColor: MOSS, borderColor: MOSS, color: CREAM }
- : emphasized
- ? { borderColor: "rgba(239,233,212,0.5)", color: CREAM }
- : { borderColor: LEADER, color: MOSS_SOFT };
- return (
-
- );
-}
-
-function CodeBlock({
- children,
- emphasized = false,
-}: {
- children: string;
- /** The one call site the page most wants a visitor's eye to land on —
- a solid moss fill instead of the usual cream, so it reads as the
- main focal point rather than blending into the section. */
- emphasized?: boolean;
-}) {
- return (
-
-
- {children}
-
-
-
- );
-}
-
-/* ── hero terminal — the page's signature ───────────────────────────── */
-
-const TRANSCRIPT: {
- kind: "user" | "tool" | "agent" | "prompt";
- text: string;
-}[] = [
- { kind: "user", text: "help me apply to MHacks" },
- { kind: "tool", text: "mhacks · get_application_schema" },
- { kind: "tool", text: "mhacks · save_application_draft" },
- {
- kind: "agent",
- text: "Draft saved. I still need your school and t-shirt size — then we can review the MLH terms together before submitting.",
- },
- { kind: "prompt", text: "" },
-];
-
-function TranscriptLine({ line }: { line: (typeof TRANSCRIPT)[number] }) {
- if (line.kind === "user" || line.kind === "prompt") {
- return (
-
- Works in Claude.ai on the web and in Claude Desktop.
-
-
- Go to Settings → Connectors → Add custom connector.
- Paste the server URL above.
-
- Claude will open a login page — sign in with your email (MHacks uses
- a one-time code sent to your inbox, no password).
-
-
- Approve the connection when prompted. You'll see what Claude is
- requesting access to before you approve.
-
-
-
- Then inside a session, run{" "}
- /mcp, select{" "}
- mhacks, and
- authenticate — same email login + approval as Claude.ai.
-
-
- );
- }
- if (client === "codex") {
- return (
-
- {/*
- Best-effort: based on Codex CLI's config.toml MCP server
- format as of this assistant's knowledge cutoff (Jan 2026).
- Codex's remote-MCP / OAuth support may have changed since —
- verify this against current Codex docs before treating it as
- authoritative, and update if the config shape or command has
- moved.
- */}
-
- Add the server to{" "}
- ~/.codex/config.toml:
-
- Codex will open a browser window for the same email one-time-code
- flow.
-
-
- );
- }
- return (
-
- Any client that supports the MCP Streamable HTTP transport and OAuth 2.1
- can connect using the same server URL. You'll go through the same
- email-login-and-approve flow regardless of client.
-
-
-
-
-
- );
-}
-
-/* ── what you can do: the prompts are the content ───────────────────── */
-
-const PROMPTS = [
- {
- quote: "Who am I connected as?",
- detail:
- "Confirms the MHacks account your agent is authenticated as, straight from your login, before you do anything else.",
- },
- {
- quote: "Check my MHacks application status",
- detail:
- "See whether you've already applied, and if so, its current status.",
- },
- {
- quote: "Help me fill out my MHacks application",
- detail:
- "Your agent can walk you through each field, save your progress as a draft, and come back to it later.",
- },
- {
- quote: "Submit my MHacks application",
- detail: "Once everything's filled in, your agent submits it for you.",
- },
-];
-
-function PromptsSection() {
- return (
-
-
-
- {PROMPTS.map((p) => (
-
-
-
- “{p.quote}”
-
-
- {p.detail}
-
-
-
- ))}
-
-
- );
-}
-
-/* ── how auth works ─────────────────────────────────────────────────── */
-
-const AUTH_FACTS: { lead: string; body: React.ReactNode }[] = [
- {
- lead: "Your identity comes from your login, not from anything you tell the agent.",
- body: "Whatever email you authenticate with is the account the application is tied to — an agent can't submit on someone else's behalf.",
- },
- {
- lead: "Submission is final.",
- body: "There's currently no MCP tool to edit or withdraw a submitted application, so review it with your agent before confirming.",
- },
- {
- lead: "You'll be asked to explicitly agree",
- body: "to the MLH Code of Conduct, Privacy Policy, and communications terms before submission — your agent should read these to you and ask for a clear yes/no, not assume.",
- },
- {
- lead: "Resume upload usually won't happen through the agent.",
- body: (
- <>
- Uploading requires the agent to make its own HTTP request with the
- file's raw bytes — attaching a PDF to the chat only lets the agent
- read it. Coding-agent clients with their own network access (Claude
- Code, Codex, Cursor) can do this; standard Claude.ai / Claude Desktop
- chat can't, so expect your agent to tell you to upload your resume
- yourself at{" "}
-
- mhacks.org/apply
-
- , then it'll confirm it landed before continuing.
- >
- ),
- },
- {
- lead: "You can revoke access at any time.",
- body: (
- <>
- See and revoke any agent's access at{" "}
-
- mhacks.org/account/connections
-
- .
- >
- ),
- },
-];
-
-function AuthSection() {
- return (
-
-
-
- {AUTH_FACTS.map((f, i) => (
-
-
-
- ✳
-
-
-
- {f.lead}
- {" "}
- {f.body}
-
-
-
- ))}
-
-
- );
-}
-
-/* ── gutter photo ────────────────────────────────────────────────────── */
-
-// The clear band must never run narrower than the content column, or text
-// spills past it into the photo. The content column is `max-w-3xl` (768px)
-// with `sm:px-6` (24px/side = 48px total) padding — the gutter photo only
-// ever renders at sm and up, so those are the only numbers that matter
-// here. MIN_GAP is extra breathing room on top of that: pure cream between
-// the text and the photo, never the two touching edge-to-edge. Below the
-// viewport where that gap can be honored, the photo clamps to 0 and just
-// isn't shown — "disappear" is the fallback, not the padding. Fixed
-// position (not scroll-linked) since a static photo doesn't need the
-// flowers' scroll-driven redraw.
-const MIN_GAP = 32;
-const GUTTER_WIDTH = `max(0px, calc((100vw - min(768px, calc(100vw - 48px))) / 2 - ${MIN_GAP}px))`;
-
-function GutterPhoto() {
- return (
-
- {/* Soft bloom behind the title — a quiet nod to the landing hero. */}
-
-
-
- Connect an AI agent to MHacks
-
-
- MHacks has an MCP server that lets you apply through Claude,
- Codex, or any other MCP-capable agent instead of filling out the
- web form by hand. Your agent can read the application schema, save
- a draft, ask you questions, upload your resume, and submit — all
- tied to your real, logged-in MHacks account.
-
-
-
-
-
-
-
-
-
- Server URL — use it exactly as written, in any client below
-
- {SERVER_URL}
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/app/layout.tsx b/app/layout.tsx
index 3f626a5..b0ef23f 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -1,9 +1,11 @@
-import type { Metadata } from "next";
+import type { Metadata, Viewport } from "next";
import {
Geist,
Geist_Mono,
+ Instrument_Sans,
Instrument_Serif,
Red_Hat_Display,
+ Red_Hat_Mono,
} from "next/font/google";
import "./globals.css";
import { TooltipProvider } from "@/components/ui/tooltip";
@@ -28,6 +30,12 @@ const instrumentSerif = Instrument_Serif({
style: ["normal", "italic"],
});
+const instrumentSans = Instrument_Sans({
+ variable: "--font-instrument-sans",
+ subsets: ["latin"],
+ weight: ["400", "500", "600"],
+});
+
const redHatDisplay = Red_Hat_Display({
variable: "--font-red-hat-display",
subsets: ["latin"],
@@ -35,10 +43,28 @@ const redHatDisplay = Red_Hat_Display({
style: ["normal", "italic"],
});
+const redHatMono = Red_Hat_Mono({
+ variable: "--font-red-hat-mono",
+ subsets: ["latin"],
+ weight: ["400", "500", "600"],
+});
+
export const metadata: Metadata = {
- title: "MHacks 2026",
+ metadataBase: new URL("https://mhacks.org"),
+ title: "MHacks 2026 · Digital Garden",
description:
- "Michigan's premier student hackathon — 24 hours, limitless ideas.",
+ "MHacks is the University of Michigan's flagship hackathon. 24 hours of building at the intersection of nature and technology. Ann Arbor, Fall 2026.",
+ openGraph: {
+ title: "MHacks 2026 · Digital Garden",
+ description:
+ "The University of Michigan's flagship hackathon. Build something that grows.",
+ type: "website",
+ },
+};
+
+export const viewport: Viewport = {
+ themeColor: "#3A4A26",
+ colorScheme: "light",
};
export default function RootLayout({
@@ -50,7 +76,7 @@ export default function RootLayout({
diff --git a/app/not-found.tsx b/app/not-found.tsx
index 21c051d..4164f7c 100644
--- a/app/not-found.tsx
+++ b/app/not-found.tsx
@@ -1,125 +1,133 @@
import Image from "next/image";
-import Link from "next/link";
-import { ArrowLeft, ArrowUpRight, Mail } from "lucide-react";
-const navLinks = [
- { href: "/#about", label: "About" },
- { href: "/#timeline", label: "Dates" },
- { href: "/#sponsors", label: "Sponsors" },
- { href: "/#faqs", label: "FAQ" },
-];
+import { MlhBadge } from "@/components/landing/MlhBadge";
+import { CtaButton } from "@/components/landing/cta-button";
+import { asset } from "@/lib/landing/asset";
+import { GRAIN_140 } from "@/lib/landing/textures";
-const detailItems = ["October 3 - 4, 2026", "Ann Arbor, MI", "800+ Hackers"];
+const DOT_GRID = [
+ "radial-gradient(circle, rgba(255,255,255,0.3) 0.5px, transparent 0.5px)",
+ "radial-gradient(circle, rgba(255,255,255,0.16) 0.5px, transparent 0.5px)",
+].join(", ");
export default function NotFound() {
return (
-
-
-
-
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
-
-
+
-
- Apply Now
-
-
+
+
-
-
-
- 404 / Page not found
-
-
- Lost in Ann Arbor
-
-
- This path did not bloom, but MHacks is still right where you left
- it.
-
- MHacks is the University of Michigan's flagship hackathon,
- bringing together the brightest student minds from across the
- country.
-
-
- Over 24 hours, you'll collaborate, create, and compete for over
- $40,000 in prizes.
-
-
- Whether you're a seasoned hacker or attending your first
- hackathon, MHacks is the place to turn your wildest ideas into
- reality.
-
-
- Join us for a weekend of innovation, mentorship, and community.
-
-
-
-
- );
-}
diff --git a/components/ascii-background.tsx b/components/ascii-background.tsx
deleted file mode 100644
index 0d0f16f..0000000
--- a/components/ascii-background.tsx
+++ /dev/null
@@ -1,138 +0,0 @@
-"use client";
-
-import { useEffect, useRef } from "react";
-
-// deterministic 2D hash → [0,1), used to scatter + vary the flowers
-function hash(x: number, y: number) {
- let h = Math.imul(x, 374761393) + Math.imul(y, 668265263);
- h = Math.imul(h ^ (h >>> 13), 1274126177);
- return ((h ^ (h >>> 16)) >>> 0) / 4294967295;
-}
-
-// a small ascii flower: a center dot ringed by inner petals and outer tips,
-// all drawn as little squares to keep the dotted-glyph aesthetic
-function drawFlower(
- ctx: CanvasRenderingContext2D,
- cx: number,
- cy: number,
- r: number,
- rot: number,
- alpha: number,
- color: string,
-) {
- const dot = Math.max(2, Math.round(r / 5));
- const tip = Math.max(1, dot - 1);
- ctx.fillStyle = `rgba(${color}, ${alpha})`;
- // center
- ctx.fillRect(cx - dot / 2, cy - dot / 2, dot, dot);
- const petals = 6;
- for (let p = 0; p < petals; p++) {
- const a = rot + (p / petals) * Math.PI * 2;
- const ix = cx + Math.cos(a) * r * 0.55;
- const iy = cy + Math.sin(a) * r * 0.55;
- ctx.fillRect(ix - dot / 2, iy - dot / 2, dot, dot);
- const ox = cx + Math.cos(a) * r;
- const oy = cy + Math.sin(a) * r;
- ctx.fillRect(ox - tip / 2, oy - tip / 2, tip, tip);
- }
-}
-
-/** Ascii flowers scattered down the left and right gutters, leaving the center
- * column clear for text and cards. The field parallaxes with the page and the
- * blooms gently rotate/swell as you scroll, so it reacts to scroll site-wide.
- * Sits at -z-10 so it paints over the paper background but behind content. */
-export default function AsciiBackground({
- gap = 74,
- color = "58, 74, 38", // moss rgb channels — alpha is computed per flower
-}: {
- gap?: number;
- color?: string;
-}) {
- const ref = useRef(null);
-
- useEffect(() => {
- const canvas = ref.current;
- if (!canvas) return;
- const ctx = canvas.getContext("2d");
- if (!ctx) return;
-
- let raf = 0;
- let scrollY = window.scrollY;
- let dpr = 1;
-
- const draw = () => {
- const w = canvas.width / dpr;
- const h = canvas.height / dpr;
- ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
- ctx.clearRect(0, 0, w, h);
-
- // clear central band for content; flowers live in the side gutters
- const clear = Math.min(w - 64, 940);
- const gutter = Math.max(0, (w - clear) / 2);
- if (gutter < 8) return;
-
- const cols = Math.ceil(w / gap) + 2;
- const startRow = Math.floor(scrollY / gap) - 1;
- const endRow = Math.ceil((scrollY + h) / gap) + 1;
-
- for (let gy = startRow; gy <= endRow; gy++) {
- for (let gx = 0; gx < cols; gx++) {
- if (hash(gx, gy) < 0.42) continue;
- const seed = hash(gx, gy);
- const jx = (hash(gx + 31, gy + 7) - 0.5) * gap * 0.6;
- const jy = (hash(gx + 5, gy + 53) - 0.5) * gap * 0.6;
- const x = gx * gap + jx;
- const worldY = gy * gap + jy;
- const y = worldY - scrollY;
- if (y < -gap || y > h + gap) continue;
-
- // only keep flowers inside the side gutters, fading toward center
- const distFromEdge = Math.min(x, w - x);
- if (distFromEdge > gutter) continue;
- const edge = Math.max(0, Math.min(1, 1 - distFromEdge / gutter));
-
- // scroll-driven bloom: blossoms swell + brighten in a travelling wave
- const wave =
- Math.sin(worldY / 220 - gx * 0.4 - scrollY / 360) * 0.5 + 0.5;
- const r = gap * (0.24 + seed * 0.16) * (0.8 + wave * 0.4);
- const alpha = (0.22 + edge * 0.48) * (0.65 + wave * 0.35);
- const rot = seed * Math.PI * 2 + scrollY / 1200;
-
- drawFlower(ctx, x, y, r, rot, alpha, color);
- }
- }
- };
-
- const resize = () => {
- const w = window.innerWidth;
- const h = window.innerHeight;
- dpr = Math.min(window.devicePixelRatio || 1, 2);
- canvas.width = Math.round(w * dpr);
- canvas.height = Math.round(h * dpr);
- draw();
- };
-
- const onScroll = () => {
- scrollY = window.scrollY;
- cancelAnimationFrame(raf);
- raf = requestAnimationFrame(draw);
- };
-
- resize();
- window.addEventListener("scroll", onScroll, { passive: true });
- window.addEventListener("resize", resize);
- return () => {
- window.removeEventListener("scroll", onScroll);
- window.removeEventListener("resize", resize);
- cancelAnimationFrame(raf);
- };
- }, [gap, color]);
-
- return (
-
- );
-}
diff --git a/components/cta-section.tsx b/components/cta-section.tsx
deleted file mode 100644
index 3444b97..0000000
--- a/components/cta-section.tsx
+++ /dev/null
@@ -1,68 +0,0 @@
-"use client";
-
-import { motion } from "framer-motion";
-import { useApplicationsOpen } from "./use-applications-open";
-
-const EASE = [0.25, 0.1, 0.25, 1] as const;
-
-export default function CtaSection() {
- const applicationsOpen = useApplicationsOpen();
-
- return (
-
-
-
-
-
- );
-}
diff --git a/components/faq-accordion.tsx b/components/faq-accordion.tsx
deleted file mode 100644
index cf966e1..0000000
--- a/components/faq-accordion.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-"use client";
-
-import { useState } from "react";
-
-const faqs = [
- {
- q: "What is MHacks?",
- a: "MHacks is the University of Michigan's premier student-run hackathon, held annually in Ann Arbor. It brings together hundreds of students from across the country for a weekend of building, learning, and competing.",
- },
- {
- q: "Who can attend?",
- a: "MHacks is open to all currently enrolled undergraduate and graduate students. Students of all skill levels and disciplines are welcome — no prior hackathon experience required.",
- },
- {
- q: "Where is MHacks?",
- a: "MHacks is held on the University of Michigan's campus in Ann Arbor, Michigan. Venue details will be shared with registered participants closer to the event date.",
- },
- {
- q: "Are there travel reimbursements?",
- a: "We offer travel reimbursements on a limited basis for participants traveling from outside the Ann Arbor area. Apply during registration and we'll follow up with details.",
- },
- {
- q: "How much does MHacks cost?",
- a: "MHacks is completely free to attend. We cover meals, snacks, and event resources throughout the entire 24-hour event, thanks to our generous sponsors.",
- },
- {
- q: "How do teams work?",
- a: "Teams can have 1–4 members. You can form a team before the event or find teammates at our team formation session at the start of the hackathon.",
- },
- {
- q: "What if this is my first hackathon?",
- a: "First-timers are absolutely welcome. We'll have intro workshops, beginner-friendly resources, and dedicated mentors to help you build something great regardless of your experience level.",
- },
-];
-
-export default function FaqAccordion() {
- const [open, setOpen] = useState(null);
-
- return (
-
- {faqs.map((faq, i) => (
-
-
-
-
{faq.a}
-
-
- ))}
-
- );
-}
diff --git a/components/faq-section.tsx b/components/faq-section.tsx
deleted file mode 100644
index 281d6c3..0000000
--- a/components/faq-section.tsx
+++ /dev/null
@@ -1,148 +0,0 @@
-"use client";
-
-import { useState } from "react";
-import Image from "next/image";
-import { AnimatePresence, motion } from "framer-motion";
-
-const EASE = [0.25, 0.1, 0.25, 1] as const;
-
-const FAQS = [
- {
- q: "What is MHacks?",
- a: "MHacks is the University of Michigan's premier student-run hackathon, held annually in Ann Arbor. It brings together hundreds of students from across the country for a weekend of building, learning, and competing.",
- },
- {
- q: "Who can attend?",
- a: "MHacks is open to all currently enrolled undergraduate and graduate students. Students of all skill levels and disciplines are welcome — no prior hackathon experience required.",
- },
- {
- q: "Where is MHacks?",
- a: "MHacks is held on the University of Michigan's campus in Ann Arbor, Michigan. Venue details will be shared with registered participants closer to the event date.",
- },
- {
- q: "Are there travel reimbursements?",
- a: "We offer travel reimbursements on a limited basis for participants traveling from outside the Ann Arbor area. To qualify, you must submit your application before the early deadline (Aug. 7). Apply during registration and we'll follow up with details.",
- },
- {
- q: "How much does MHacks cost?",
- a: "MHacks is completely free to attend. We cover meals, snacks, and event resources throughout the entire 24-hour event, thanks to our generous sponsors.",
- },
- {
- q: "How do teams work?",
- a: "Teams can have 1–4 members. You can form a team before the event or find teammates at our team formation session at the start of the hackathon.",
- },
- {
- q: "What if this is my first hackathon?",
- a: "First-timers are absolutely welcome. We'll have intro workshops, beginner-friendly resources, and dedicated mentors to help you build something great regardless of your experience level.",
- },
-];
-
-function FaqItem({ q, a, index }: { q: string; a: string; index: number }) {
- const [open, setOpen] = useState(false);
-
- return (
-
-
-
-
- {open && (
-
-
;
+}
+
+/** Faux agent session showing the MCP apply flow — the dark terminal card
+ used on the home Agent section and the How to MCP page hero. */
+export function AgentTerminal({ className }: { className?: string }) {
+ const reduced = useReducedMotion();
+ return (
+