Skip to content
Merged
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
36 changes: 17 additions & 19 deletions docs/homepage-redesign-roadmap.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# Homepage Redesign Roadmap

Status: Active — PRs 1–4 merged; calendar event details in progress
Status: Active — PRs 1–4 and 4A merged; Community links and page order in progress
Primary audience: People considering their first Kyoto Tech Meetup
Secondary audience: Existing community members looking for events, venue details, conversations, and member work

## Goal

Redesign the homepage so a first-time visitor can understand the meetup and reach an RSVP as quickly as possible, while preserving the site's role as a useful community resource hub.

The redesign should replace generic, text-heavy marketing sections with concrete information, real community evidence, and direct actions. Existing members should be able to bypass newcomer-oriented content through clear navigation and task-based shortcuts.
The redesign should replace generic, text-heavy marketing sections with concrete information, real community evidence, and direct actions. Existing members should be able to bypass newcomer-oriented content through clear navigation and recognizable links to frequently used resources.

## Audience priority

Expand All @@ -21,16 +21,16 @@ Prioritizing new visitors does not mean burying member resources. The homepage s

```text
Fixed navigation
Next meetup · Calendar · Locations · Community Hub · Language · RSVP
Next meetup · Calendar · Community links · Language · RSVP

Event-led hero
Clear invitation · Next-event details · Direct RSVP · Community photograph

First-meetup expectations
Practical first-visit facts · Secondary RSVP

Community Hub
Events · Discord · Member posts · GitHub · Contact
Community links
Meetup · Discord · GitHub · Contact

Upcoming events
Mobile event list · Desktop calendar · RSVP counts · Maps links
Expand Down Expand Up @@ -61,7 +61,7 @@ Final invitation and footer
| 3 | Event-led hero | PR 2 | New visitors see and can RSVP for the next event immediately |
| 4 | First-meetup onboarding | PR 3 | Generic end-of-page marketing sections are replaced with practical guidance |
| 4A | Calendar event details | PR 2 | Calendar events show RSVP counts and direct Maps links without a separate locations section |
| 5 | Community Hub and page order | PRs 1 and 4 | Returning members have task-based shortcuts and the page has a coherent narrative |
| 5 | Community links and page order | PRs 1 and 4 | Returning members have fast access to frequently used resources and the page has a coherent narrative |
| 6 | Responsive events | PRs 2, 4A, and 5 | Mobile event discovery becomes easier and lighter |
| 7 | Member feed refinement | PR 5 | Member work becomes authentic community evidence rather than a long set of carousels |
| 8 | Accessibility, performance, analytics, and final QA | PRs 3–7 | The complete journey is verified and measurable |
Expand Down Expand Up @@ -322,33 +322,31 @@ Make each calendar event more useful at a glance and consolidate venue discovery

---

## PR 5: Community Hub and page order
## PR 5: Community links and page order

### Objective

Give existing members fast, task-oriented access to resources without interrupting the newcomer joining path.
Give existing members fast access to frequently used resources without interrupting the newcomer joining path.

### Scope

- Move the current quick links below the first-meetup onboarding section.
- Rename the section to “Community Hub.”
- Mix internal destination links with external community services.
- Rename the section to “Community links.”
- Preserve the four existing external destinations: Meetup, Discord, GitHub, and Contact.
- Reorder the remainder of the homepage according to the target structure.
- Add a fixed-navigation destination for the hub.
- Restore a compact community-size signal near the hub introduction, backed by Meetup data rather than a hard-coded claim.

### Suggested shortcuts
### Resource links

- Upcoming events → `#calendar`
- Meetup → community Meetup group
- Discord → community Discord
- Member posts → `#community-feed`
- GitHub → community GitHub organization
- Contact organizers → contact form
- Contact → organizer contact form

### Implementation notes

- Rename `#quick-links` to `#community-hub` in `src/pages/index.astro`.
- Update the link data to describe tasks rather than repeat service names alone.
- Show external-link indicators only for external destinations.
- Keep the link cards concise; one label and, at most, one short supporting line.
- Update header anchors and localized navigation strings.
Expand All @@ -360,12 +358,12 @@ Give existing members fast, task-oriented access to resources without interrupti
- Preserve the last valid member count when Meetup is unavailable or the count cannot be parsed; a count failure must not discard otherwise valid event data.
- Convert the exact count to a conservative public milestone by rounding down to the nearest 25. For example, counts from 225 through 249 render as “225+ members and growing.”
- Do not render the module when there is no valid cached count or the rounded milestone is below 25.
- Keep the module out of the event-led hero. Place it as concise community-level social proof near the Community Hub introduction.
- Use the module as a small eyebrow above the hero heading. Keep it free of borders, fills, pill shapes, and hover states so it cannot be mistaken for a button or alert.
- Add aligned English and Japanese strings, and keep formatting in a small tested helper rather than embedding rounding logic in the template.

### Acceptance criteria

