Public-facing impact reports for the String volunteer-run edutech ecosystem. Tracks metrics, updates, and impact across all active and past products.
Live site → reports.string.sg
| Product | Status | Report |
|---|---|---|
| String — Solutions Aggregator | Building | /string-solutions |
| Events | Building | /events |
| Diagrams | Building | /diagrams |
| MatCHER | Building | /matcher |
| Bingo | Maintenance | /bingo |
| Whine — Problem Bank | Deprecated | /whine |
| Remarks Co-Pilot | Deprecated | /remarkscopilot |
- Node.js v18 or later
- npm (bundled with Node.js)
git clone https://github.com/String-sg/reports.git
cd reports
npm installnpm run devOpens at http://localhost:3000 with hot-reload.
npm run build # static export via Next.js
npm run start # serve the built output locallynpm run lint # runs eslint on the whole projectNote:
eslintis referenced in thelintscript but is not yet listed indevDependencies. Runnpm install -D eslint(or add it topackage.json) before using this command. Thebuildstep does not block on TypeScript errors (typescript.ignoreBuildErrors: trueinnext.config.mjs) — runnpx tsc --noEmitto check types manually.
| Layer | Choice |
|---|---|
| Framework | Next.js 16 (App Router) |
| UI | React 19 + shadcn/ui |
| Styling | Tailwind CSS v4 |
| Analytics | @vercel/analytics |
| Deployment | Vercel |
reports/
├── app/
│ ├── layout.tsx # Root layout — fonts, metadata, analytics
│ ├── page.tsx # Index page — lists all products
│ ├── globals.css # ✅ Active design tokens (dark-mode-only)
│ ├── opengraph-image.tsx # OG image for the index page
│ └── [slug]/
│ ├── page.tsx # Dynamic product detail page
│ └── opengraph-image.tsx # OG image per product
├── components/
│ ├── project-card.tsx # Product card used on the index
│ ├── deprecated-alternative.tsx # Amber banner for deprecated products
│ └── ui/ # shadcn/ui primitives (mostly installed, partially used)
├── lib/
│ └── data.ts # ⭐ Single source of truth for all product data
├── styles/
│ └── globals.css # ⚠️ Unused shadcn-default starter — do not edit
├── public/ # Static assets (favicons, icons)
├── next.config.mjs
├── components.json # shadcn/ui config
└── tsconfig.json
All product data lives in lib/data.ts as a single typed array PROJECTS: Project[]. There is exactly one dynamic route (app/[slug]/page.tsx) that renders any product by its slug. generateStaticParams enumerates PROJECTS at build time so every report is statically generated.
To add a new product report, add one entry to PROJECTS — no new page file is needed.
Key fields on Project:
| Field | Purpose |
|---|---|
slug |
URL path — e.g. "bingo" → /bingo |
status |
"Building" | "Prototype" | "Maintenance" | "Deprecated" |
teacherImpact |
Teacher-domain filtered user count. Feeds the "Teachers reached" aggregate on the home page. |
volunteers |
Volunteer headcount (overrides contributors.length for display). |
alternative |
If set and status is Deprecated, renders the amber alternative-product banner. |
highlightStat |
The single prominent stat shown on the product card and OG image. |
AGGREGATE (bottom of lib/data.ts) auto-computes:
totalUsers— sum ofteacherImpactacross all projectstotalVolunteers— sum ofvolunteersacross all projectstotalProjects— manual count of non-deprecated products
To change the home-page stat strip, edit the per-project fields — do not hardcode the aggregate values. Note that totalUsers and totalVolunteers are auto-computed from PROJECTS, but totalProjects is a manually maintained count of non-deprecated products and must be updated whenever a product changes status.
| Status | Sort order | Join CTA | Deprecated banner |
|---|---|---|---|
| Building | 1st | Shown | — |
| Maintenance | 2nd | Shown | — |
| Prototype | 3rd | Shown | — |
| Deprecated | 4th | Hidden | Shown if alternative is set |
If you add a new status value, update STATUS_ORDER in app/page.tsx, STATUS_CONFIG in components/project-card.tsx, and STATUS_COLOR/STATUS_BG in app/[slug]/opengraph-image.tsx.
Dynamic OG images are generated at build time via next/og ImageResponse:
| File | Route |
|---|---|
app/opengraph-image.tsx |
/ (index) |
app/[slug]/opengraph-image.tsx |
/<slug> (per product) |
Next.js auto-discovers these via file conventions. Do not add openGraph.images to app/layout.tsx metadata — it will conflict. Colours inside OG handlers are inlined as hex (CSS variables are not available in ImageResponse).
The site is dark-mode only — there is no light theme or theme toggle. Design tokens are defined in app/globals.css and consumed via Tailwind utilities (bg-card, text-primary, text-muted-foreground, etc.). See string-brand.md for the full token reference.
Fonts are loaded via next/font/google:
- Headings — Space Grotesk (
--font-space-grotesk) - Body / UI — Montserrat (
--font-montserrat)
| Package | Version | Purpose |
|---|---|---|
next |
16.2.0 | Framework — App Router, static generation, OG images |
react / react-dom |
19.2.x | UI rendering |
tailwindcss |
^4.2.0 | Utility-first CSS |
@vercel/analytics |
1.6.1 | Page-view analytics |
lucide-react |
^0.564.0 | Icon set |
class-variance-authority |
^0.7.1 | Variant-based class composition (shadcn/ui) |
clsx + tailwind-merge |
latest | Conditional class merging |
@radix-ui/* |
various | Headless UI primitives (installed via shadcn/ui) |
Most @radix-ui/* packages are installed via the shadcn/ui setup but are not actively used by current pages — only project-card.tsx and deprecated-alternative.tsx are wired into the app.
| Package | Version | Purpose |
|---|---|---|
typescript |
5.7.3 | Static typing |
@types/react / @types/react-dom |
19.x | React type definitions |
@tailwindcss/postcss |
^4.2.0 | Tailwind v4 PostCSS integration |
postcss |
^8.5 | CSS processing |
tw-animate-css |
1.3.3 | Animation utilities for shadcn/ui |
String is volunteer-run and always welcoming help. Here are the most impactful open areas:
- Add real contributor names —
contributorsarrays inlib/data.tsare all currently empty ([]). Fill in names, roles, and initials. - Update aggregate metrics —
totalProjectsinAGGREGATEis a manually maintained count; update it when products change status. Per-productteacherImpactandvolunteersfields also need keeping current (the aggregate totals are auto-computed from them). - Populate Diagrams and Bingo metrics — both products currently show "WIP / metrics coming soon".
- Prototype status badge — the
Prototypestatus exists in the type system but has no products using it yet; verify the badge renders correctly when needed. - Remove
styles/globals.css— this file is the unused shadcn-default OKLCH starter; deleting it removes confusion for future contributors. - Responsive polish — the aggregate stats strip and some detail-page sections could benefit from additional mobile testing.
- Add ESLint to devDependencies —
eslintis referenced in thelintscript but not yet listed inpackage.json; runnpm install -D eslintand commit the updatedpackage.json. - Add a product — the data model fully supports any new String product; just add an entry to
PROJECTSinlib/data.ts. - OG image typography — OG images currently use the system
sans-seriffallback. Loading Space Grotesk via afetch()call in the OG handler would match the site's visual identity.
- Review
dev.mdpending items — a list of open decisions and TODOs is maintained indev.md.
String runs entirely on volunteer effort. To join the team, visit join.string.sg.
For technical questions or to discuss a contribution, open an issue or pull request in this repository.