From 2cd7156419c8c04a9f00b4c43f92709afaffcd20 Mon Sep 17 00:00:00 2001 From: bhumindeshpande8-spec Date: Thu, 13 Aug 2026 11:42:27 +0530 Subject: [PATCH 1/2] add lfx and sob to hall of fame, edit text --- web/app/programmes/page.tsx | 14 ++++++- web/components/CommunityBanner.tsx | 6 ++- web/components/Team.tsx | 55 +++++++++++++++++------- web/components/hall/ContribWall.tsx | 2 +- web/components/hall/Hall.tsx | 3 +- web/components/hero/Terminal.tsx | 2 +- web/content/club.ts | 65 ++++++++++++++++++++++++++--- 7 files changed, 118 insertions(+), 29 deletions(-) diff --git a/web/app/programmes/page.tsx b/web/app/programmes/page.tsx index 67f14ec..a97f64e 100644 --- a/web/app/programmes/page.tsx +++ b/web/app/programmes/page.tsx @@ -136,8 +136,18 @@ function ProgrammeField({ p }: { p: ProgrammeInfo }) {
Who from SST has done it
+ {/* The written answer, for programmes where a count and a standout + name say more than a list of selections could — see `ours` in + ProgrammeInfo. It sits above the derived names rather than + replacing them, so adding a selection later cannot silently drop + this line, and this line cannot silently hide a selection. */} + {p.ours &&

{p.ours}

} {ours.length > 0 ? ( -
    +
      {ours.map((a) => (
    • {a.url ? ( @@ -162,7 +172,7 @@ function ProgrammeField({ p }: { p: ProgrammeInfo }) {
    • ))}
    - ) : ( + ) : p.ours ? null : ( // TIER-AWARE, because one sentence cannot serve both. The first // version said "the first person from this college to get in has not // been picked" for every programme — which is meaningless for diff --git a/web/components/CommunityBanner.tsx b/web/components/CommunityBanner.tsx index fc3b1ae..61c3b92 100644 --- a/web/components/CommunityBanner.tsx +++ b/web/components/CommunityBanner.tsx @@ -9,8 +9,10 @@ // reason — the numbers and the faces have to be real: // // 1. "Join 300+ Contributors". There is no 300 anywhere in this repo, and no -// field that could become one: the club records fifteen published -// selections, eight people running it, and one project with an API-verified +// field that could become one: the club records its published +// selections (SELECTIONS in club.ts, and the count moves as names come in — +// which is why it is not written out here), eight people running it, and +// one project with an API-verified // merge count. A recruitment page for sixteen-year-olds that inflates its // community by twentyfold is the kind of claim that costs the club its // credibility the first time somebody counts. The label reads from diff --git a/web/components/Team.tsx b/web/components/Team.tsx index a3ce201..d625dfd 100644 --- a/web/components/Team.tsx +++ b/web/components/Team.tsx @@ -357,7 +357,6 @@ function Node({ member, diameter, designation, - remit, tone = "quiet", priority = false, maxWidth = CARD_MAX_W, @@ -376,8 +375,11 @@ function Node({ }; diameter: string; designation?: string; - /** Overrides member.remit. A desk's remit belongs to the desk, not the person. */ - remit?: string; + /* The `remit` override that used to sit here is gone with its one caller. It + existed so a desk could push its own remit onto each member's hover card; + nothing does that any more, and a prop kept for a caller that no longer + exists is the next person's wrong turn. A remit on the card now means the + person holds an office that has one. */ tone?: "loud" | "quiet"; priority?: boolean; maxWidth?: string; @@ -386,6 +388,14 @@ function Node({ /** Which edge it is anchored to, so a card near the chart's edge stays on the page. */ tipAlign?: "start" | "center" | "end"; }) { + // Whether there is anything to say on hover. A node with nothing to say must not + // render a card: `.person-tip` is a styled bubble with padding and a border, so + // an empty one is a small dark rectangle that appears on hover and explains + // nothing. This became reachable the moment the desk stopped passing its remit + // down — three of its four members carry only a name. + const hasTip = Boolean( + member.remit || member.batch || member.highlights?.length, + ); return (
    - {member.batch && ( -

    Batch {member.batch}

    - )} -

    {remit ?? member.remit}

    - -
    + {hasTip && ( +
    + {member.batch && ( +

    Batch {member.batch}

    + )} + {member.remit && ( +

    {member.remit}

    + )} + +
    + )}
{member.github && ( @@ -709,12 +723,21 @@ export default function Team() { gridTemplateColumns: `repeat(${DESK.members.length}, minmax(0, 1fr))`, }} > + {/* NO REMIT PASSED DOWN, per the club. The desk's remit used to ride + on every member's hover card, which meant four faces in a row each + popping the same paragraph — the sentence is about the desk, not + about the person under the pointer, and reading it four times is + how you learn to stop hovering. It has not been deleted: the desk + label above these four still stands for it in the chart, and the + stacked list below writes it out once, in full, where it is read + aloud and where touch devices get it. What is left on hover is only + what belongs to the person — their batch and their highlights — and + a member with neither now gets no bubble at all. */} {DESK.members.map((m, i) => ( ))} diff --git a/web/components/hall/ContribWall.tsx b/web/components/hall/ContribWall.tsx index 42612ca..b6b7820 100644 --- a/web/components/hall/ContribWall.tsx +++ b/web/components/hall/ContribWall.tsx @@ -22,7 +22,7 @@ // // * It is NOT per-student. One wall under the whole grid reads as a section // motif; a strip behind each card would read as that person's activity, and -// none of these fifteen has activity data recorded anywhere in this repo. +// none of these students has activity data recorded anywhere in this repo. // // * The pattern is DETERMINISTIC, from a tiny integer hash rather than // Math.random(). Random would differ between the server render and the client diff --git a/web/components/hall/Hall.tsx b/web/components/hall/Hall.tsx index 6010890..9a60f10 100644 --- a/web/components/hall/Hall.tsx +++ b/web/components/hall/Hall.tsx @@ -345,7 +345,8 @@ export default function Hall() { {/* THE OPEN SLOT, last in the grid. Every other card is a record of something that already happened, which - makes the whole grid readable as a closed set — fifteen people, done. This + makes the whole grid readable as a closed set — this is who got picked, + done. This one card says the list is still being written, and it is the only card here that is an invitation rather than evidence. diff --git a/web/components/hero/Terminal.tsx b/web/components/hero/Terminal.tsx index a35f89f..abe7587 100644 --- a/web/components/hero/Terminal.tsx +++ b/web/components/hero/Terminal.tsx @@ -24,7 +24,7 @@ // The evidence did not go anywhere. It moved to where it was already stronger: // the floating chips still carry the API-verified merge count and the cohort // size (see Hero.tsx), the figure block under the hero states the total, and the -// hall names all fifteen with links. Nobody lost a credit; the credit stopped +// hall names every one of them with links. Nobody lost a credit; the credit stopped // being a leaderboard. // // NOTHING HERE IS A CLAIM. That is the other thing the change buys. A simulated diff --git a/web/content/club.ts b/web/content/club.ts index 9f7f38e..b6a27d1 100644 --- a/web/content/club.ts +++ b/web/content/club.ts @@ -385,6 +385,25 @@ const GSOC_2025: [name: string, studyYear: string, org?: string][] = [ ["Sauhard Gupta", "3rd year"], ]; +/* The first selections outside GSoC, and the reason the wall stops being a GSoC + wall. Same shape and same rules as the arrays above: the org slot stays empty + until the club supplies it, and the card prints "org TBA" rather than a guess. + + VIVEK SINGH SOLANKI APPEARS TWICE ON PURPOSE — once for GSoC 2026 above and once + for LFX below. Two selections into two programmes are two facts, and the wall is + a list of selections, not of people. Everything that counts PEOPLE dedupes by + name already (see NumbersStrip), so this adds a card without inflating the + member count. Note also that he is a different person from the "Vivek Singh" + selected into Summer of Bitcoin; the names are close enough that a future editor + will wonder, so: not a duplicate, and not a typo. */ +const SOB_2026: [name: string, studyYear: string, org?: string][] = [ + ["Vivek Singh", "2nd year"], +]; + +const LFX_2026: [name: string, studyYear: string, org?: string][] = [ + ["Vivek Singh Solanki", "3rd year", "Besu"], +]; + /** * The published list. These render everywhere — local, preview and production. * @@ -399,16 +418,17 @@ const GSOC_2025: [name: string, studyYear: string, org?: string][] = [ * WHAT IS STILL MISSING, and it belongs here rather than in a ticket: not one of * these entries has a proof `url`. The page's entire argument is "somebody else * picked them, go and check", and until each name carries a link — the programme's - * accepted-projects page, the student's proposal, the announcement — these fifteen - * are the only claims on this site a reader cannot verify. The roster prints an em - * dash in the Proof column for each, which is honest but is not evidence. Add `url` - * per entry as the links come in; nothing else has to change. + * accepted-projects page, the student's proposal, the announcement — these are the + * only claims on this site a reader cannot verify. The roster prints an em dash in + * the Proof column for each, which is honest but is not evidence. Add `url` per + * entry as the links come in; nothing else has to change. * * The ten LFX / C4GT / SoB rows that used to pad this list to twenty-five are gone, * deliberately. "Placeholder Seven, Example Foundation" is layout padding, not a * selected student; publishing it would have put visible nonsense on a public page - * beside fifteen real people and undercut every real name next to it. Those - * programmes get real entries the same way these did. + * beside real people and undercut every real name next to it. Those programmes get + * real entries the same way these did — the SoB and LFX rows below are the first of + * them, supplied by the club per student rather than generated to fill the grid. */ export const SELECTIONS: Selection[] = [ ...GSOC_2026.map(([name, studyYear, org]) => ({ @@ -427,6 +447,22 @@ export const SELECTIONS: Selection[] = [ org, consented: true, })), + ...SOB_2026.map(([name, studyYear, org]) => ({ + name, + programme: "SOB" as Programme, + year: "2026", + studyYear, + org, + consented: true, + })), + ...LFX_2026.map(([name, studyYear, org]) => ({ + name, + programme: "LFX" as Programme, + year: "2026", + studyYear, + org, + consented: true, + })), ]; /** @@ -525,6 +561,20 @@ export type ProgrammeInfo = { pays: string; /** What OSC specifically does to get you in. This is the product. */ weDo: string; + /** + * What the club has to show for this programme, when a list of names is the + * wrong shape for it. + * + * The "who from SST has done it" row is normally DERIVED from SELECTIONS, and + * that stays the default: a selective programme's answer is a set of people who + * were each individually picked, and deriving it is what stops this page and the + * Hall of Fame disagreeing. But the open tier is a participation programme — + * dozens take part and there is nothing to be selected into — so the honest + * answer is a count plus whoever stood out, which no list of Selection rows can + * express without inventing a selection that never happened. Prose here, names + * in SELECTIONS; when both exist the row shows both. + */ + ours?: string; url: string; }; @@ -589,6 +639,8 @@ export const PROGRAMMES: ProgrammeInfo[] = [ "No stipend. Certificates, swag and a leaderboard — plus mentors, which is the part that is actually worth having.", weDo: "Nothing to prepare. Register when it opens and pick a project in a language you can already run. Use it to learn the mechanics — fork, branch, PR, review, merge — so the paid programmes below are not your first time using Git in anger.", + ours: + "30+ students participated in GSSoC '26. Top contributor from SST: Bhumi N Deshpande.", url: "https://gssoc.girlscript.tech/", }, { @@ -601,6 +653,7 @@ export const PROGRAMMES: ProgrammeInfo[] = [ pays: "Swag, or a tree planted in your name. That is the whole reward.", weDo: "Have one project picked and built on your machine before 1 October, so you spend the month contributing rather than setting up. Come to a session in September and we will do it with you.", + ours: "Many of us had our first PRs merged here.", url: "https://hacktoberfest.com/", }, ]; From 80b09ce3f81c221e8026e5492886bff2d1c87f7c Mon Sep 17 00:00:00 2001 From: bhumindeshpande8-spec Date: Thu, 13 Aug 2026 13:09:09 +0530 Subject: [PATCH 2/2] strong cta --- web/app/join/page.tsx | 59 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/web/app/join/page.tsx b/web/app/join/page.tsx index 6f7b842..63248f0 100644 --- a/web/app/join/page.tsx +++ b/web/app/join/page.tsx @@ -11,6 +11,11 @@ import Sticker from "@/components/fx/Sticker"; // the next action, and a closing "here is what to do next" band underneath a // half-filled form is an invitation to abandon it. // +// It does now close on a band, but the distinction above is what that band is +// built to respect — it contains no link, so it argues for the form rather than +// offering somewhere else to be. Anything added down there has to clear the same +// bar: no exits under an unfinished form. +// // It is also absent from PAGES, so it appears in neither the nav strip nor beside // the other routes in the footer. The nav carries it as a filled button at the // other end of the bar — putting the same word in the strip as well would be the @@ -163,6 +168,60 @@ export default function Join() { + + {/* ---- The two futures --------------------------------------------- + The closing band, and the exception the comment at the top of this + file now carries: it is not a NextAction. Every other route ends by + handing you somewhere else to go, which under a half-filled form + would be an exit. This band has no link in it at all. It restates + the decision already on the screen and then stops, so the only + thing to do with it is scroll back up and finish. + + NOT INSIDE ApplyForm. It lived in the card for one revision and the + card is the wrong container: at 15px inside a 7-unit padded tile it + read as a third disclaimer under the two grey notes about data, and + disclaimers are what people skip. On the page at display-md it is + the loudest type below the hero. + + The asymmetry is the whole argument. Both eyebrows take .label, so + they read as one pair rather than a warning and a reward — the + weight sits in the body copy instead: `dust` on the left, `ink` on + the right, plus the accent rule down the right column. The future + where you did nothing is literally the dimmer of the two. + Do not try to colour the eyebrow to sharpen it: .label sets its own + colour and every custom class in globals.css is declared after + @tailwind utilities, so a text-* utility on it silently does + nothing. See the note on .page-top there. */} +
+ {/* One column until lg, and lg rather than sm because these are + 30-word paragraphs at display size — the point where two of them + fit side by side without either dropping to four words a line is + a good deal wider than the point where two columns fit. */} +
+
+

If you close this tab without applying

+

+ You'll open it again in February. The same form, one semester + less, and a batch of students who already know how to review your + code. +

+
+ + {/* The accent rule is the second column's left edge at lg and its top + edge below that, so the band never loses the mark that says which + of the two futures the page is pointing at. */} +
+

If you hit the button

+

+ Someone reads it this week. You show up regularly. By November + you're the one answering the questions. +

+
+
+
); }