- A returning visitor can reach every core resource from either the header or Community Hub.
- A returning visitor can reach every core resource from either the header or Community links.
- The newcomer path remains hero → expectations → RSVP before the resource hub begins.
- Internal and external destinations are visually distinguishable without excessive decoration.
- Mobile navigation exposes the same essential destinations as desktop navigation.
Expand Down Expand Up @@ -565,15 +563,15 @@ These decisions should not block PR 1 or PR 2, but they must be resolved before
- A member login or private portal
- Machine translation of member-authored posts
- Replacing Meetup as the RSVP system
- Splitting the Community Hub into a separate route before the resource set requires it
- Splitting Community links into a separate route before the resource set requires it

## Definition of success

The redesign is successful when:

- A new visitor can identify and reach the next meetup's RSVP from the first viewport.
- First-time concerns are answered with concise, factual guidance instead of generic marketing copy.
- Returning members can reach core resources directly from navigation or the Community Hub.
- Returning members can reach core resources directly from navigation or Community links.
- The mobile event experience does not depend on horizontal calendar scrolling.
- Real event imagery and member work provide the page's primary social proof.
- Both locale routes respect explicit visitor choice.
Expand Down
25 changes: 22 additions & 3 deletions scripts/fetch-meetup-events.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { fileURLToPath } from "node:url";
import {
DEFAULT_MEETUP_EVENTS_URL,
DEFAULT_MEETUP_TIMEOUT_MS,
fetchMeetupEvents,
fetchMeetupSnapshot,
isMeetupEvent,
} from "../src/lib/meetup-events.ts";

Expand Down Expand Up @@ -57,6 +57,13 @@ export function isValidMeetupEventCache(value) {
) {
return false;
}
if (
value.memberCount !== undefined &&
value.memberCount !== null &&
(!Number.isInteger(value.memberCount) || value.memberCount < 0)
) {
return false;
}

return value.events.every(isMeetupEvent);
}
Expand All @@ -79,17 +86,29 @@ async function writeMeetupEventCache(outputPath, payload) {

export async function refreshMeetupEventCache({
eventsUrl = DEFAULT_EVENTS_URL,
fetchEventsFn = fetchMeetupEvents,
fetchSnapshotFn = fetchMeetupSnapshot,
logger = console,
now = new Date(),
outputPath = DEFAULT_OUTPUT_PATH,
staleOk = false,
timeoutMs = DEFAULT_TIMEOUT_MS,
} = {}) {
try {
const events = await fetchEventsFn({ eventsUrl, now, timeoutMs });
const snapshot = await fetchSnapshotFn({ eventsUrl, now, timeoutMs });
const events = snapshot?.events;
if (!Array.isArray(events) || !events.every(isMeetupEvent)) {
throw new Error("Meetup response did not contain valid event data.");
}

const parsedMemberCount = snapshot?.memberCount;
const existing = await readMeetupEventCache(outputPath);
const memberCount =
Number.isInteger(parsedMemberCount) && parsedMemberCount >= 0
? parsedMemberCount
: (existing?.memberCount ?? null);
const payload = {
generatedAt: now.toISOString(),
memberCount,
events,
};

Expand Down
11 changes: 11 additions & 0 deletions src/components/Hero.astro
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
import type { MeetupEvent } from "../lib/meetup-events";
import { FaUserGroup } from "react-icons/fa6";
import NextEventCard from "./NextEventCard.astro";

type Props = {
Expand All @@ -9,6 +10,7 @@ type Props = {
imageAlt: string;
imageSrc: string;
lang?: "en" | "ja";
memberMilestoneLabel?: string | null;
now?: Date;
primaryCtaLabel: string;
secondaryCtaLabel: string;
Expand All @@ -23,6 +25,7 @@ const {
imageAlt,
imageSrc,
lang = "en",
memberMilestoneLabel = null,
now = new Date(),
primaryCtaLabel,
secondaryCtaLabel,
Expand All @@ -49,6 +52,14 @@ const primaryCtaUrl = event?.link ?? fallbackUrl;

<div class="mx-auto grid min-h-[50rem] w-full max-w-6xl grid-cols-1 content-center items-start gap-y-6 px-6 pb-28 pt-28 sm:px-10 sm:pb-32 sm:pt-32 lg:h-full lg:min-h-0 lg:grid-cols-[minmax(0,1fr)_minmax(22rem,0.76fr)] lg:grid-rows-[auto_auto] lg:content-center lg:gap-x-14 lg:gap-y-7 lg:px-12">
<div class="order-1 lg:col-start-1 lg:row-start-1">
{
memberMilestoneLabel ? (
<p class="mb-4 flex items-center gap-2 text-xs font-semibold uppercase tracking-[0.16em] text-slate-100 drop-shadow-sm">
<FaUserGroup className="h-3.5 w-3.5" aria-hidden="true" />
{memberMilestoneLabel}
</p>
) : null
}
<h1
class:list={[
"max-w-2xl whitespace-pre-line break-keep font-semibold leading-[1.04] tracking-[-0.035em] text-white drop-shadow-sm",
Expand Down
Loading
Loading