Skip to content
Open
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
47 changes: 47 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# AGENTS.md

## Cursor Cloud specific instructions

This is a **pnpm + Turborepo monorepo** (`pnpm@10.33.2`, Node 20–23; the VM has Node 22 + pnpm via `corepack`). It contains three deployable products plus shared packages:

- `sites/mainweb` (workspace name `web`) — the main Next.js full‑stack app: public marketing site **and** an authenticated member Portal (auth, tRPC, Drizzle/Postgres, Stripe). Dev server on **port 3001**.
- `sites/hacklytics2027` — static Next.js hackathon marketing site. Dev server on **port 3000**.
- `sites/discordBot` (`@query/discord-bot`) — standalone Discord.js bot (no HTTP port).

Standard scripts live in the root `package.json` (`dev`, `build`, `lint`, `typecheck`, `test`) and per‑package `package.json` files. `pnpm dev` runs every app in parallel via Turbo; use `pnpm --filter web dev` / `pnpm --filter hacklytics2027 dev` to run one. Tests: `pnpm test` (Vitest, `packages/api`). Lint: `pnpm lint`.

### Dependencies are auto-installed
The startup/update script runs `pnpm install`. You normally do **not** need to reinstall.

### Postgres runs in Docker, started manually (no systemd on the VM)
The DB is **required** for the mainweb portal and any DB/auth flows. Docker is pre-installed but the daemon is **not** started automatically:

1. Start the Docker daemon (background): `sudo dockerd > /tmp/dockerd.log 2>&1 &` then wait a few seconds. The daemon is configured for `fuse-overlayfs` with the containerd snapshotter disabled (see `/etc/docker/daemon.json`) — required in this VM; don't change it.
2. Start Postgres: `sudo docker compose up -d db` (root `docker-compose.yml`; Postgres 15 on host port **5433**, db `app_db`, user/pass `postgres`/`postgres`).
3. Apply the schema: `cd packages/db && pnpm drizzle-kit push --force`. See gotcha below — `pnpm --filter @query/db migrate:push` is interactive and **silently no-ops in a non-interactive shell**, so always pass `--force`. It is idempotent.

### CRITICAL: injected Secrets point at PRODUCTION — override to local before running dev servers
The user's Cursor Secrets are injected into the VM environment and set `DATABASE_URL`, `EMAIL_SERVER_*` (real Gmail SMTP), `STRIPE_*` (LIVE keys), `NEXTAUTH_URL`, etc. to **production/live** values. **Next.js does NOT override already-defined `process.env` values with `.env`/`.env.local`**, so a dev server started with the ambient environment will talk to the **production database, send real emails, and use live Stripe keys**.

To develop safely against local services, launch the mainweb dev server with explicit local overrides so they take precedence over the injected env, e.g.:

```bash
DATABASE_URL='postgresql://postgres:postgres@localhost:5433/app_db' \
AUTH_URL='http://localhost:3001' NEXTAUTH_URL='http://localhost:3001' \
EMAIL_SERVER_HOST='127.0.0.1' EMAIL_SERVER_PORT='1025' EMAIL_SERVER_USER='' EMAIL_SERVER_PASSWORD='' \
EMAIL_FROM='noreply@datasciencegt.org' \
STRIPE_SECRET_KEY='' NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY='' STRIPE_WEBHOOK_SECRET='' \
pnpm --filter web dev
```

`AUTH_SECRET`/`NEXTAUTH_SECRET` from the injected env are fine to reuse. `sites/mainweb/.env.local` and root `.env` also hold local values, but remember they are ignored for any variable already present in `process.env`, which is why the inline overrides above are needed. `packages/db` tooling (drizzle-kit) reads the root `.env` directly, so keep `DATABASE_URL` local there too.

### Email (6-digit code) login without real email
The portal login is a 6-digit code flow. The code is written to the Postgres `verificationToken` table as `custom:<code>` before the email is sent. For local dev you can either (a) run a local SMTP catcher on `127.0.0.1:1025` (`python3 -m aiosmtpd -n -l 127.0.0.1:1025`, pre-installed) and point `EMAIL_SERVER_HOST/PORT` at it, or (b) simply read the latest code from the DB:
`sudo docker exec monorepo-postgres psql -U postgres -d app_db -tAc "SELECT replace(token,'custom:','') FROM \"verificationToken\" WHERE token LIKE 'custom:%' ORDER BY expires DESC LIMIT 1;"`

### Discord bot needs live credentials
`sites/discordBot` validates its env with zod and hard-fails unless real `DISCORD_BOT_TOKEN`, `DISCORD_CLIENT_ID`, `GUILD_ID`, role IDs, and `NOTION_TOKEN`/`NOTION_GUIDE_PAGE_ID` are set. It cannot run locally without those and is optional for portal/site work.

