diff --git a/CLAUDE.md b/CLAUDE.md index de21906c..b0f2ba6e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,9 @@ Full spec + rollout checklist: **`docs/refined-design-system.md`** (read this before extending the look to more pages). Migrated so far: `/` (`src/pages/index.js`), `/projects` (`src/components/ProjectList/*`), the global NavBar (incl. a refined mobile layout — centered logo + refined dropdown), `/about`, `/about/judges`, `/about/mentors` (`Mentorship` rewritten as refined sections), `/about/success-stories`, `/about/completion`, `/sponsor`, `/nonprofits` (`NonProfitList` + new calm card `NonProfitListTileRefined`; original `NonProfitListTile` stays for event pages), `/blog` (`BlogPage`; reused `News` list kept), `/onboarding` (refined chrome; wizard logic intact; **July 2026 content overhaul** — 9 steps: Welcome, Mission, How It Works [full lifecycle incl. post-hackathon Definition of Done + project-status ladder], Get Involved [hacker/mentor/judge/volunteer/nonprofit role cards], Using the Site [site map + first-steps guide + "Anatomy of a project page" numbered walkthrough (problem → code → plan → people; mirrors ProblemStatement.js section order — keep in sync) + "public portfolio" band: GitHub/Slack/demo-videos/public-profile are what recruiters can review; work is documented as GitHub Issues for public credit — no issues in a repo → pull the code and write them like a PM], Slack, Introduce Yourself, FAQs, Feedback. New sections: `HowItWorksSection`/`RolesSection`/`WebsiteTourSection` in `src/components/Onboarding/`; `JudgingOverview`/`MentoringOverview`/`BuddySystem` are now orphaned — don't re-add them or their claims. FAQ answers must reflect real flows: there are NO project leads, NO commenting on projects, NO buddy system — joining a project = "Want to help?" toggle / its Slack channel / GitHub repo (write Issues like a PM if none exist) / next hackathon. **Design contract:** step content renders inside a scoped `onboardingTheme` ThemeProvider (in `pages/onboarding/index.js` — Fraunces headings, Hanken body, navy/terracotta palette, flat hairline Papers/Cards; don't reintroduce elevation shadows, hover-lifts, or rainbow chips) and every step opens with the shared `StepHeader` (renders `

`; the page masthead owns the `

`). Slack step leads with the two-account gotcha (an ohack.dev login [usually Google] does NOT create a Slack account — join via `/signup`) and real channel deep links: #introductions C01EY49JV8U, #ask-a-mentor C01E5CGDQ74, #random C06BRHRS5BQ, plus per-project #npo-* channels; never reference #help/#team-formation/#buddy-matching/#project-matching — they don't exist), `/signup` (full rewrite: editorial hero, "What you get" benefit cards, numbered join steps beside the framed `join_slack_1.png`, navy CTA band; primary CTA is now a real `` — dropped the JS `router.push`/`window.open` branch; `handleSignupClick` only fires the `CompleteRegistration` GA event), `/volunteer`, `/about/hackers`, `/about/process` (refined chrome; Mermaid flow + Gantt diagrams kept in calm card frames), `/praise` (refined chrome; `PraiseBoard` feed kept), `/hack/code-of-conduct` (full rewrite: editorial hero, core-values cards, numbered accessibility list, expected-vs-unacceptable two-col w/ +/× markers, navy CTA band), `/profile/[userid]` public (`PublicProfile`), and a full refined pass on the own `/profile` editor (`Profile.js` — editorial masthead, hairline sticky tab strip, scoped navy/terracotta MUI theme, `PanelHeader` on every tab, privacy legend on Basic Info, hairline rules in Volunteer History, `.ohx-card` Giveaway Entries; form logic untouched), `/myfeedback` (full rewrite: editorial hero, navy CircularProgress score ring w/ Fraunces number, hairline skill accordions w/ navy LinearProgress bars, `.ohx-card` entries + `.ohx-tag` chips) and `/feedback/[userid]` (`GiveFeedback`, `ssr:false`: hero + `Section`-framed form, navy-themed MUI Select/Radio/Slider/Checkbox, native `.ohx-btn` submit; also fixed a pre-existing hooks-order bug where `if (!user)` returned before `useEffect`), `/hack/[event_id]/manageteam` (**Sep 2026: rebuilt as the Team Dashboard** — `src/components/TeamDashboard/*`, the DevPost replacement; composition + invariants in the "Team Dashboard" section below), and `/hack/[event_id]/team/[team_id]` (full rewrite: editorial masthead + hairline `.ohx-card` sections + a **deep-linkable sticky Table of Contents** — sections are `
` with hover `#` copy-link anchors (keyboard accessible via `&:focus-visible`), the TOC lists only present sections (`aria-current="location"`) and tracks the active one via `IntersectionObserver`; embedded `MentorTeamPanel`/`TeamCompletionChecklist` keep their own styling in anchored `headed={false}` wrappers). **Key invariants on this page:** (1) Status labels come from `TEAM_STATUS_OPTIONS.find()` via `statusLabel()` — never render raw enum strings like `NONPROFIT_SELECTED`; winning statuses get a 🏆 prefix from `getWinningStatus()`; `INACTIVE` skips the status tag since the dot already conveys it. (2) `SectionBlock` is **module-scope** (not inside the component) — defining it inside caused a remount storm on every IntersectionObserver `setActiveId` tick, wiping in-progress mentor note drafts and reloading the demo iframe; it receives `copiedId`/`onCopyLink` as props. (3) `parseLocalDate` from `src/lib/dateUtils.js` is used for all event window checks (`eventHasStarted`, `eventEnded`) so date-only strings aren't parsed as UTC midnight. (4) Membership check uses `useTeamMembership(eventId, teamId)` from `src/hooks/use-team-membership.js` — shared by the page and passed as `isOnTeam`/`membershipChecked` props to `TeamCompletionChecklist` (no duplicate fetch). Page uses result to show: "Manage your team" primary CTA for members, a combined nudge card for missing DevPost/demo, and a ghost "Want to join this team?" affordance for non-members when status is joinable. (5) Nonprofit: never render `selected_nonprofit_id` text — only show when `nonprofitData.name` is available; `getStaticProps` fetches `{ name, description }` into `nonprofitData`; a "Nonprofit partner" section card links to `/nonprofit/`. (6) `awards[]` rendered as `ohx-tag--accent` chips after the status tag. (7) All GitHub repos in `github_links[]` rendered (handles both string and `{link,name}` shapes). (8) `getStaticProps` rethrows network errors (ISR keeps the last good version) and returns `notFound` only on genuine 404. (9) **Project section first** (Sep 2026): `TeamProjectSection` (`id="project"`) renders before every other section when `hasProjectContent(team)`; members without a write-up get the "Tell the story of what you built." card → `manageteam#project`; legacy teams (no `project_*`) render no Project section and no submission tag. (10) **OG + JSON-LD come from `src/components/Teams/projectMeta.js`** (`buildTeamOgImage`/`buildTeamDescription`/`buildProjectJsonLd`) — never hand-compose them in the page; see "Project pages & gallery". **Harmonized (not full rewrite):** `/hack` index (keeps its bespoke finder + `HackPageNav`; hero/CTAs/Support band recolored via inline `RX` tokens). The upcoming/current event cards were refined too: `HackathonList` full-mode heading → Fraunces eyebrow; `EventFeature` full card rewritten to a refined hairline card (eyebrow date + quiet type tag, Fraunces title, navy donation rings, navy-primary/ghost event-link buttons, "View event →"; fixed the old nested-anchor bug; var-fallback colors since `/hack` isn't a `RefinedRoot`). `ImpactMetrics` (shared with the archive) → warm `--surface-2` tiles with Fraunces navy numbers + "Impact at a glance" overline (was rainbow `color`-prop numbers). `/hack/[event_id]` got a **masthead-led refined pass**: body wrapped in `` (it's now the `
`; all section IDs + `TableOfContents` + `FloatingNavigation` preserved), `HackathonHeader` rebuilt into a scope-independent editorial masthead (inline styles w/ `var(--x, fallback)` so `/agenda` + `/census` also benefit), refined "Build a team" buttons + recap teaser. Phase-2 refined the **event-only** sub-components: `NonprofitList` (calm clickable cards + navy view-toggle), `EventConstraints`, `DonationProgress` (navy rings), `EventCountdown` (navy countdown card replacing the gradient + flat timeline), `TeamList`/`TeamCard` (flat hairline cards, Fraunces team names, refined "Team members" label + navy Join button — `TeamList` is event-page-only despite earlier substring matches in admin/event-teams; **profile avatars load lazily per-card via IntersectionObserver with `rootMargin: "200px"`, tracked by `fetchedTeamProfilesRef` Set** — do NOT restore the eager `fetchTeamMemberProfiles(teams)` call on mount that fired N parallel requests for all 30+ cards at once). **Backend C7 enrichment:** `get_single_hackathon_event` (`services/hackathons_service.py`) now calls `_enrich_teams_users_batch` after converting team DocumentReferences — one `db.get_all` across all members of all teams, deduped, so the frontend receives `users[]` already as `{id, user_id, name, nickname, profile_image}` objects. `HackathonResults.js`/`TeamList.js` handle both the old id-string and new object shapes (backwards-compatible). The list endpoint `get_hackathon_list` is NOT enriched — only the single-event getter., and `HackathonLeaderboard` (flat cards, warm stat tiles w/ terracotta icons + Fraunces navy numbers, squared navy Org button). **Phase 3 (done) finished the event page**: `EventLinks` (6 rainbow app buttons → one calm uniform card set + calm social-proof strip), `InteractiveFAQ` (flat hairline accordions), `HackathonResults` (calm `--surface-2` frame, Fraunces navy stat numbers; gold/silver/bronze winner medals kept), `VolunteerList` (`PersonCard` → flat hairline). The **shared** ones (`InteractiveFAQ`, `HackathonResults`) use **inline CSS-var fallbacks** (`var(--ink,#16181D)`) so they render refined on their non-`RefinedRoot` routes too (verified on `/hack/[event_id]/results` + `OnboardingFAQ`) — no per-route gating. A final chrome pass refined `TableOfContents` (navy active pill, was magenta), `FloatingNavigation` (navy FAB), `VolunteerList` (navy "Learn more" + navy availability chips; in-person stays green), and `MentorAvailability` (navy banner/Slack button). The event page is now fully refined end-to-end. Also refined: `/hack/request` (host-an-event page + `HackathonRequestForm`) and `/office-hours`. **`/hack/request` is student-first** — org-type selectable cards default to **university** (was corporate); the **$5k budget minimum was removed** (slider min `$0`, never blocks submit, reframed as optional funding); the donation-% ask is **corporate-only** (forced to 0 at submit otherwise); phone is optional; rainbow Papers → calm `--surface-2` cards; everything navy/terracotta via a scoped MUI `ThemeProvider` (`createTheme(base, …)`). **`formData` keys are preserved** — `HackathonRequestDetailDialog`, the edit page `/hack/request/[request_id]`, and the backend all depend on them. **Scroll gotcha:** step nav now `scrollIntoView`s a `formTopRef` on the form's Paper (`scrollMarginTop:88`), NOT `window.scrollTo(0,0)` (which overshot to page-top now that the form sits below the hero). `/office-hours` now renders its JSON-LD, uses the idempotent `initFacebookPixel` (was a raw `ReactPixel.init` — CWV rule), and dropped the artificial 1s loading delay + dead ICS code. Also refined: `/nonprofit/[nonprofit_id]` (`NonProfit.js` — editorial masthead, meta row as `.ohx-tag`/`.ohx-link`s, projects list, canonical www fix) and `/project/[project_id]` (`Project.js` — `RefinedRoot` chrome, soft breadcrumb, canonical). Both share `ProblemStatement.js` which was fully restyled (purple gradient hero → calm `--surface-2` band; gradient chips → `.ohx-tag`; gradient metric tiles → Fraunces navy tiles; gradient section headers → hairline `.ohx-card`; gradient CTAs → `.ohx-btn`). **Key invariant:** `ProblemStatement` is ONLY ever inside a `RefinedRoot`, so it may use scoped `.ohx-*` classes directly (no inline `var(--x, fallback)` needed). Pass `headingLevel="h2"` from the nonprofit page (N cards, nonprofit name is the `

`) and `headingLevel="h1"` from the project page (project title is the `

`). The "Want to help?" toggle is hidden when `status === "production"` (live projects don't need volunteers) but stays visible for users already helping so they can toggle off; `maintenance` status keeps the toggle. **"Code & Tasks" (July 2026 discoverability fix):** it is an ALWAYS-VISIBLE section (`id="code-and-tasks-"` — unique per problem statement because nonprofit pages render N cards; `scrollMarginTop: 96`) directly after the Project Description — never move it back into the collapsed accordion trio (burying it was the bug; only References/Events stay accordions). Repo list = `problem_statement.github` (handles legacy string shape) MERGED with repos from hackathon teams in the already-fetched event data (`team.problem_statements` includes the ps id; `selected_nonprofit_id`-vs-`resolvedNonprofits` fallback applies ONLY to teams with no `problem_statements` — avoids claiming a sibling project's repos on multi-project nonprofits), deduped by `normalizeRepoLink`; team repos render under a "from hackathon teams" label with "Built by {team} ({event})" attribution, and a duplicate just enriches the project-level card. Live issue data (counts + top-5 open titles per repo) is fetched through the public backend proxy `GET /api/github/issues?org&repo&state=all` ONLY once the section scrolls near (fire-once IntersectionObserver, `rootMargin: 200px`) — batched Promise.all + `issuesRequestedRef` dedupe + ONE setState (never per-repo), errors swallowed (static links still render); backend caches this route 10 min. `RepoCard` + repo-link helpers are **module-scope** (SectionBlock remount lesson). When no repos exist at all, render the "Where's the code?" `--surface-2` card (event-page links + `slack_channel` button) — never hide the section silently. Header top row shows an `#code-and-tasks` anchor `.ohx-tag` when repos exist. Backend fix-forward: `approve_team` (`api/teams/teams_service.py::_link_repo_to_problem_statements`) now also appends the created repo to the linked problem statement(s)' `github` array (team `problem_statements` refs preferred; single-PS-nonprofit fallback; ambiguous → skip + log) and clears `get_single_problem_statement_old`'s cache. **Events & Teams section:** `src/components/Events/Events.js` was rewritten as refined event cards — title is `event.title` (NOT `{location} {type}` — that rendered a street address as the heading), Past/Upcoming `.ohx-tag`, compact date range via `parseLocalDate`, location + description as muted meta, constraint `.ohx-tag`s, ghost Event-page/DevPost buttons, plus a **read-only** teams list (name, member first-names, link to `/hack//team/`) fed by `teamsByEvent` from the same ProblemStatement memo that derives team repos. The old interactive join/leave path was dead (its `teams` state was never populated) and was removed from ProblemStatement along with the per-user `userDetails` fetch — `event-teams.js` and `event-team.js` are now orphaned; don't re-add join/leave here (team joining lives on the event page / manageteam). **Reference Documents:** `ReferenceItem.js` restyled from outlined MUI buttons to hairline row-cards (`ohx-card--hover` `` rows: navy kind icon + sentence-case name + kind `.ohx-tag` [GitHub/Slides/Document/Video/Link] + open-in-new indicator; `docs.google.com/presentation` now classified as Slides); video references keep their `VideoDisplay` embed. Only ProblemStatement consumes it, so `.ohx-*` classes are safe there. `styles/nonprofit/styles.js` is now orphaned — leave in place. -See `docs/refined-design-system.md`. Pattern for new pages: keep `Head`/`getStaticProps`/schema verbatim, add ``, wrap body in ``, rebuild sections with `.ohx-*` classes, one `

`, preserve GA. +See `docs/refined-design-system.md`. Pattern for new pages: keep `Head`/`getStaticProps`/schema verbatim, wrap body in `` (or the `RefinedPage` shell below — fonts load globally, do NOT add ``), rebuild sections with `.ohx-*` classes, one `

`, preserve GA. + +**Refined pages batch (Oct 2026):** `src/components/design/RefinedPage.js` is the shell for simple content pages — `RefinedPage` (RefinedRoot + `.ohx-wrap` section that clears the 64px NavBar; `narrow` for `.ohx-narrow`, `as="article"`), `PageMasthead` (eyebrow → the ONE `

` with optional `italic` accent phrase → `.ohx-lead`), `BackLink`, `Prose` (`.ohx-prose` — Fraunces h2/h3, 70ch measure, accent list markers, navy underlined links; added to `refined.js`), `LinkRow` (one primary `.ohx-btn` + soft `.ohx-link`s) and `CtaBand` (navy closing band). Migrated with it: `/404` (random sleeping image in a reserved 4:3 card; GA `404_error` kept), `/contact` (form logic, reCAPTCHA, `?type=` prefill and the mentor/judge read-first Dialog untouched; MUI fields under `refinedFormTheme` + `refinedFieldSx`; submit is a native `.ohx-btn--primary`), `/feedback` (index), `/about/why` + `/about/why/[title]` (the `whyPages[slug].Content` MUI bodies render inside `Prose`, whose `.ohx-prose .MuiTypography-*` rules restyle their `h4`→Fraunces / `body1`→17px; the 10 `src/components/content/*Content.js` files now use `component="h2"` and no inline 14px), `/about/privacy-policy` (`LAST_UPDATED` constant = the date the policy TEXT last changed — it used to print today's date), `/hack/liability-waiver`, `/hack/photo-release`, `/praise/[id]`, `/hack/[event_id]/results` + `/hack/results` (chrome only; `HackathonResults`/`HackathonFunnel` unchanged), and the four `/about/success-stories/` case studies, which are now plain `story` data objects rendered by `src/components/SuccessStories/SuccessStoryLayout.js` (owns the Head: canonical `www`, fixed `og:url` — they pointed at a non-existent `/success-stories/…` path — Article + BreadcrumbList JSON-LD via `serializeJsonLd`, CDN `og:image`s). **Still not refined** (next batches): the SEO pillar pages (`/coding-for-nonprofits`, `/hackathon-for-social-good`, `/hackathon-judge-opportunities`, `/hackathon-judging-criteria`, `/volunteer/professional-development-volunteering`, `/about/hackers/software-engineering-career-path`, `/about/locations/asu-tempe-arizona`), the tutorial pages (`/about/judges/overview`, `/about/mentors/overview`, `/judge/overview`), `/about/hearts` + `/community-champions` (700-line components), `/blog/[blog_id]`, `/cert`, `/hack/[event_id]/{media,agenda}`, `/store/*`, `/signup2`, `/unsubscribe`; intentionally bespoke: `/12-years-of-social-good`, `/about/style-guide`, print pages (`/hack/flier`, `/hack/[event_id]/print-timeline`), the planning board (own theme), redirect stubs. The homepage and projects page were reimagined away from the old busy/multi-gradient look into a calm "civic editorial" system. Shared tokens + utility classes live in `src/components/design/refined.js`: diff --git a/docs/refined-design-system.md b/docs/refined-design-system.md index f549abb5..cf190a44 100644 --- a/docs/refined-design-system.md +++ b/docs/refined-design-system.md @@ -67,6 +67,12 @@ Never reintroduce the old rainbow of status chips. single replacement for all the old colored MUI chips. - Sponsor logos: wrap a logo row in `.ohx-sponsors` → logos render grayscale at low opacity, regain color on hover (classic restrained treatment). +- Prose: `.ohx-prose` for long-form content (legal pages, essays, case studies) — + Fraunces `h2`/`h3`, 70ch measure, accent list markers, navy underlined links. +- Page shell: `src/components/design/RefinedPage.js` exports `RefinedPage` + (RefinedRoot + `.ohx-wrap` section with navbar clearance), `PageMasthead` + (eyebrow → the one `

` → lead), `BackLink`, `Prose`, `CtaBand` (navy band, + one primary + one ghost) and `LinkRow` for simple content pages. - Motion: `.rise` = one staggered fade-up on load; set per-element `style={{ animationDelay: 'Nms' }}`. Honors `prefers-reduced-motion`. @@ -118,6 +124,7 @@ visually, disable cache via CDP (`Network.setCacheDisabled` + ## Rollout status - [x] `/` (homepage) - [x] `/projects` +- [x] Oct 2026 batch via `RefinedPage`: `/404`, `/contact`, `/feedback`, `/about/why` (+ `[title]`), `/about/privacy-policy`, `/hack/liability-waiver`, `/hack/photo-release`, `/praise/[id]`, `/hack/[event_id]/results`, `/hack/results`, the four `/about/success-stories/` case studies (`SuccessStoryLayout`) - [x] Global NavBar - [x] `/nonprofits` — `NonProfitList` rewritten; new calm card `NonProfitListTileRefined` (the original `NonProfitListTile` is still used diff --git a/src/components/SuccessStories/SuccessStoryLayout.js b/src/components/SuccessStories/SuccessStoryLayout.js new file mode 100644 index 00000000..ac082451 --- /dev/null +++ b/src/components/SuccessStories/SuccessStoryLayout.js @@ -0,0 +1,366 @@ +import React, { useEffect } from "react"; +import Head from "next/head"; +import Link from "next/link"; +import { initFacebookPixel } from "../../lib/ga"; +import { serializeJsonLd } from "../../lib/jsonLd"; +import { + RefinedPage, + BackLink, + PageMasthead, + CtaBand, +} from "../design/RefinedPage"; +import { Eyebrow } from "../design/refined"; + +const SITE = "https://www.ohack.dev"; + +const cardStyle = { padding: "22px 24px" }; + +const ItemList = ({ items }) => ( +
    + {items.map((item) => ( +
  • + {item} +
  • + ))} +
+); + +/** + * One layout for every case-study page under /about/success-stories/. + * `story` is plain data (see the four page files); this component owns the + * SEO head (canonical + Article/Breadcrumb JSON-LD) and the refined chrome. + */ +export default function SuccessStoryLayout({ story }) { + const { + slug, + nonprofit, + headline, + lead, + meta, + video, + facts = [], + challenge, + solution, + resources = [], + impact, + years, + } = story; + const pageUrl = `${SITE}/about/success-stories/${slug}`; + + useEffect(() => { + initFacebookPixel(); + }, []); + + const jsonLd = [ + { + "@context": "https://schema.org", + "@type": "Article", + headline: headline, + description: meta.description, + image: meta.image, + url: pageUrl, + mainEntityOfPage: pageUrl, + author: { "@type": "Organization", name: "Opportunity Hack", url: SITE }, + publisher: { + "@type": "Organization", + name: "Opportunity Hack", + url: SITE, + }, + about: { "@type": "Organization", name: nonprofit }, + }, + { + "@context": "https://schema.org", + "@type": "BreadcrumbList", + itemListElement: [ + { "@type": "ListItem", position: 1, name: "Home", item: `${SITE}/` }, + { + "@type": "ListItem", + position: 2, + name: "Success stories", + item: `${SITE}/about/success-stories`, + }, + { "@type": "ListItem", position: 3, name: headline, item: pageUrl }, + ], + }, + ]; + + return ( + <> + + {meta.title} + + {meta.keywords && } + + + + + + + + + + + {jsonLd.map((sd, i) => ( +