### Stale docs
`GCP_SETUP.md` references `pnpm dev:full` and `scripts/sync-secrets.sh`, **neither of which exists**. Use `pnpm dev` / per-filter commands instead.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
245 changes: 142 additions & 103 deletions sites/hacklytics2027/app/globals.css
Original file line number Diff line number Diff line change
@@ -1,146 +1,185 @@
/* app/globals.css — Digital Bloom Theme */
/* app/globals.css — Hacklytics 2027 · Swiss editorial system
Paper stock, ink type, one gold rule. No glass, no glow, no blur. */
@import "tailwindcss";

@theme inline {
--color-bloom-pink: var(--bloom-pink);
--color-bloom-cyan: var(--bloom-cyan);
--color-bloom-lime: var(--bloom-lime);
--color-bloom-purple: var(--bloom-purple);
--color-gridline: var(--color-gridline);
--color-paper: var(--paper);
--color-paper-2: var(--paper-2);
--color-ink: var(--ink);
--color-ink-soft: var(--ink-soft);
--color-navy: var(--navy);
--color-gold: var(--gold);
--color-gold-hot: var(--gold-hot);
--color-gold-deep: var(--gold-deep);
--color-rule: var(--rule);

/* legacy tokens — kept so unmounted components don't render invisible */
--color-bloom-pink: var(--gold-hot);
--color-bloom-cyan: var(--navy);
--color-bloom-lime: var(--gold);
--color-bloom-purple: var(--navy);
}

:root {
--color-bg: #030305;
--color-text: #f0f0f2;
--color-gridline: rgba(255, 255, 255, 0.05);
--paper: #f2efe9;
--paper-2: #e6e1d6;
--ink: #101010;
--ink-soft: #55524b;
--navy: #003057;
--gold: #b3a369;
--gold-hot: #eaaa00; /* on ink only — 1.8:1 on paper */
--gold-deep: #7a5c00; /* gold for type on paper — 5.4:1 */
--rule: rgba(16, 16, 16, 0.16);
--rule-strong: rgba(16, 16, 16, 0.85);

--bloom-pink: #ff2d78;
--bloom-cyan: #00e5ff;
--bloom-lime: #c8ff00;
--bloom-purple: #9b00ff;
--font-family-mono: var(--font-mono), ui-monospace, monospace;
--font-family-sans: var(--font-sans), "Helvetica Neue", Arial, sans-serif;

--font-family-mono: "Roboto Mono", monospace;
--font-family-sans: "Space Grotesk", sans-serif;
--navbar-height: 58px;
--gutter: 1.25rem;
}

--navbar-height: 72px;
@media (min-width: 768px) {
:root { --gutter: 2.5rem; --navbar-height: 64px; }
}

/* ─── Base ─── */
html { scroll-behavior: smooth; }
/* ─── Base ─────────────────────────────────────────────────────────────── */
html {
scroll-behavior: smooth;
background: var(--paper);
}

body {
background-color: var(--color-bg);
color: var(--color-text);
background-color: var(--paper);
color: var(--ink);
font-family: var(--font-family-sans);
overflow-x: hidden;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}

::selection {
background-color: var(--bloom-cyan);
color: var(--color-bg);
background-color: var(--ink);
color: var(--paper);
}

/* ─── Typography ─── */
.font-display {
font-family: var(--font-family-sans);
font-weight: 700;
letter-spacing: -0.04em;
line-height: 0.9;
/* Ink ring + paper halo, so focus stays visible on paper AND on ink surfaces */
:focus-visible {
outline: 2px solid var(--ink);
outline-offset: 2px;
box-shadow: 0 0 0 4px var(--paper);
}

/* ─── Glass Surfaces — used sparingly ─── */
.glass {
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.08);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
/* ─── Type ─────────────────────────────────────────────────────────────── */
.display {
font-family: var(--font-family-sans);
font-weight: 800;
text-transform: uppercase;
letter-spacing: -0.045em;
line-height: 0.82;
font-feature-settings: "ss01";
}

/* Navbar glass — slightly more opaque */
.glass-nav {
background: rgba(5, 5, 8, 0.85);
border-bottom: 1px solid rgba(255, 255, 255, 0.06);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
.mono-label {
font-family: var(--font-family-mono);
font-weight: 500;
font-size: 0.6875rem;
text-transform: uppercase;
letter-spacing: 0.12em;
line-height: 1;
}

/* ─── Glow utilities — used intentionally, not everywhere ─── */
.glow-pink { box-shadow: 0 0 32px rgba(255, 45, 120, 0.35); }
.glow-cyan { box-shadow: 0 0 32px rgba(0, 229, 255, 0.35); }
.glow-lime { box-shadow: 0 0 32px rgba(200, 255, 0, 0.3); }
.glow-purple { box-shadow: 0 0 32px rgba(155, 0, 255, 0.3); }
.tabular { font-variant-numeric: tabular-nums; }

.text-glow-cyan {
text-shadow: 0 0 20px rgba(0, 229, 255, 0.5);
}
.text-glow-pink {
text-shadow: 0 0 20px rgba(255, 45, 120, 0.5);
.lede {
font-size: clamp(1.125rem, 1.6vw, 1.5rem);
line-height: 1.45;
letter-spacing: -0.012em;
}

/* ─── Layout ─── */
.section-wrap {
padding-left: 1.5rem;
padding-right: 1.5rem;
.dropcap::first-letter {
float: left;
font-weight: 800;
font-size: 4.6em;
line-height: 0.74;
padding: 0.06em 0.1em 0 0;
color: var(--ink);
}
@media (min-width: 768px) {
.section-wrap { padding-left: 3rem; padding-right: 3rem; }
}
@media (min-width: 1280px) {
.section-wrap { padding-left: 5rem; padding-right: 5rem; }
}


/* ─── Rules & structure ────────────────────────────────────────────────── */
.rule-t { border-top: 1px solid var(--rule); }
.rule-b { border-bottom: 1px solid var(--rule); }
.rule-heavy-t { border-top: 2px solid var(--ink); }
.rule-heavy-b { border-bottom: 2px solid var(--ink); }

/* ─── Backward-compat shims for unrewritten components ─── */
.glass-panel {
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.08);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-radius: 1rem;
.wrap {
width: 100%;
margin-inline: auto;
max-width: 88rem;
padding-inline: var(--gutter);
}

/* Visible column rules — the page shows its own grid */
.gridlines {
position: absolute;
inset: 0;
pointer-events: none;
z-index: 0;
background-image: repeating-linear-gradient(
to right,
var(--rule) 0 1px,
transparent 1px calc(100% / 6)
);
opacity: 0.5;
}
.glass-card {
background: rgba(255, 255, 255, 0.02);
border: 1px solid rgba(255, 255, 255, 0.06);
border-radius: 0.75rem;
@media (max-width: 767px) { .gridlines { display: none; } }

.section-anchor {
scroll-margin-top: calc(var(--navbar-height) + 1px);
content-visibility: auto;
contain-intrinsic-size: auto 700px;
}
.hover-bloom-glow:hover {
border-color: rgba(0, 229, 255, 0.4);
box-shadow: 0 0 20px rgba(0, 229, 255, 0.2);

/* ─── Invert-on-hover — the one interaction motif ──────────────────────── */
.invert-hover {
transition: background-color 140ms linear, color 140ms linear;
}
.bloom-text-glow {
text-shadow: 0 0 20px rgba(0, 229, 255, 0.5);
.invert-hover:hover {
background-color: var(--ink);
color: var(--paper);
}


/* ─── Accent line — thin colored rule used for section punctuation ─── */
.accent-line {
display: block;
width: 2.5rem;
height: 2px;
border-radius: 1px;
/* ─── Ticker ───────────────────────────────────────────────────────────── */
@keyframes ticker-scroll {
from { transform: translate3d(0, 0, 0); }
to { transform: translate3d(-50%, 0, 0); }
}

/* ─── Section anchor offset for fixed navbar ─── */
.section-anchor {
scroll-margin-top: calc(var(--navbar-height) + 1.5rem);
content-visibility: auto;
contain-intrinsic-size: auto 600px;
.ticker-track {
display: flex;
width: max-content;
animation: ticker-scroll 38s linear infinite;
}

/* ─── Divider ─── */
.bloom-divider {
width: 100%;
height: 1px;
background: linear-gradient(
to right,
transparent,
rgba(255, 255, 255, 0.08) 20%,
rgba(255, 255, 255, 0.08) 80%,
transparent
);
margin: 0;
/* ─── Scroll reveal — progressive, no JS, no layout shift ──────────────── */
@supports (animation-timeline: view()) {
@media (prefers-reduced-motion: no-preference) {
.reveal {
animation: reveal-in linear both;
animation-timeline: view();
animation-range: entry 5% cover 22%;
}
@keyframes reveal-in {
from { opacity: 0; transform: translateY(18px); }
to { opacity: 1; transform: none; }
}
}
}


@media (prefers-reduced-motion: reduce) {
html { scroll-behavior: auto; }
.ticker-track { animation: none; }
*, *::before, *::after {
animation-duration: 0.001ms !important;
transition-duration: 0.001ms !important;
}
}
5 changes: 5 additions & 0 deletions sites/hacklytics2027/app/icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading